diff --git a/.coveragerc b/.coveragerc
index a8d7d89544d..d5296455981 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -28,6 +28,9 @@ omit =
homeassistant/components/apple_tv.py
homeassistant/components/*/apple_tv.py
+ homeassistant/components/aqualogic.py
+ homeassistant/components/*/aqualogic.py
+
homeassistant/components/arduino.py
homeassistant/components/*/arduino.py
@@ -53,7 +56,7 @@ omit =
homeassistant/components/bbb_gpio.py
homeassistant/components/*/bbb_gpio.py
- homeassistant/components/blink.py
+ homeassistant/components/blink/*
homeassistant/components/*/blink.py
homeassistant/components/bloomsky.py
@@ -105,8 +108,11 @@ omit =
homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py
+ homeassistant/components/evohome.py
+ homeassistant/components/*/evohome.py
+
homeassistant/components/fritzbox.py
- homeassistant/components/switch/fritzbox.py
+ homeassistant/components/*/fritzbox.py
homeassistant/components/ecovacs.py
homeassistant/components/*/ecovacs.py
@@ -310,6 +316,9 @@ omit =
homeassistant/components/*/thinkingcleaner.py
+ homeassistant/components/tibber/*
+ homeassistant/components/*/tibber.py
+
homeassistant/components/toon.py
homeassistant/components/*/toon.py
@@ -510,6 +519,7 @@ omit =
homeassistant/components/light/lw12wifi.py
homeassistant/components/light/mystrom.py
homeassistant/components/light/nanoleaf_aurora.py
+ homeassistant/components/light/opple.py
homeassistant/components/light/osramlightify.py
homeassistant/components/light/piglow.py
homeassistant/components/light/rpi_gpio_pwm.py
@@ -620,7 +630,6 @@ omit =
homeassistant/components/notify/telstra.py
homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py
- homeassistant/components/notify/yessssms.py
homeassistant/components/nuimo_controller.py
homeassistant/components/prometheus.py
homeassistant/components/rainbird.py
@@ -679,6 +688,7 @@ omit =
homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py
homeassistant/components/sensor/geizhals.py
+ homeassistant/components/sensor/gitlab_ci.py
homeassistant/components/sensor/gitter.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py
@@ -687,6 +697,7 @@ omit =
homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py
+ homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py
@@ -709,6 +720,7 @@ omit =
homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/mvglive.py
homeassistant/components/sensor/nederlandse_spoorwegen.py
+ homeassistant/components/sensor/netatmo_public.py
homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/netdata_public.py
homeassistant/components/sensor/neurio_energy.py
@@ -764,7 +776,6 @@ omit =
homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py
- homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py
homeassistant/components/sensor/trafikverket_weatherstation.py
@@ -772,7 +783,6 @@ omit =
homeassistant/components/sensor/travisci.py
homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py
- homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/ups.py
homeassistant/components/sensor/uscis.py
homeassistant/components/sensor/vasttrafik.py
diff --git a/CODEOWNERS b/CODEOWNERS
index b6ce8c04909..91d5fd67670 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -41,6 +41,7 @@ homeassistant/components/hassio.py @home-assistant/hassio
# Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
+homeassistant/components/alarm_control_panel/simplisafe.py @bachya
homeassistant/components/binary_sensor/hikvision.py @mezz64
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/camera/yi.py @bachya
@@ -50,6 +51,7 @@ homeassistant/components/climate/sensibo.py @andrey-git
homeassistant/components/cover/group.py @cdce8p
homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills
+homeassistant/components/device_tracker/huawei_router.py @abmantis
homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/lifx.py @amelchio
@@ -80,19 +82,21 @@ homeassistant/components/sensor/qnap.py @colinodell
homeassistant/components/sensor/sma.py @kellerza
homeassistant/components/sensor/sql.py @dgomes
homeassistant/components/sensor/sytadin.py @gautric
-homeassistant/components/sensor/tibber.py @danielhiversen
-homeassistant/components/sensor/upnp.py @dgomes
homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/axis.py @kane610
+homeassistant/components/blink/* @fronzbot
+homeassistant/components/*/blink.py @fronzbot
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/*/deconz.py @kane610
homeassistant/components/ecovacs.py @OverloadUT
homeassistant/components/*/ecovacs.py @OverloadUT
+homeassistant/components/edp_redy.py @abmantis
+homeassistant/components/*/edp_redy.py @abmantis
homeassistant/components/eight_sleep.py @mezz64
homeassistant/components/*/eight_sleep.py @mezz64
homeassistant/components/hive.py @Rendili @KJonline
@@ -106,7 +110,7 @@ homeassistant/components/konnected.py @heythisisnate
homeassistant/components/*/konnected.py @heythisisnate
homeassistant/components/matrix.py @tinloaf
homeassistant/components/*/matrix.py @tinloaf
-homeassistant/components/openuv.py @bachya
+homeassistant/components/openuv/* @bachya
homeassistant/components/*/openuv.py @bachya
homeassistant/components/qwikswitch.py @kellerza
homeassistant/components/*/qwikswitch.py @kellerza
@@ -119,6 +123,8 @@ homeassistant/components/tesla.py @zabuldon
homeassistant/components/*/tesla.py @zabuldon
homeassistant/components/tellduslive.py @molobrakos @fredrike
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
+homeassistant/components/tibber/* @danielhiversen
+homeassistant/components/*/tibber.py @danielhiversen
homeassistant/components/*/tradfri.py @ggravlingen
homeassistant/components/upcloud.py @scop
homeassistant/components/*/upcloud.py @scop
diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py
index fb4700c806f..54c34d8ec2c 100644
--- a/homeassistant/auth/auth_store.py
+++ b/homeassistant/auth/auth_store.py
@@ -1,9 +1,9 @@
"""Storage for auth models."""
from collections import OrderedDict
from datetime import timedelta
+import hmac
from logging import getLogger
from typing import Any, Dict, List, Optional # noqa: F401
-import hmac
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback
@@ -28,7 +28,8 @@ class AuthStore:
"""Initialize the auth store."""
self.hass = hass
self._users = None # type: Optional[Dict[str, models.User]]
- self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
+ self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
+ private=True)
async def async_get_users(self) -> List[models.User]:
"""Retrieve all users."""
@@ -213,14 +214,24 @@ class AuthStore:
if self._users is not None:
return
- users = OrderedDict() # type: Dict[str, models.User]
-
if data is None:
- self._users = users
+ self._set_defaults()
return
+ users = OrderedDict() # type: Dict[str, models.User]
+
+ # When creating objects we mention each attribute explicetely. This
+ # prevents crashing if user rolls back HA version after a new property
+ # was added.
+
for user_dict in data['users']:
- users[user_dict['id']] = models.User(**user_dict)
+ users[user_dict['id']] = models.User(
+ name=user_dict['name'],
+ id=user_dict['id'],
+ is_owner=user_dict['is_owner'],
+ is_active=user_dict['is_active'],
+ system_generated=user_dict['system_generated'],
+ )
for cred_dict in data['credentials']:
users[cred_dict['user_id']].credentials.append(models.Credentials(
@@ -340,3 +351,7 @@ class AuthStore:
'credentials': credentials,
'refresh_tokens': refresh_tokens,
}
+
+ def _set_defaults(self) -> None:
+ """Set default values for auth store."""
+ self._users = OrderedDict() # type: Dict[str, models.User]
diff --git a/homeassistant/auth/mfa_modules/notify.py b/homeassistant/auth/mfa_modules/notify.py
index 84f9de614c1..03be4c74d32 100644
--- a/homeassistant/auth/mfa_modules/notify.py
+++ b/homeassistant/auth/mfa_modules/notify.py
@@ -85,7 +85,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
super().__init__(hass, config)
self._user_settings = None # type: Optional[_UsersDict]
self._user_store = hass.helpers.storage.Store(
- STORAGE_VERSION, STORAGE_KEY)
+ STORAGE_VERSION, STORAGE_KEY, private=True)
self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, [])
self._message_template = config[CONF_MESSAGE]
diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py
index 625cc0302e1..9b5896ef666 100644
--- a/homeassistant/auth/mfa_modules/totp.py
+++ b/homeassistant/auth/mfa_modules/totp.py
@@ -67,7 +67,7 @@ class TotpAuthModule(MultiFactorAuthModule):
super().__init__(hass, config)
self._users = None # type: Optional[Dict[str, str]]
self._user_store = hass.helpers.storage.Store(
- STORAGE_VERSION, STORAGE_KEY)
+ STORAGE_VERSION, STORAGE_KEY, private=True)
@property
def input_schema(self) -> vol.Schema:
diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py
index b0f4024c3ab..bd00ca72b83 100644
--- a/homeassistant/auth/models.py
+++ b/homeassistant/auth/models.py
@@ -19,19 +19,19 @@ class User:
"""A user."""
name = attr.ib(type=str) # type: Optional[str]
- id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
+ id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
is_owner = attr.ib(type=bool, default=False)
is_active = attr.ib(type=bool, default=False)
system_generated = attr.ib(type=bool, default=False)
# List of credentials of a user.
credentials = attr.ib(
- type=list, default=attr.Factory(list), cmp=False
+ type=list, factory=list, cmp=False
) # type: List[Credentials]
# Tokens associated with a user.
refresh_tokens = attr.ib(
- type=dict, default=attr.Factory(dict), cmp=False
+ type=dict, factory=dict, cmp=False
) # type: Dict[str, RefreshToken]
@@ -48,12 +48,10 @@ class RefreshToken:
validator=attr.validators.in_((
TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM,
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)))
- id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
- created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
- token = attr.ib(type=str,
- default=attr.Factory(lambda: generate_secret(64)))
- jwt_key = attr.ib(type=str,
- default=attr.Factory(lambda: generate_secret(64)))
+ id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
+ created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
+ token = attr.ib(type=str, factory=lambda: generate_secret(64))
+ jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64))
last_used_at = attr.ib(type=Optional[datetime], default=None)
last_used_ip = attr.ib(type=Optional[str], default=None)
@@ -69,7 +67,7 @@ class Credentials:
# Allow the auth provider to store data to represent their auth.
data = attr.ib(type=dict)
- id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
+ id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
is_new = attr.ib(type=bool, default=True)
diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py
index c743a5b7f65..8710e7c60bc 100644
--- a/homeassistant/auth/providers/homeassistant.py
+++ b/homeassistant/auth/providers/homeassistant.py
@@ -52,7 +52,8 @@ class Data:
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the user data store."""
self.hass = hass
- self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
+ self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
+ private=True)
self._data = None # type: Optional[Dict[str, Any]]
async def async_load(self) -> None:
diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py
index bf1577cbf01..e2701ee37f1 100644
--- a/homeassistant/components/__init__.py
+++ b/homeassistant/components/__init__.py
@@ -59,61 +59,9 @@ def is_on(hass, entity_id=None):
return False
-def turn_on(hass, entity_id=None, **service_data):
- """Turn specified entity on if possible."""
- if entity_id is not None:
- service_data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data)
-
-
-def turn_off(hass, entity_id=None, **service_data):
- """Turn specified entity off."""
- if entity_id is not None:
- service_data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
-
-
-def toggle(hass, entity_id=None, **service_data):
- """Toggle specified entity."""
- if entity_id is not None:
- service_data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
-
-
-def stop(hass):
- """Stop Home Assistant."""
- hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
-
-
-def restart(hass):
- """Stop Home Assistant."""
- hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
-
-
-def check_config(hass):
- """Check the config files."""
- hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
-
-
-def reload_core_config(hass):
- """Reload the core config."""
- hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
-
-
-@asyncio.coroutine
-def async_reload_core_config(hass):
- """Reload the core config."""
- yield from hass.services.async_call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
-
-
-@asyncio.coroutine
-def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
+async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
"""Set up general services related to Home Assistant."""
- @asyncio.coroutine
- def async_handle_turn_service(service):
+ async def async_handle_turn_service(service):
"""Handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service)
@@ -148,7 +96,7 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
tasks.append(hass.services.async_call(
domain, service.service, data, blocking))
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
@@ -164,15 +112,14 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
- @asyncio.coroutine
- def async_handle_core_service(call):
+ async def async_handle_core_service(call):
"""Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP:
hass.async_create_task(hass.async_stop())
return
try:
- errors = yield from conf_util.async_check_ha_config_file(hass)
+ errors = await conf_util.async_check_ha_config_file(hass)
except HomeAssistantError:
return
@@ -193,16 +140,15 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
- @asyncio.coroutine
- def async_handle_reload_config(call):
+ async def async_handle_reload_config(call):
"""Service handler for reloading core config."""
try:
- conf = yield from conf_util.async_hass_config_yaml(hass)
+ conf = await conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err:
_LOGGER.error(err)
return
- yield from conf_util.async_process_ha_core_config(
+ await conf_util.async_process_ha_core_config(
hass, conf.get(ha.DOMAIN) or {})
hass.services.async_register(
diff --git a/homeassistant/components/abode.py b/homeassistant/components/abode.py
index bafbc0781ca..64bedb4ac7c 100644
--- a/homeassistant/components/abode.py
+++ b/homeassistant/components/abode.py
@@ -4,7 +4,6 @@ This component provides basic support for Abode Home Security system.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/abode/
"""
-import asyncio
import logging
from functools import partial
from requests.exceptions import HTTPError, ConnectTimeout
@@ -261,8 +260,7 @@ class AbodeDevice(Entity):
self._data = data
self._device = device
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe Abode events."""
self.hass.async_add_job(
self._data.abode.events.add_device_callback,
@@ -308,8 +306,7 @@ class AbodeAutomation(Entity):
self._automation = automation
self._event = event
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe Abode events."""
if self._event:
self.hass.async_add_job(
diff --git a/homeassistant/components/alarm_control_panel/__init__.py b/homeassistant/components/alarm_control_panel/__init__.py
index 63977ed88c7..a42e6e880b5 100644
--- a/homeassistant/components/alarm_control_panel/__init__.py
+++ b/homeassistant/components/alarm_control_panel/__init__.py
@@ -4,7 +4,6 @@ Component to interface with an alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -14,7 +13,6 @@ from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
-from homeassistant.loader import bind_hass
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
@@ -32,85 +30,12 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
})
-@bind_hass
-def alarm_disarm(hass, code=None, entity_id=None):
- """Send the alarm the command for disarm."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
-
-
-@bind_hass
-def alarm_arm_home(hass, code=None, entity_id=None):
- """Send the alarm the command for arm home."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
-
-
-@bind_hass
-def alarm_arm_away(hass, code=None, entity_id=None):
- """Send the alarm the command for arm away."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
-
-
-@bind_hass
-def alarm_arm_night(hass, code=None, entity_id=None):
- """Send the alarm the command for arm night."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
-
-
-@bind_hass
-def alarm_trigger(hass, code=None, entity_id=None):
- """Send the alarm the command for trigger."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
-
-
-@bind_hass
-def alarm_arm_custom_bypass(hass, code=None, entity_id=None):
- """Send the alarm the command for arm custom bypass."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_ALARM_ARM_CUSTOM_BYPASS, data)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Track states and offer events for sensors."""
component = hass.data[DOMAIN] = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
- yield from component.async_setup(config)
+ await component.async_setup(config)
component.async_register_entity_service(
SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA,
diff --git a/homeassistant/components/alarm_control_panel/alarmdecoder.py b/homeassistant/components/alarm_control_panel/alarmdecoder.py
index 5606209d1e6..25496dff0eb 100644
--- a/homeassistant/components/alarm_control_panel/alarmdecoder.py
+++ b/homeassistant/components/alarm_control_panel/alarmdecoder.py
@@ -4,7 +4,6 @@ Support for AlarmDecoder-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdecoder/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -59,8 +58,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
self._ready = None
self._zone_bypassed = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_PANEL_MESSAGE, self._message_callback)
diff --git a/homeassistant/components/alarm_control_panel/alarmdotcom.py b/homeassistant/components/alarm_control_panel/alarmdotcom.py
index 98766deb3b6..9b07dc41690 100644
--- a/homeassistant/components/alarm_control_panel/alarmdotcom.py
+++ b/homeassistant/components/alarm_control_panel/alarmdotcom.py
@@ -4,7 +4,6 @@ Interfaces with Alarm.com alarm control panels.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
"""
-import asyncio
import logging
import re
@@ -32,9 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up a Alarm.com control panel."""
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
@@ -42,7 +40,7 @@ def async_setup_platform(hass, config, async_add_entities,
password = config.get(CONF_PASSWORD)
alarmdotcom = AlarmDotCom(hass, name, code, username, password)
- yield from alarmdotcom.async_login()
+ await alarmdotcom.async_login()
async_add_entities([alarmdotcom])
@@ -63,15 +61,13 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._alarm = Alarmdotcom(
username, password, self._websession, hass.loop)
- @asyncio.coroutine
- def async_login(self):
+ async def async_login(self):
"""Login to Alarm.com."""
- yield from self._alarm.async_login()
+ await self._alarm.async_login()
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Fetch the latest state."""
- yield from self._alarm.async_update()
+ await self._alarm.async_update()
return self._alarm.state
@property
@@ -106,23 +102,20 @@ class AlarmDotCom(alarm.AlarmControlPanel):
'sensor_status': self._alarm.sensor_status
}
- @asyncio.coroutine
- def async_alarm_disarm(self, code=None):
+ async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if self._validate_code(code):
- yield from self._alarm.async_alarm_disarm()
+ await self._alarm.async_alarm_disarm()
- @asyncio.coroutine
- def async_alarm_arm_home(self, code=None):
+ async def async_alarm_arm_home(self, code=None):
"""Send arm hom command."""
if self._validate_code(code):
- yield from self._alarm.async_alarm_arm_home()
+ await self._alarm.async_alarm_arm_home()
- @asyncio.coroutine
- def async_alarm_arm_away(self, code=None):
+ async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
if self._validate_code(code):
- yield from self._alarm.async_alarm_arm_away()
+ await self._alarm.async_alarm_arm_away()
def _validate_code(self, code):
"""Validate given code."""
diff --git a/homeassistant/components/alarm_control_panel/blink.py b/homeassistant/components/alarm_control_panel/blink.py
new file mode 100644
index 00000000000..850ac52fda4
--- /dev/null
+++ b/homeassistant/components/alarm_control_panel/blink.py
@@ -0,0 +1,86 @@
+"""
+Support for Blink Alarm Control Panel.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/alarm_control_panel.blink/
+"""
+import logging
+
+from homeassistant.components.alarm_control_panel import AlarmControlPanel
+from homeassistant.components.blink import (
+ BLINK_DATA, DEFAULT_ATTRIBUTION)
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY)
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['blink']
+
+ICON = 'mdi:security'
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the Arlo Alarm Control Panels."""
+ if discovery_info is None:
+ return
+ data = hass.data[BLINK_DATA]
+
+ # Current version of blinkpy API only supports one sync module. When
+ # support for additional models is added, the sync module name should
+ # come from the API.
+ sync_modules = []
+ sync_modules.append(BlinkSyncModule(data, 'sync'))
+ add_entities(sync_modules, True)
+
+
+class BlinkSyncModule(AlarmControlPanel):
+ """Representation of a Blink Alarm Control Panel."""
+
+ def __init__(self, data, name):
+ """Initialize the alarm control panel."""
+ self.data = data
+ self.sync = data.sync
+ self._name = name
+ self._state = None
+
+ @property
+ def icon(self):
+ """Return icon."""
+ return ICON
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ return self._state
+
+ @property
+ def name(self):
+ """Return the name of the panel."""
+ return "{} {}".format(BLINK_DATA, self._name)
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ return {
+ ATTR_ATTRIBUTION: DEFAULT_ATTRIBUTION,
+ }
+
+ def update(self):
+ """Update the state of the device."""
+ _LOGGER.debug("Updating Blink Alarm Control Panel %s", self._name)
+ self.data.refresh()
+ mode = self.sync.arm
+ if mode:
+ self._state = STATE_ALARM_ARMED_AWAY
+ else:
+ self._state = STATE_ALARM_DISARMED
+
+ def alarm_disarm(self, code=None):
+ """Send disarm command."""
+ self.sync.arm = False
+ self.sync.refresh()
+
+ def alarm_arm_away(self, code=None):
+ """Send arm command."""
+ self.sync.arm = True
+ self.sync.refresh()
diff --git a/homeassistant/components/alarm_control_panel/egardia.py b/homeassistant/components/alarm_control_panel/egardia.py
index 4e278c10e07..dfd60c4abde 100644
--- a/homeassistant/components/alarm_control_panel/egardia.py
+++ b/homeassistant/components/alarm_control_panel/egardia.py
@@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.egardia/
"""
-import asyncio
import logging
import requests
@@ -61,8 +60,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
self._rs_codes = rs_codes
self._rs_port = rs_port
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Add Egardiaserver callback if enabled."""
if self._rs_enabled:
_LOGGER.debug("Registering callback to Egardiaserver")
diff --git a/homeassistant/components/alarm_control_panel/envisalink.py b/homeassistant/components/alarm_control_panel/envisalink.py
index df91884b32c..f0f3d2a43f7 100644
--- a/homeassistant/components/alarm_control_panel/envisalink.py
+++ b/homeassistant/components/alarm_control_panel/envisalink.py
@@ -4,7 +4,6 @@ Support for Envisalink-based alarm control panels (Honeywell/DSC).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -32,9 +31,8 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Perform the setup for Envisalink alarm panels."""
configured_partitions = discovery_info['partitions']
code = discovery_info[CONF_CODE]
@@ -88,8 +86,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
_LOGGER.debug("Setting up alarm: %s", alarm_name)
super().__init__(alarm_name, info, controller)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
@@ -128,8 +125,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
state = STATE_ALARM_DISARMED
return state
- @asyncio.coroutine
- def async_alarm_disarm(self, code=None):
+ async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if code:
self.hass.data[DATA_EVL].disarm_partition(
@@ -138,8 +134,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
self.hass.data[DATA_EVL].disarm_partition(
str(self._code), self._partition_number)
- @asyncio.coroutine
- def async_alarm_arm_home(self, code=None):
+ async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
if code:
self.hass.data[DATA_EVL].arm_stay_partition(
@@ -148,8 +143,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
self.hass.data[DATA_EVL].arm_stay_partition(
str(self._code), self._partition_number)
- @asyncio.coroutine
- def async_alarm_arm_away(self, code=None):
+ async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
if code:
self.hass.data[DATA_EVL].arm_away_partition(
@@ -158,8 +152,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
self.hass.data[DATA_EVL].arm_away_partition(
str(self._code), self._partition_number)
- @asyncio.coroutine
- def async_alarm_trigger(self, code=None):
+ async def async_alarm_trigger(self, code=None):
"""Alarm trigger command. Will be used to trigger a panic alarm."""
self.hass.data[DATA_EVL].panic_alarm(self._panic_type)
diff --git a/homeassistant/components/alarm_control_panel/manual_mqtt.py b/homeassistant/components/alarm_control_panel/manual_mqtt.py
index 7bf9443424c..834a502baa0 100644
--- a/homeassistant/components/alarm_control_panel/manual_mqtt.py
+++ b/homeassistant/components/alarm_control_panel/manual_mqtt.py
@@ -4,7 +4,6 @@ Support for manual alarms controllable via MQTT.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
"""
-import asyncio
import copy
import datetime
import logging
@@ -363,8 +362,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
return mqtt.async_subscribe(
self.hass, self._command_topic, message_received, self._qos)
- @asyncio.coroutine
- def _async_state_changed_listener(self, entity_id, old_state, new_state):
+ async def _async_state_changed_listener(self, entity_id, old_state,
+ new_state):
"""Publish state change to MQTT."""
mqtt.async_publish(
self.hass, self._state_topic, new_state.state, self._qos, True)
diff --git a/homeassistant/components/alarm_control_panel/mqtt.py b/homeassistant/components/alarm_control_panel/mqtt.py
index e5ad54c4147..ad1c0d1e3b8 100644
--- a/homeassistant/components/alarm_control_panel/mqtt.py
+++ b/homeassistant/components/alarm_control_panel/mqtt.py
@@ -4,7 +4,6 @@ This platform enables the possibility to control a MQTT alarm.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.mqtt/
"""
-import asyncio
import logging
import re
@@ -18,10 +17,13 @@ from homeassistant.const import (
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME, CONF_CODE)
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
- CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
- CONF_RETAIN, MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
+ CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
+ CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -46,13 +48,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up the MQTT Alarm Control Panel platform."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT alarm control panel through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT alarm control panel dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add an MQTT alarm control panel."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(alarm.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities,
+ discovery_hash=None):
+ """Set up the MQTT Alarm Control Panel platform."""
async_add_entities([MqttAlarm(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
@@ -65,18 +82,22 @@ def async_setup_platform(hass, config, async_add_entities,
config.get(CONF_CODE),
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
- config.get(CONF_PAYLOAD_NOT_AVAILABLE))])
+ config.get(CONF_PAYLOAD_NOT_AVAILABLE),
+ discovery_hash,)])
-class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
+class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
+ alarm.AlarmControlPanel):
"""Representation of a MQTT alarm status."""
def __init__(self, name, state_topic, command_topic, qos, retain,
payload_disarm, payload_arm_home, payload_arm_away, code,
- availability_topic, payload_available, payload_not_available):
+ availability_topic, payload_available, payload_not_available,
+ discovery_hash):
"""Init the MQTT Alarm Control Panel."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._state = STATE_UNKNOWN
self._name = name
self._state_topic = state_topic
@@ -87,11 +108,12 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away
self._code = code
+ self._discovery_hash = discovery_hash
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe mqtt events."""
- yield from super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
@callback
def message_received(topic, payload, qos):
@@ -104,7 +126,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self._state = payload
self.async_schedule_update_ha_state()
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@property
@@ -131,8 +153,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
return 'Number'
return 'Any'
- @asyncio.coroutine
- def async_alarm_disarm(self, code=None):
+ async def async_alarm_disarm(self, code=None):
"""Send disarm command.
This method is a coroutine.
@@ -143,8 +164,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self.hass, self._command_topic, self._payload_disarm, self._qos,
self._retain)
- @asyncio.coroutine
- def async_alarm_arm_home(self, code=None):
+ async def async_alarm_arm_home(self, code=None):
"""Send arm home command.
This method is a coroutine.
@@ -155,8 +175,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self.hass, self._command_topic, self._payload_arm_home, self._qos,
self._retain)
- @asyncio.coroutine
- def async_alarm_arm_away(self, code=None):
+ async def async_alarm_arm_away(self, code=None):
"""Send arm away command.
This method is a coroutine.
diff --git a/homeassistant/components/alarm_control_panel/satel_integra.py b/homeassistant/components/alarm_control_panel/satel_integra.py
index 86603763396..c4e42855d8a 100644
--- a/homeassistant/components/alarm_control_panel/satel_integra.py
+++ b/homeassistant/components/alarm_control_panel/satel_integra.py
@@ -4,7 +4,6 @@ Support for Satel Integra alarm, using ETHM module: https://www.satel.pl/en/ .
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.satel_integra/
"""
-import asyncio
import logging
import homeassistant.components.alarm_control_panel as alarm
@@ -18,9 +17,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['satel_integra']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up for Satel Integra alarm panels."""
if not discovery_info:
return
@@ -39,8 +37,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
self._state = None
self._arm_home_mode = arm_home_mode
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)
@@ -74,21 +71,18 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
"""Return the state of the device."""
return self._state
- @asyncio.coroutine
- def async_alarm_disarm(self, code=None):
+ async def async_alarm_disarm(self, code=None):
"""Send disarm command."""
if code:
- yield from self.hass.data[DATA_SATEL].disarm(code)
+ await self.hass.data[DATA_SATEL].disarm(code)
- @asyncio.coroutine
- def async_alarm_arm_away(self, code=None):
+ async def async_alarm_arm_away(self, code=None):
"""Send arm away command."""
if code:
- yield from self.hass.data[DATA_SATEL].arm(code)
+ await self.hass.data[DATA_SATEL].arm(code)
- @asyncio.coroutine
- def async_alarm_arm_home(self, code=None):
+ async def async_alarm_arm_home(self, code=None):
"""Send arm home command."""
if code:
- yield from self.hass.data[DATA_SATEL].arm(
+ await self.hass.data[DATA_SATEL].arm(
code, self._arm_home_mode)
diff --git a/homeassistant/components/alarm_control_panel/simplisafe.py b/homeassistant/components/alarm_control_panel/simplisafe.py
index 2c3b25330d9..34c68f26c2a 100644
--- a/homeassistant/components/alarm_control_panel/simplisafe.py
+++ b/homeassistant/components/alarm_control_panel/simplisafe.py
@@ -12,75 +12,100 @@ import voluptuous as vol
from homeassistant.components.alarm_control_panel import (
PLATFORM_SCHEMA, AlarmControlPanel)
from homeassistant.const import (
- CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
- STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
- STATE_ALARM_DISARMED, STATE_UNKNOWN)
-import homeassistant.helpers.config_validation as cv
+ CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
+ STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
+from homeassistant.helpers import aiohttp_client, config_validation as cv
+from homeassistant.util.json import load_json, save_json
-REQUIREMENTS = ['simplisafe-python==2.0.2']
+REQUIREMENTS = ['simplisafe-python==3.1.2']
_LOGGER = logging.getLogger(__name__)
-DEFAULT_NAME = 'SimpliSafe'
-
ATTR_ALARM_ACTIVE = "alarm_active"
ATTR_TEMPERATURE = "temperature"
+DATA_FILE = '.simplisafe'
+
+DEFAULT_NAME = 'SimpliSafe'
+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): cv.string,
- vol.Optional(CONF_CODE): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
+ vol.Optional(CONF_CODE): cv.string,
})
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the SimpliSafe platform."""
- from simplipy.api import SimpliSafeApiInterface, SimpliSafeAPIException
+ from simplipy import API
+ from simplipy.errors import SimplipyError
+
+ username = config[CONF_USERNAME]
+ password = config[CONF_PASSWORD]
name = config.get(CONF_NAME)
code = config.get(CONF_CODE)
- username = config.get(CONF_USERNAME)
- password = config.get(CONF_PASSWORD)
+
+ websession = aiohttp_client.async_get_clientsession(hass)
+
+ config_data = await hass.async_add_executor_job(
+ load_json, hass.config.path(DATA_FILE))
try:
- simplisafe = SimpliSafeApiInterface(username, password)
- except SimpliSafeAPIException:
- _LOGGER.error("Failed to set up SimpliSafe")
+ if config_data:
+ try:
+ simplisafe = await API.login_via_token(
+ config_data['refresh_token'], websession)
+ _LOGGER.debug('Logging in with refresh token')
+ except SimplipyError:
+ _LOGGER.info('Refresh token expired; attempting credentials')
+ simplisafe = await API.login_via_credentials(
+ username, password, websession)
+ else:
+ simplisafe = await API.login_via_credentials(
+ username, password, websession)
+ _LOGGER.debug('Logging in with credentials')
+ except SimplipyError as err:
+ _LOGGER.error("There was an error during setup: %s", err)
return
- systems = []
+ config_data = {'refresh_token': simplisafe.refresh_token}
+ await hass.async_add_executor_job(
+ save_json, hass.config.path(DATA_FILE), config_data)
- for system in simplisafe.get_systems():
- systems.append(SimpliSafeAlarm(system, name, code))
-
- add_entities(systems)
+ systems = await simplisafe.get_systems()
+ async_add_entities(
+ [SimpliSafeAlarm(system, name, code) for system in systems], True)
class SimpliSafeAlarm(AlarmControlPanel):
"""Representation of a SimpliSafe alarm."""
- def __init__(self, simplisafe, name, code):
+ def __init__(self, system, name, code):
"""Initialize the SimpliSafe alarm."""
- self.simplisafe = simplisafe
- self._name = name
+ self._attrs = {}
self._code = str(code) if code else None
+ self._name = name
+ self._system = system
+ self._state = None
@property
def unique_id(self):
"""Return the unique ID."""
- return self.simplisafe.location_id
+ return self._system.system_id
@property
def name(self):
"""Return the name of the device."""
- if self._name is not None:
+ if self._name:
return self._name
- return 'Alarm {}'.format(self.simplisafe.location_id)
+ return 'Alarm {}'.format(self._system.system_id)
@property
def code_format(self):
"""Return one or more digits/characters."""
- if self._code is None:
+ if not self._code:
return None
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number'
@@ -89,53 +114,12 @@ class SimpliSafeAlarm(AlarmControlPanel):
@property
def state(self):
"""Return the state of the device."""
- status = self.simplisafe.state
- if status.lower() == 'off':
- state = STATE_ALARM_DISARMED
- elif status.lower() == 'home' or status.lower() == 'home_count':
- state = STATE_ALARM_ARMED_HOME
- elif (status.lower() == 'away' or status.lower() == 'exitDelay' or
- status.lower() == 'away_count'):
- state = STATE_ALARM_ARMED_AWAY
- else:
- state = STATE_UNKNOWN
- return state
+ return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
- attributes = {}
-
- attributes[ATTR_ALARM_ACTIVE] = self.simplisafe.alarm_active
- if self.simplisafe.temperature is not None:
- attributes[ATTR_TEMPERATURE] = self.simplisafe.temperature
-
- return attributes
-
- def update(self):
- """Update alarm status."""
- self.simplisafe.update()
-
- def alarm_disarm(self, code=None):
- """Send disarm command."""
- if not self._validate_code(code, 'disarming'):
- return
- self.simplisafe.set_state('off')
- _LOGGER.info("SimpliSafe alarm disarming")
-
- def alarm_arm_home(self, code=None):
- """Send arm home command."""
- if not self._validate_code(code, 'arming home'):
- return
- self.simplisafe.set_state('home')
- _LOGGER.info("SimpliSafe alarm arming home")
-
- def alarm_arm_away(self, code=None):
- """Send arm away command."""
- if not self._validate_code(code, 'arming away'):
- return
- self.simplisafe.set_state('away')
- _LOGGER.info("SimpliSafe alarm arming away")
+ return self._attrs
def _validate_code(self, code, state):
"""Validate given code."""
@@ -143,3 +127,46 @@ class SimpliSafeAlarm(AlarmControlPanel):
if not check:
_LOGGER.warning("Wrong code entered for %s", state)
return check
+
+ async def async_alarm_disarm(self, code=None):
+ """Send disarm command."""
+ if not self._validate_code(code, 'disarming'):
+ return
+
+ await self._system.set_off()
+
+ async def async_alarm_arm_home(self, code=None):
+ """Send arm home command."""
+ if not self._validate_code(code, 'arming home'):
+ return
+
+ await self._system.set_home()
+
+ async def async_alarm_arm_away(self, code=None):
+ """Send arm away command."""
+ if not self._validate_code(code, 'arming away'):
+ return
+
+ await self._system.set_away()
+
+ async def async_update(self):
+ """Update alarm status."""
+ await self._system.update()
+
+ if self._system.state == self._system.SystemStates.off:
+ self._state = STATE_ALARM_DISARMED
+ elif self._system.state in (
+ self._system.SystemStates.home,
+ self._system.SystemStates.home_count):
+ self._state = STATE_ALARM_ARMED_HOME
+ elif self._system.state in (
+ self._system.SystemStates.away,
+ self._system.SystemStates.away_count,
+ self._system.SystemStates.exit_delay):
+ self._state = STATE_ALARM_ARMED_AWAY
+ else:
+ self._state = None
+
+ self._attrs[ATTR_ALARM_ACTIVE] = self._system.alarm_going_off
+ if self._system.temperature:
+ self._attrs[ATTR_TEMPERATURE] = self._system.temperature
diff --git a/homeassistant/components/alarm_control_panel/spc.py b/homeassistant/components/alarm_control_panel/spc.py
index 9150518022f..b4c49d4d190 100644
--- a/homeassistant/components/alarm_control_panel/spc.py
+++ b/homeassistant/components/alarm_control_panel/spc.py
@@ -9,8 +9,7 @@ import logging
import homeassistant.components.alarm_control_panel as alarm
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback
-from homeassistant.components.spc import (
- ATTR_DISCOVER_AREAS, DATA_API, SIGNAL_UPDATE_ALARM)
+from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_ALARM)
from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
@@ -37,12 +36,11 @@ def _get_alarm_state(area):
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the SPC alarm control panel platform."""
- if (discovery_info is None or
- discovery_info[ATTR_DISCOVER_AREAS] is None):
+ if discovery_info is None:
return
-
- async_add_entities([SpcAlarm(area=area, api=hass.data[DATA_API])
- for area in discovery_info[ATTR_DISCOVER_AREAS]])
+ api = hass.data[DATA_API]
+ async_add_entities([SpcAlarm(area=area, api=api)
+ for area in api.areas.values()])
class SpcAlarm(alarm.AlarmControlPanel):
diff --git a/homeassistant/components/alarm_control_panel/wink.py b/homeassistant/components/alarm_control_panel/wink.py
index d75fad30c96..001c6fad85c 100644
--- a/homeassistant/components/alarm_control_panel/wink.py
+++ b/homeassistant/components/alarm_control_panel/wink.py
@@ -4,7 +4,6 @@ Interfaces with Wink Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.wink/
"""
-import asyncio
import logging
import homeassistant.components.alarm_control_panel as alarm
@@ -38,8 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
"""Representation a Wink camera alarm."""
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
diff --git a/homeassistant/components/alert.py b/homeassistant/components/alert.py
index 3ec01fc6ab8..e224351f9db 100644
--- a/homeassistant/components/alert.py
+++ b/homeassistant/components/alert.py
@@ -10,7 +10,8 @@ import logging
import voluptuous as vol
-from homeassistant.core import callback
+from homeassistant.components.notify import (
+ ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY)
from homeassistant.const import (
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
@@ -59,53 +60,12 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
-def turn_on(hass, entity_id):
- """Reset the alert."""
- hass.add_job(async_turn_on, hass, entity_id)
-
-
-@callback
-def async_turn_on(hass, entity_id):
- """Async reset the alert."""
- data = {ATTR_ENTITY_ID: entity_id}
- hass.async_create_task(
- hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
-
-
-def turn_off(hass, entity_id):
- """Acknowledge alert."""
- hass.add_job(async_turn_off, hass, entity_id)
-
-
-@callback
-def async_turn_off(hass, entity_id):
- """Async acknowledge the alert."""
- data = {ATTR_ENTITY_ID: entity_id}
- hass.async_create_task(
- hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
-
-
-def toggle(hass, entity_id):
- """Toggle acknowledgement of alert."""
- hass.add_job(async_toggle, hass, entity_id)
-
-
-@callback
-def async_toggle(hass, entity_id):
- """Async toggle acknowledgement of alert."""
- data = {ATTR_ENTITY_ID: entity_id}
- hass.async_create_task(
- hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Alert component."""
alerts = config.get(DOMAIN)
all_alerts = {}
- @asyncio.coroutine
- def async_handle_alert_service(service_call):
+ async def async_handle_alert_service(service_call):
"""Handle calls to alert services."""
alert_ids = service.extract_entity_ids(hass, service_call)
@@ -113,11 +73,11 @@ def async_setup(hass, config):
alert = all_alerts[alert_id]
alert.async_set_context(service_call.context)
if service_call.service == SERVICE_TURN_ON:
- yield from alert.async_turn_on()
+ await alert.async_turn_on()
elif service_call.service == SERVICE_TOGGLE:
- yield from alert.async_toggle()
+ await alert.async_toggle()
else:
- yield from alert.async_turn_off()
+ await alert.async_turn_off()
# Setup alerts
for entity_id, alert in alerts.items():
@@ -141,7 +101,7 @@ def async_setup(hass, config):
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
return True
@@ -196,17 +156,15 @@ class Alert(ToggleEntity):
"""Hide the alert when it is not firing."""
return not self._can_ack or not self._firing
- @asyncio.coroutine
- def watched_entity_change(self, entity, from_state, to_state):
+ async def watched_entity_change(self, entity, from_state, to_state):
"""Determine if the alert should start or stop."""
_LOGGER.debug("Watched entity (%s) has changed", entity)
if to_state.state == self._alert_state and not self._firing:
- yield from self.begin_alerting()
+ await self.begin_alerting()
if to_state.state != self._alert_state and self._firing:
- yield from self.end_alerting()
+ await self.end_alerting()
- @asyncio.coroutine
- def begin_alerting(self):
+ async def begin_alerting(self):
"""Begin the alert procedures."""
_LOGGER.debug("Beginning Alert: %s", self._name)
self._ack = False
@@ -214,25 +172,23 @@ class Alert(ToggleEntity):
self._next_delay = 0
if not self._skip_first:
- yield from self._notify()
+ await self._notify()
else:
- yield from self._schedule_notify()
+ await self._schedule_notify()
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def end_alerting(self):
+ async def end_alerting(self):
"""End the alert procedures."""
_LOGGER.debug("Ending Alert: %s", self._name)
self._cancel()
self._ack = False
self._firing = False
if self._done_message and self._send_done_message:
- yield from self._notify_done_message()
+ await self._notify_done_message()
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def _schedule_notify(self):
+ async def _schedule_notify(self):
"""Schedule a notification."""
delay = self._delay[self._next_delay]
next_msg = datetime.now() + delay
@@ -240,8 +196,7 @@ class Alert(ToggleEntity):
event.async_track_point_in_time(self.hass, self._notify, next_msg)
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
- @asyncio.coroutine
- def _notify(self, *args):
+ async def _notify(self, *args):
"""Send the alert notification."""
if not self._firing:
return
@@ -250,36 +205,32 @@ class Alert(ToggleEntity):
_LOGGER.info("Alerting: %s", self._name)
self._send_done_message = True
for target in self._notifiers:
- yield from self.hass.services.async_call(
- 'notify', target, {'message': self._name})
- yield from self._schedule_notify()
+ await self.hass.services.async_call(
+ DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name})
+ await self._schedule_notify()
- @asyncio.coroutine
- def _notify_done_message(self, *args):
+ async def _notify_done_message(self, *args):
"""Send notification of complete alert."""
_LOGGER.info("Alerting: %s", self._done_message)
self._send_done_message = False
for target in self._notifiers:
- yield from self.hass.services.async_call(
- 'notify', target, {'message': self._done_message})
+ await self.hass.services.async_call(
+ DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message})
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Async Unacknowledge alert."""
_LOGGER.debug("Reset Alert: %s", self._name)
self._ack = False
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Async Acknowledge alert."""
_LOGGER.debug("Acknowledged Alert: %s", self._name)
self._ack = True
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_toggle(self, **kwargs):
+ async def async_toggle(self, **kwargs):
"""Async toggle alert."""
if self._ack:
- return self.async_turn_on()
- return self.async_turn_off()
+ return await self.async_turn_on()
+ return await self.async_turn_off()
diff --git a/homeassistant/components/alexa/__init__.py b/homeassistant/components/alexa/__init__.py
index d120270650f..337d8993b28 100644
--- a/homeassistant/components/alexa/__init__.py
+++ b/homeassistant/components/alexa/__init__.py
@@ -4,7 +4,6 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -53,8 +52,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Activate Alexa component."""
config = config.get(DOMAIN, {})
flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS)
diff --git a/homeassistant/components/alexa/intent.py b/homeassistant/components/alexa/intent.py
index 8d4520d74e8..85cb4f105cd 100644
--- a/homeassistant/components/alexa/intent.py
+++ b/homeassistant/components/alexa/intent.py
@@ -4,7 +4,6 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/
"""
-import asyncio
import enum
import logging
@@ -59,16 +58,15 @@ class AlexaIntentsView(http.HomeAssistantView):
url = INTENTS_API_ENDPOINT
name = 'api:alexa'
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Handle Alexa."""
hass = request.app['hass']
- message = yield from request.json()
+ message = await request.json()
_LOGGER.debug("Received Alexa request: %s", message)
try:
- response = yield from async_handle_message(hass, message)
+ response = await async_handle_message(hass, message)
return b'' if response is None else self.json(response)
except UnknownRequest as err:
_LOGGER.warning(str(err))
@@ -101,8 +99,7 @@ def intent_error_response(hass, message, error):
return alexa_response.as_dict()
-@asyncio.coroutine
-def async_handle_message(hass, message):
+async def async_handle_message(hass, message):
"""Handle an Alexa intent.
Raises:
@@ -120,20 +117,18 @@ def async_handle_message(hass, message):
if not handler:
raise UnknownRequest('Received unknown request {}'.format(req_type))
- return (yield from handler(hass, message))
+ return await handler(hass, message)
@HANDLERS.register('SessionEndedRequest')
-@asyncio.coroutine
-def async_handle_session_end(hass, message):
+async def async_handle_session_end(hass, message):
"""Handle a session end request."""
return None
@HANDLERS.register('IntentRequest')
@HANDLERS.register('LaunchRequest')
-@asyncio.coroutine
-def async_handle_intent(hass, message):
+async def async_handle_intent(hass, message):
"""Handle an intent request.
Raises:
@@ -153,7 +148,7 @@ def async_handle_intent(hass, message):
else:
intent_name = alexa_intent_info['name']
- intent_response = yield from intent.async_handle(
+ intent_response = await intent.async_handle(
hass, DOMAIN, intent_name,
{key: {'value': value} for key, value
in alexa_response.variables.items()})
diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py
index 176c286ebc3..f88d81ab851 100644
--- a/homeassistant/components/alexa/smart_home.py
+++ b/homeassistant/components/alexa/smart_home.py
@@ -1,5 +1,4 @@
"""Support for alexa Smart Home Skill API."""
-import asyncio
import logging
import math
from datetime import datetime
@@ -695,8 +694,7 @@ class SmartHomeView(http.HomeAssistantView):
"""Initialize."""
self.smart_home_config = smart_home_config
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Handle Alexa Smart Home requests.
The Smart Home API requires the endpoint to be implemented in AWS
@@ -704,11 +702,11 @@ class SmartHomeView(http.HomeAssistantView):
the response.
"""
hass = request.app['hass']
- message = yield from request.json()
+ message = await request.json()
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
- response = yield from async_handle_message(
+ response = await async_handle_message(
hass, self.smart_home_config, message)
_LOGGER.debug("Sending Alexa Smart Home response: %s", response)
return b'' if response is None else self.json(response)
diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam.py
index 5da117e74c3..b8a2d461489 100644
--- a/homeassistant/components/android_ip_webcam.py
+++ b/homeassistant/components/android_ip_webcam.py
@@ -149,16 +149,14 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the IP Webcam component."""
from pydroid_ipcam import PyDroidIPCam
webcams = hass.data[DATA_IP_WEBCAM] = {}
websession = async_get_clientsession(hass)
- @asyncio.coroutine
- def async_setup_ipcamera(cam_config):
+ async def async_setup_ipcamera(cam_config):
"""Set up an IP camera."""
host = cam_config[CONF_HOST]
username = cam_config.get(CONF_USERNAME)
@@ -188,16 +186,15 @@ def async_setup(hass, config):
if motion is None:
motion = 'motion_active' in cam.enabled_sensors
- @asyncio.coroutine
- def async_update_data(now):
+ async def async_update_data(now):
"""Update data from IP camera in SCAN_INTERVAL."""
- yield from cam.update()
+ await cam.update()
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
async_track_point_in_utc_time(
hass, async_update_data, utcnow() + interval)
- yield from async_update_data(None)
+ await async_update_data(None)
# Load platforms
webcams[host] = cam
@@ -242,7 +239,7 @@ def async_setup(hass, config):
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
return True
@@ -255,8 +252,7 @@ class AndroidIPCamEntity(Entity):
self._host = host
self._ipcam = ipcam
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register update dispatcher."""
@callback
def async_ipcam_update(host):
diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py
index 21ff0e3286d..012e71a08a7 100644
--- a/homeassistant/components/apple_tv.py
+++ b/homeassistant/components/apple_tv.py
@@ -77,14 +77,13 @@ def request_configuration(hass, config, atv, credentials):
"""Request configuration steps from the user."""
configurator = hass.components.configurator
- @asyncio.coroutine
- def configuration_callback(callback_data):
+ async def configuration_callback(callback_data):
"""Handle the submitted configuration."""
from pyatv import exceptions
pin = callback_data.get('pin')
try:
- yield from atv.airplay.finish_authentication(pin)
+ await atv.airplay.finish_authentication(pin)
hass.components.persistent_notification.async_create(
'Authentication succeeded!
Add the following '
'to credentials: in your apple_tv configuration:
'
@@ -108,11 +107,10 @@ def request_configuration(hass, config, atv, credentials):
)
-@asyncio.coroutine
-def scan_for_apple_tvs(hass):
+async def scan_for_apple_tvs(hass):
"""Scan for devices and present a notification of the ones found."""
import pyatv
- atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
+ atvs = await pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
devices = []
for atv in atvs:
@@ -132,14 +130,12 @@ def scan_for_apple_tvs(hass):
notification_id=NOTIFICATION_SCAN_ID)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Apple TV component."""
if DATA_APPLE_TV not in hass.data:
hass.data[DATA_APPLE_TV] = {}
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Handle service calls."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
@@ -158,17 +154,16 @@ def async_setup(hass, config):
continue
atv = device.atv
- credentials = yield from atv.airplay.generate_credentials()
- yield from atv.airplay.load_credentials(credentials)
+ credentials = await atv.airplay.generate_credentials()
+ await atv.airplay.load_credentials(credentials)
_LOGGER.debug('Generated new credentials: %s', credentials)
- yield from atv.airplay.start_authentication()
+ await atv.airplay.start_authentication()
hass.async_add_job(request_configuration,
hass, config, atv, credentials)
- @asyncio.coroutine
- def atv_discovered(service, info):
+ async def atv_discovered(service, info):
"""Set up an Apple TV that was auto discovered."""
- yield from _setup_atv(hass, {
+ await _setup_atv(hass, {
CONF_NAME: info['name'],
CONF_HOST: info['host'],
CONF_LOGIN_ID: info['properties']['hG'],
@@ -179,7 +174,7 @@ def async_setup(hass, config):
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_SCAN, async_service_handler,
@@ -192,8 +187,7 @@ def async_setup(hass, config):
return True
-@asyncio.coroutine
-def _setup_atv(hass, atv_config):
+async def _setup_atv(hass, atv_config):
"""Set up an Apple TV."""
import pyatv
name = atv_config.get(CONF_NAME)
@@ -209,7 +203,7 @@ def _setup_atv(hass, atv_config):
session = async_get_clientsession(hass)
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
if credentials:
- yield from atv.airplay.load_credentials(credentials)
+ await atv.airplay.load_credentials(credentials)
power = AppleTVPowerManager(hass, atv, start_off)
hass.data[DATA_APPLE_TV][host] = {
@@ -258,4 +252,4 @@ class AppleTVPowerManager:
self.atv.push_updater.start()
for listener in self.listeners:
- self.hass.async_add_job(listener.async_update_ha_state())
+ self.hass.async_create_task(listener.async_update_ha_state())
diff --git a/homeassistant/components/aqualogic.py b/homeassistant/components/aqualogic.py
new file mode 100644
index 00000000000..abb61d42ca3
--- /dev/null
+++ b/homeassistant/components/aqualogic.py
@@ -0,0 +1,95 @@
+"""
+Support for AquaLogic component.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/aqualogic/
+"""
+from datetime import timedelta
+import logging
+import time
+import threading
+
+import voluptuous as vol
+
+from homeassistant.const import (CONF_HOST, CONF_PORT,
+ EVENT_HOMEASSISTANT_START,
+ EVENT_HOMEASSISTANT_STOP)
+from homeassistant.helpers import config_validation as cv
+
+REQUIREMENTS = ["aqualogic==1.0"]
+
+_LOGGER = logging.getLogger(__name__)
+
+DOMAIN = "aqualogic"
+UPDATE_TOPIC = DOMAIN + "_update"
+CONF_UNIT = "unit"
+RECONNECT_INTERVAL = timedelta(seconds=10)
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_HOST): cv.string,
+ vol.Required(CONF_PORT): cv.port
+ }),
+}, extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, config):
+ """Set up AquaLogic platform."""
+ host = config[DOMAIN][CONF_HOST]
+ port = config[DOMAIN][CONF_PORT]
+ processor = AquaLogicProcessor(hass, host, port)
+ hass.data[DOMAIN] = processor
+ hass.bus.listen_once(EVENT_HOMEASSISTANT_START,
+ processor.start_listen)
+ hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
+ processor.shutdown)
+ _LOGGER.debug("AquaLogicProcessor %s:%i initialized", host, port)
+ return True
+
+
+class AquaLogicProcessor(threading.Thread):
+ """AquaLogic event processor thread."""
+
+ def __init__(self, hass, host, port):
+ """Initialize the data object."""
+ super().__init__(daemon=True)
+ self._hass = hass
+ self._host = host
+ self._port = port
+ self._shutdown = False
+ self._panel = None
+
+ def start_listen(self, event):
+ """Start event-processing thread."""
+ _LOGGER.debug("Event processing thread started")
+ self.start()
+
+ def shutdown(self, event):
+ """Signal shutdown of processing event."""
+ _LOGGER.debug("Event processing signaled exit")
+ self._shutdown = True
+
+ def data_changed(self, panel):
+ """Aqualogic data changed callback."""
+ self._hass.helpers.dispatcher.dispatcher_send(UPDATE_TOPIC)
+
+ def run(self):
+ """Event thread."""
+ from aqualogic.core import AquaLogic
+
+ while True:
+ self._panel = AquaLogic()
+ self._panel.connect(self._host, self._port)
+ self._panel.process(self.data_changed)
+
+ if self._shutdown:
+ return
+
+ _LOGGER.error("Connection to %s:%d lost",
+ self._host, self._port)
+ time.sleep(RECONNECT_INTERVAL.seconds)
+
+ @property
+ def panel(self):
+ """Retrieve the AquaLogic object."""
+ return self._panel
diff --git a/homeassistant/components/auth/.translations/de.json b/homeassistant/components/auth/.translations/de.json
index 21c83290629..4ef4a5bf9e8 100644
--- a/homeassistant/components/auth/.translations/de.json
+++ b/homeassistant/components/auth/.translations/de.json
@@ -16,7 +16,8 @@
"description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:",
"title": "\u00dcberpr\u00fcfe das Setup"
}
- }
+ },
+ "title": "Benachrichtig f\u00fcr One-Time Password"
},
"totp": {
"error": {
diff --git a/homeassistant/components/auth/.translations/fr.json b/homeassistant/components/auth/.translations/fr.json
index 85540314af0..cf0a1888495 100644
--- a/homeassistant/components/auth/.translations/fr.json
+++ b/homeassistant/components/auth/.translations/fr.json
@@ -1,11 +1,23 @@
{
"mfa_setup": {
"notify": {
+ "abort": {
+ "no_available_service": "Aucun service de notification disponible."
+ },
+ "error": {
+ "invalid_code": "Code invalide. Veuillez essayer \u00e0 nouveau."
+ },
"step": {
+ "init": {
+ "description": "Veuillez s\u00e9lectionner l'un des services de notification:",
+ "title": "Configurer un mot de passe \u00e0 usage unique d\u00e9livr\u00e9 par le composant notify"
+ },
"setup": {
- "description": "Un mot de passe unique a \u00e9t\u00e9 envoy\u00e9 par **notify.{notify_service}**. Veuillez le saisir ci-dessous :"
+ "description": "Un mot de passe unique a \u00e9t\u00e9 envoy\u00e9 par **notify.{notify_service}**. Veuillez le saisir ci-dessous :",
+ "title": "V\u00e9rifier la configuration"
}
- }
+ },
+ "title": "Notifier un mot de passe unique"
},
"totp": {
"error": {
diff --git a/homeassistant/components/auth/.translations/hu.json b/homeassistant/components/auth/.translations/hu.json
index 4500098553e..0a3a3c58820 100644
--- a/homeassistant/components/auth/.translations/hu.json
+++ b/homeassistant/components/auth/.translations/hu.json
@@ -1,5 +1,21 @@
{
"mfa_setup": {
+ "notify": {
+ "abort": {
+ "no_available_service": "Nincs el\u00e9rhet\u0151 \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1s."
+ },
+ "error": {
+ "invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra."
+ },
+ "step": {
+ "init": {
+ "description": "V\u00e1lassz \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1st:"
+ },
+ "setup": {
+ "title": "Be\u00e1ll\u00edt\u00e1s ellen\u0151rz\u00e9se"
+ }
+ }
+ },
"totp": {
"error": {
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra. Ha ez a hiba folyamatosan el\u0151fordul, akkor gy\u0151z\u0151dj meg r\u00f3la, hogy a Home Assistant rendszered \u00f3r\u00e1ja pontosan j\u00e1r."
diff --git a/homeassistant/components/auth/.translations/ko.json b/homeassistant/components/auth/.translations/ko.json
index e1f26e88bc7..7efc50d534c 100644
--- a/homeassistant/components/auth/.translations/ko.json
+++ b/homeassistant/components/auth/.translations/ko.json
@@ -26,7 +26,7 @@
"step": {
"init": {
"description": "\uc2dc\uac04 \uae30\ubc18\uc758 \uc77c\ud68c\uc6a9 \ube44\ubc00\ubc88\ud638\ub97c \uc0ac\uc6a9\ud558\ub294 2\ub2e8\uacc4 \uc778\uc99d\uc744 \ud558\ub824\uba74 \uc778\uc99d\uc6a9 \uc571\uc744 \uc774\uc6a9\ud574\uc11c QR \ucf54\ub4dc\ub97c \uc2a4\uce94\ud574 \uc8fc\uc138\uc694. \uc778\uc99d\uc6a9 \uc571\uc740 [Google OTP](https://support.google.com/accounts/answer/1066447) \ub610\ub294 [Authy](https://authy.com/) \ub97c \ucd94\ucc9c\ub4dc\ub9bd\ub2c8\ub2e4.\n\n{qr_code}\n\n\uc2a4\uce94 \ud6c4\uc5d0 \uc0dd\uc131\ub41c 6\uc790\ub9ac \ucf54\ub4dc\ub97c \uc785\ub825\ud574\uc11c \uc124\uc815\uc744 \ud655\uc778\ud558\uc138\uc694. QR \ucf54\ub4dc \uc2a4\uce94\uc5d0 \ubb38\uc81c\uac00 \uc788\ub2e4\uba74, **`{code}`** \ucf54\ub4dc\ub85c \uc9c1\uc811 \uc124\uc815\ud574\ubcf4\uc138\uc694.",
- "title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2 \ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
+ "title": "TOTP \ub97c \uc0ac\uc6a9\ud558\uc5ec 2\ub2e8\uacc4 \uc778\uc99d \uad6c\uc131"
}
},
"title": "TOTP (\uc2dc\uac04 \uae30\ubc18 OTP)"
diff --git a/homeassistant/components/auth/.translations/nl.json b/homeassistant/components/auth/.translations/nl.json
index 40a873023dd..9ec8006507b 100644
--- a/homeassistant/components/auth/.translations/nl.json
+++ b/homeassistant/components/auth/.translations/nl.json
@@ -1,5 +1,24 @@
{
"mfa_setup": {
+ "notify": {
+ "abort": {
+ "no_available_service": "Geen meldingsservices beschikbaar."
+ },
+ "error": {
+ "invalid_code": "Ongeldige code, probeer opnieuw."
+ },
+ "step": {
+ "init": {
+ "description": "Selecteer een van de meldingsdiensten:",
+ "title": "Stel een \u00e9\u00e9nmalig wachtwoord in dat wordt afgegeven door een meldingscomponent"
+ },
+ "setup": {
+ "description": "Een \u00e9\u00e9nmalig wachtwoord is verzonden via **notify. {notify_service}**. Voer het hieronder in:",
+ "title": "Controleer de instellingen"
+ }
+ },
+ "title": "Eenmalig wachtwoord melden"
+ },
"totp": {
"error": {
"invalid_code": "Ongeldige code, probeer het opnieuw. Als u deze fout blijft krijgen, controleer dan of de klok van uw Home Assistant systeem correct is ingesteld."
diff --git a/homeassistant/components/auth/.translations/pl.json b/homeassistant/components/auth/.translations/pl.json
index 3e320ba8d62..6adaaa019c5 100644
--- a/homeassistant/components/auth/.translations/pl.json
+++ b/homeassistant/components/auth/.translations/pl.json
@@ -13,10 +13,11 @@
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
},
"setup": {
- "description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez ** powiadom. {notify_service} **. Wpisz je poni\u017cej:",
+ "description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wpisz je poni\u017cej:",
"title": "Sprawd\u017a konfiguracj\u0119"
}
- }
+ },
+ "title": "Powiadomienie z has\u0142em jednorazowym"
},
"totp": {
"error": {
diff --git a/homeassistant/components/auth/.translations/pt.json b/homeassistant/components/auth/.translations/pt.json
index 474dbe488be..5401c0117e6 100644
--- a/homeassistant/components/auth/.translations/pt.json
+++ b/homeassistant/components/auth/.translations/pt.json
@@ -1,5 +1,24 @@
{
"mfa_setup": {
+ "notify": {
+ "abort": {
+ "no_available_service": "Nenhum servi\u00e7o de notifica\u00e7\u00e3o dispon\u00edvel."
+ },
+ "error": {
+ "invalid_code": "C\u00f3digo inv\u00e1lido, por favor tente novamente."
+ },
+ "step": {
+ "init": {
+ "description": "Por favor, selecione um dos servi\u00e7os de notifica\u00e7\u00e3o:",
+ "title": "Configurar uma palavra passe entregue pela componente de notifica\u00e7\u00e3o"
+ },
+ "setup": {
+ "description": "Foi enviada uma palavra passe atrav\u00e9s de **notify.{notify_service}**. Por favor, insira-a:",
+ "title": "Verificar a configura\u00e7\u00e3o"
+ }
+ },
+ "title": "Notificar palavra passe de uso \u00fanico"
+ },
"totp": {
"error": {
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistent \u00e9 preciso."
diff --git a/homeassistant/components/auth/.translations/sv.json b/homeassistant/components/auth/.translations/sv.json
index 604ae3c4fe5..9246a88c512 100644
--- a/homeassistant/components/auth/.translations/sv.json
+++ b/homeassistant/components/auth/.translations/sv.json
@@ -8,11 +8,16 @@
"invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen."
},
"step": {
+ "init": {
+ "description": "Var god v\u00e4lj en av notifieringstj\u00e4nsterna:",
+ "title": "Konfigurera ett eng\u00e5ngsl\u00f6senord levererat genom notifieringskomponenten"
+ },
"setup": {
"description": "Ett eng\u00e5ngsl\u00f6senord har skickats av **notify.{notify_service}**. V\u00e4nligen ange det nedan:",
- "title": "Verifiera installationen"
+ "title": "Verifiera inst\u00e4llningen"
}
- }
+ },
+ "title": "Meddela eng\u00e5ngsl\u00f6senord"
},
"totp": {
"error": {
diff --git a/homeassistant/components/auth/.translations/zh-Hant.json b/homeassistant/components/auth/.translations/zh-Hant.json
index e791f20a738..b7a26f5079c 100644
--- a/homeassistant/components/auth/.translations/zh-Hant.json
+++ b/homeassistant/components/auth/.translations/zh-Hant.json
@@ -25,7 +25,7 @@
},
"step": {
"init": {
- "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u6383\u63cf\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u6383\u63cf\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
+ "description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
"title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u5169\u6b65\u9a5f\u9a57\u8b49"
}
},
diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py
index bee72d8e4fc..58be53d4122 100644
--- a/homeassistant/components/auth/__init__.py
+++ b/homeassistant/components/auth/__init__.py
@@ -105,7 +105,6 @@ Home Assistant. User need to record the token in secure place.
"id": 11,
"type": "auth/long_lived_access_token",
"client_name": "GPS Logger",
- "client_icon": null,
"lifespan": 365
}
@@ -433,7 +432,7 @@ def websocket_current_user(
"""Get current user."""
enabled_modules = await hass.auth.async_get_enabled_mfa(user)
- connection.send_message_outside(
+ connection.send_message(
websocket_api.result_message(msg['id'], {
'id': user.id,
'name': user.name,
@@ -468,7 +467,7 @@ def websocket_create_long_lived_access_token(
access_token = hass.auth.async_create_access_token(
refresh_token)
- connection.send_message_outside(
+ connection.send_message(
websocket_api.result_message(msg['id'], access_token))
hass.async_create_task(
@@ -480,8 +479,8 @@ def websocket_create_long_lived_access_token(
def websocket_refresh_tokens(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
"""Return metadata of users refresh tokens."""
- current_id = connection.request.get('refresh_token_id')
- connection.to_write.put_nowait(websocket_api.result_message(msg['id'], [{
+ current_id = connection.refresh_token_id
+ connection.send_message(websocket_api.result_message(msg['id'], [{
'id': refresh.id,
'client_id': refresh.client_id,
'client_name': refresh.client_name,
@@ -509,7 +508,7 @@ def websocket_delete_refresh_token(
await hass.auth.async_remove_refresh_token(refresh_token)
- connection.send_message_outside(
+ connection.send_message(
websocket_api.result_message(msg['id'], {}))
hass.async_create_task(
diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py
index bcf73258ffa..30432a612a4 100644
--- a/homeassistant/components/auth/indieauth.py
+++ b/homeassistant/components/auth/indieauth.py
@@ -1,24 +1,13 @@
"""Helpers to resolve client ID/secret."""
import asyncio
+from ipaddress import ip_address
from html.parser import HTMLParser
-from ipaddress import ip_address, ip_network
from urllib.parse import urlparse, urljoin
import aiohttp
from aiohttp.client_exceptions import ClientError
-# IP addresses of loopback interfaces
-ALLOWED_IPS = (
- ip_address('127.0.0.1'),
- ip_address('::1'),
-)
-
-# RFC1918 - Address allocation for Private Internets
-ALLOWED_NETWORKS = (
- ip_network('10.0.0.0/8'),
- ip_network('172.16.0.0/12'),
- ip_network('192.168.0.0/16'),
-)
+from homeassistant.util.network import is_local
async def verify_redirect_uri(hass, client_id, redirect_uri):
@@ -185,9 +174,7 @@ def _parse_client_id(client_id):
# Not an ip address
pass
- if (address is None or
- address in ALLOWED_IPS or
- any(address in network for network in ALLOWED_NETWORKS)):
+ if address is None or is_local(address):
return parts
raise ValueError('Hostname should be a domain name or local IP address')
diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py
index 82eb913d890..121d95aede3 100644
--- a/homeassistant/components/auth/mfa_setup_flow.py
+++ b/homeassistant/components/auth/mfa_setup_flow.py
@@ -64,7 +64,7 @@ def websocket_setup_mfa(
if flow_id is not None:
result = await flow_manager.async_configure(
flow_id, msg.get('user_input'))
- connection.send_message_outside(
+ connection.send_message(
websocket_api.result_message(
msg['id'], _prepare_result_json(result)))
return
@@ -72,7 +72,7 @@ def websocket_setup_mfa(
mfa_module_id = msg.get('mfa_module_id')
mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id)
if mfa_module is None:
- connection.send_message_outside(websocket_api.error_message(
+ connection.send_message(websocket_api.error_message(
msg['id'], 'no_module',
'MFA module {} is not found'.format(mfa_module_id)))
return
@@ -80,7 +80,7 @@ def websocket_setup_mfa(
result = await flow_manager.async_init(
mfa_module_id, data={'user_id': connection.user.id})
- connection.send_message_outside(
+ connection.send_message(
websocket_api.result_message(
msg['id'], _prepare_result_json(result)))
@@ -99,13 +99,13 @@ def websocket_depose_mfa(
await hass.auth.async_disable_user_mfa(
connection.user, msg['mfa_module_id'])
except ValueError as err:
- connection.send_message_outside(websocket_api.error_message(
+ connection.send_message(websocket_api.error_message(
msg['id'], 'disable_failed',
'Cannot disable MFA Module {}: {}'.format(
mfa_module_id, err)))
return
- connection.send_message_outside(
+ connection.send_message(
websocket_api.result_message(
msg['id'], 'done'))
diff --git a/homeassistant/components/auth/strings.json b/homeassistant/components/auth/strings.json
index 2b1fc0c94f6..57f5ed659b0 100644
--- a/homeassistant/components/auth/strings.json
+++ b/homeassistant/components/auth/strings.json
@@ -17,15 +17,15 @@
"step": {
"init": {
"title": "Set up one-time password delivered by notify component",
- "description": "Please select one of notify service:"
+ "description": "Please select one of the notification services:"
},
"setup": {
"title": "Verify setup",
- "description": "A one-time password have sent by **notify.{notify_service}**. Please input it in below:"
+ "description": "A one-time password has been sent via **notify.{notify_service}**. Please enter it below:"
}
},
"abort": {
- "no_available_service": "No available notify services."
+ "no_available_service": "No notification services available."
},
"error": {
"invalid_code": "Invalid code, please try again."
diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py
index 43fd4cedb88..a1f1563f5e1 100644
--- a/homeassistant/components/automation/__init__.py
+++ b/homeassistant/components/automation/__init__.py
@@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
-from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity
@@ -115,49 +114,6 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, entity_id=None):
- """Turn on specified automation or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
-
-
-@bind_hass
-def turn_off(hass, entity_id=None):
- """Turn off specified automation or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
-
-
-@bind_hass
-def toggle(hass, entity_id=None):
- """Toggle specified automation or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
-
-
-@bind_hass
-def trigger(hass, entity_id=None):
- """Trigger specified automation or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
-
-
-@bind_hass
-def reload(hass):
- """Reload the automation from config."""
- hass.services.call(DOMAIN, SERVICE_RELOAD)
-
-
-@bind_hass
-def async_reload(hass):
- """Reload the automation from config.
-
- Returns a coroutine object.
- """
- return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
-
-
async def async_setup(hass, config):
"""Set up the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass,
@@ -412,8 +368,8 @@ def _async_get_action(hass, config, name):
async def action(entity_id, variables, context):
"""Execute an action."""
_LOGGER.info('Executing %s', name)
- logbook.async_log_entry(
- hass, name, 'has been triggered', DOMAIN, entity_id)
+ hass.components.logbook.async_log_entry(
+ name, 'has been triggered', DOMAIN, entity_id)
await script_obj.async_run(variables, context)
return action
diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py
index e19a85edae6..a9605f343fd 100644
--- a/homeassistant/components/automation/event.py
+++ b/homeassistant/components/automation/event.py
@@ -4,7 +4,6 @@ Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#event-trigger
"""
-import asyncio
import logging
import voluptuous as vol
@@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE)
event_data_schema = vol.Schema(
diff --git a/homeassistant/components/automation/homeassistant.py b/homeassistant/components/automation/homeassistant.py
index b55d99f706a..30ab979d6f4 100644
--- a/homeassistant/components/automation/homeassistant.py
+++ b/homeassistant/components/automation/homeassistant.py
@@ -4,7 +4,6 @@ Offer Home Assistant core automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#homeassistant-trigger
"""
-import asyncio
import logging
import voluptuous as vol
@@ -23,8 +22,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py
index c827fe8f7a4..c0d2dd99ba2 100644
--- a/homeassistant/components/automation/litejet.py
+++ b/homeassistant/components/automation/litejet.py
@@ -4,7 +4,6 @@ Trigger an automation when a LiteJet switch is released.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/automation.litejet/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -33,8 +32,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
number = config.get(CONF_NUMBER)
held_more_than = config.get(CONF_HELD_MORE_THAN)
diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py
index 60c33ca9b0e..99d5ab8674c 100644
--- a/homeassistant/components/automation/mqtt.py
+++ b/homeassistant/components/automation/mqtt.py
@@ -4,7 +4,6 @@ Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
"""
-import asyncio
import json
import voluptuous as vol
@@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD)
@@ -51,6 +49,6 @@ def async_trigger(hass, config, action):
'trigger': data
})
- remove = yield from mqtt.async_subscribe(
+ remove = await mqtt.async_subscribe(
hass, topic, mqtt_automation_listener)
return remove
diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py
index f0dcbf0be57..675b6f3653a 100644
--- a/homeassistant/components/automation/numeric_state.py
+++ b/homeassistant/components/automation/numeric_state.py
@@ -4,7 +4,6 @@ Offer numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
"""
-import asyncio
import logging
import voluptuous as vol
@@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW)
diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py
index 263d4158e25..46c5cafa071 100644
--- a/homeassistant/components/automation/state.py
+++ b/homeassistant/components/automation/state.py
@@ -4,7 +4,6 @@ Offer state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#state-trigger
"""
-import asyncio
import voluptuous as vol
from homeassistant.core import callback
@@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
}), cv.key_dependency(CONF_FOR, CONF_TO))
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL)
diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py
index 497b8453267..7cefe6953a1 100644
--- a/homeassistant/components/automation/sun.py
+++ b/homeassistant/components/automation/sun.py
@@ -4,7 +4,6 @@ Offer sun based automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#sun-trigger
"""
-import asyncio
from datetime import timedelta
import logging
@@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for events based on configuration."""
event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET)
diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py
index 67a44f1a347..c0d83b1067f 100644
--- a/homeassistant/components/automation/template.py
+++ b/homeassistant/components/automation/template.py
@@ -4,7 +4,6 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#template-trigger
"""
-import asyncio
import logging
import voluptuous as vol
@@ -23,8 +22,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass
diff --git a/homeassistant/components/automation/time.py b/homeassistant/components/automation/time.py
index a3a8496c3c5..eccc31581a0 100644
--- a/homeassistant/components/automation/time.py
+++ b/homeassistant/components/automation/time.py
@@ -4,7 +4,6 @@ Offer time listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#time-trigger
"""
-import asyncio
import logging
import voluptuous as vol
@@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
if CONF_AT in config:
at_time = config.get(CONF_AT)
diff --git a/homeassistant/components/automation/webhook.py b/homeassistant/components/automation/webhook.py
new file mode 100644
index 00000000000..2c9c331cdc5
--- /dev/null
+++ b/homeassistant/components/automation/webhook.py
@@ -0,0 +1,54 @@
+"""
+Offer webhook triggered automation rules.
+
+For more details about this automation rule, please refer to the documentation
+at https://home-assistant.io/docs/automation/trigger/#webhook-trigger
+"""
+from functools import partial
+import logging
+
+from aiohttp import hdrs
+import voluptuous as vol
+
+from homeassistant.core import callback
+from homeassistant.const import CONF_PLATFORM
+import homeassistant.helpers.config_validation as cv
+
+DEPENDENCIES = ('webhook',)
+
+_LOGGER = logging.getLogger(__name__)
+CONF_WEBHOOK_ID = 'webhook_id'
+
+TRIGGER_SCHEMA = vol.Schema({
+ vol.Required(CONF_PLATFORM): 'webhook',
+ vol.Required(CONF_WEBHOOK_ID): cv.string,
+})
+
+
+async def _handle_webhook(action, hass, webhook_id, request):
+ """Handle incoming webhook."""
+ result = {
+ 'platform': 'webhook',
+ 'webhook_id': webhook_id,
+ }
+
+ if 'json' in request.headers.get(hdrs.CONTENT_TYPE, ''):
+ result['json'] = await request.json()
+ else:
+ result['data'] = await request.post()
+
+ hass.async_run_job(action, {'trigger': result})
+
+
+async def async_trigger(hass, config, action):
+ """Trigger based on incoming webhooks."""
+ webhook_id = config.get(CONF_WEBHOOK_ID)
+ hass.components.webhook.async_register(
+ webhook_id, partial(_handle_webhook, action))
+
+ @callback
+ def unregister():
+ """Unregister webhook."""
+ hass.components.webhook.async_unregister(webhook_id)
+
+ return unregister
diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py
index f30dfe753cb..dfc9cc418bf 100644
--- a/homeassistant/components/automation/zone.py
+++ b/homeassistant/components/automation/zone.py
@@ -4,7 +4,6 @@ Offer zone automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#zone-trigger
"""
-import asyncio
import voluptuous as vol
from homeassistant.core import callback
@@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_trigger(hass, config, action):
+async def async_trigger(hass, config, action):
"""Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE)
diff --git a/homeassistant/components/binary_sensor/ads.py b/homeassistant/components/binary_sensor/ads.py
index d46ff5ec2ee..1ee56cac9d3 100644
--- a/homeassistant/components/binary_sensor/ads.py
+++ b/homeassistant/components/binary_sensor/ads.py
@@ -4,7 +4,6 @@ Support for ADS binary sensors.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/binary_sensor.ads/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -50,8 +49,7 @@ class AdsBinarySensor(BinarySensorDevice):
self._ads_hub = ads_hub
self.ads_var = ads_var
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register device notification."""
def update(name, value):
"""Handle device notifications."""
diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/binary_sensor/alarmdecoder.py
index 82bcc50259f..1b50d6c6c72 100644
--- a/homeassistant/components/binary_sensor/alarmdecoder.py
+++ b/homeassistant/components/binary_sensor/alarmdecoder.py
@@ -4,7 +4,6 @@ Support for AlarmDecoder zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.alarmdecoder/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -64,8 +63,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._relay_addr = relay_addr
self._relay_chan = relay_chan
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_ZONE_FAULT, self._fault_callback)
diff --git a/homeassistant/components/binary_sensor/android_ip_webcam.py b/homeassistant/components/binary_sensor/android_ip_webcam.py
index 58de81c30e7..085bafd3ae3 100644
--- a/homeassistant/components/binary_sensor/android_ip_webcam.py
+++ b/homeassistant/components/binary_sensor/android_ip_webcam.py
@@ -4,8 +4,6 @@ Support for IP Webcam binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.android_ip_webcam/
"""
-import asyncio
-
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.android_ip_webcam import (
KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME)
@@ -13,9 +11,8 @@ from homeassistant.components.android_ip_webcam import (
DEPENDENCIES = ['android_ip_webcam']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the IP Webcam binary sensors."""
if discovery_info is None:
return
@@ -51,8 +48,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
"""Return true if the binary sensor is on."""
return self._state
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Retrieve latest state."""
state, _ = self._ipcam.export_sensor(self._sensor)
self._state = state == 1.0
diff --git a/homeassistant/components/binary_sensor/bayesian.py b/homeassistant/components/binary_sensor/bayesian.py
index 88669d67d80..f7802f0f29d 100644
--- a/homeassistant/components/binary_sensor/bayesian.py
+++ b/homeassistant/components/binary_sensor/bayesian.py
@@ -4,7 +4,6 @@ Use Bayesian Inference to trigger a binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.bayesian/
"""
-import asyncio
import logging
from collections import OrderedDict
@@ -74,9 +73,8 @@ def update_probability(prior, prob_true, prob_false):
return probability
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Bayesian Binary sensor."""
name = config.get(CONF_NAME)
observations = config.get(CONF_OBSERVATIONS)
@@ -119,8 +117,7 @@ class BayesianBinarySensor(BinarySensorDevice):
'state': self._process_state
}
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity about to be added."""
@callback
def async_threshold_sensor_state_listener(entity, old_state,
@@ -214,7 +211,6 @@ class BayesianBinarySensor(BinarySensorDevice):
ATTR_PROBABILITY_THRESHOLD: self._probability_threshold,
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data and update the states."""
self._deviation = bool(self.probability >= self._probability_threshold)
diff --git a/homeassistant/components/binary_sensor/blink.py b/homeassistant/components/binary_sensor/blink.py
index 6ade20b72b9..6519d09a29a 100644
--- a/homeassistant/components/binary_sensor/blink.py
+++ b/homeassistant/components/binary_sensor/blink.py
@@ -2,10 +2,11 @@
Support for Blink system camera control.
For more details about this platform, please refer to the documentation at
-https://home-assistant.io/components/binary_sensor.blink/
+https://home-assistant.io/components/binary_sensor.blink.
"""
-from homeassistant.components.blink import DOMAIN
+from homeassistant.components.blink import BLINK_DATA, BINARY_SENSORS
from homeassistant.components.binary_sensor import BinarySensorDevice
+from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['blink']
@@ -14,24 +15,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the blink binary sensors."""
if discovery_info is None:
return
+ data = hass.data[BLINK_DATA]
- data = hass.data[DOMAIN].blink
- devs = list()
- for name in data.cameras:
- devs.append(BlinkCameraMotionSensor(name, data))
- devs.append(BlinkSystemSensor(data))
+ devs = []
+ for camera in data.sync.cameras:
+ for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
+ devs.append(BlinkBinarySensor(data, camera, sensor_type))
add_entities(devs, True)
-class BlinkCameraMotionSensor(BinarySensorDevice):
+class BlinkBinarySensor(BinarySensorDevice):
"""Representation of a Blink binary sensor."""
- def __init__(self, name, data):
+ def __init__(self, data, camera, sensor_type):
"""Initialize the sensor."""
- self._name = 'blink_' + name + '_motion_enabled'
- self._camera_name = name
self.data = data
- self._state = self.data.cameras[self._camera_name].armed
+ self._type = sensor_type
+ name, icon = BINARY_SENSORS[sensor_type]
+ self._name = "{} {} {}".format(BLINK_DATA, camera, name)
+ self._icon = icon
+ self._camera = data.sync.cameras[camera]
+ self._state = None
@property
def name(self):
@@ -46,29 +50,4 @@ class BlinkCameraMotionSensor(BinarySensorDevice):
def update(self):
"""Update sensor state."""
self.data.refresh()
- self._state = self.data.cameras[self._camera_name].armed
-
-
-class BlinkSystemSensor(BinarySensorDevice):
- """A representation of a Blink system sensor."""
-
- def __init__(self, data):
- """Initialize the sensor."""
- self._name = 'blink armed status'
- self.data = data
- self._state = self.data.arm
-
- @property
- def name(self):
- """Return the name of the blink sensor."""
- return self._name.replace(" ", "_")
-
- @property
- def is_on(self):
- """Return the status of the sensor."""
- return self._state
-
- def update(self):
- """Update sensor state."""
- self.data.refresh()
- self._state = self.data.arm
+ self._state = self._camera.attributes[self._type]
diff --git a/homeassistant/components/binary_sensor/bmw_connected_drive.py b/homeassistant/components/binary_sensor/bmw_connected_drive.py
index 36229828d63..3fe8136c93b 100644
--- a/homeassistant/components/binary_sensor/bmw_connected_drive.py
+++ b/homeassistant/components/binary_sensor/bmw_connected_drive.py
@@ -4,7 +4,6 @@ Reads vehicle status from BMW connected drive portal.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.bmw_connected_drive/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -124,7 +123,10 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
if not check_control_messages:
result['check_control_messages'] = 'OK'
else:
- result['check_control_messages'] = check_control_messages
+ cbs_list = []
+ for message in check_control_messages:
+ cbs_list.append(message['ccmDescriptionShort'])
+ result['check_control_messages'] = cbs_list
elif self._attribute == 'charging_status':
result['charging_status'] = vehicle_state.charging_status.value
# pylint: disable=protected-access
@@ -190,8 +192,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
"""Schedule a state update."""
self.schedule_update_ha_state(True)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Add callback after being added to hass.
Show latest data after startup.
diff --git a/homeassistant/components/binary_sensor/egardia.py b/homeassistant/components/binary_sensor/egardia.py
index 0db2cac667f..56d7dda17ba 100644
--- a/homeassistant/components/binary_sensor/egardia.py
+++ b/homeassistant/components/binary_sensor/egardia.py
@@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.egardia/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -18,9 +17,8 @@ EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion',
'IR': 'motion'}
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Initialize the platform."""
if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None):
diff --git a/homeassistant/components/binary_sensor/envisalink.py b/homeassistant/components/binary_sensor/envisalink.py
index 2568879bcc6..276ace8dd51 100644
--- a/homeassistant/components/binary_sensor/envisalink.py
+++ b/homeassistant/components/binary_sensor/envisalink.py
@@ -4,7 +4,6 @@ Support for Envisalink zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/
"""
-import asyncio
import logging
import datetime
@@ -22,9 +21,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['envisalink']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Envisalink binary sensor devices."""
configured_zones = discovery_info['zones']
@@ -56,8 +54,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
_LOGGER.debug('Setting up zone: %s', zone_name)
super().__init__(zone_name, info, controller)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONE_UPDATE, self._update_callback)
diff --git a/homeassistant/components/binary_sensor/ffmpeg_motion.py b/homeassistant/components/binary_sensor/ffmpeg_motion.py
index 365bcafbd69..df811d47e56 100644
--- a/homeassistant/components/binary_sensor/ffmpeg_motion.py
+++ b/homeassistant/components/binary_sensor/ffmpeg_motion.py
@@ -4,7 +4,6 @@ Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -46,13 +45,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the FFmpeg binary motion sensor."""
manager = hass.data[DATA_FFMPEG]
- if not manager.async_run_test(config.get(CONF_INPUT)):
+ if not await manager.async_run_test(config.get(CONF_INPUT)):
return
entity = FFmpegMotion(hass, manager, config)
@@ -98,8 +96,7 @@ class FFmpegMotion(FFmpegBinarySensor):
self.ffmpeg = SensorMotion(
manager.binary, hass.loop, self._async_callback)
- @asyncio.coroutine
- def _async_start_ffmpeg(self, entity_ids):
+ async def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance.
This method is a coroutine.
@@ -116,7 +113,7 @@ class FFmpegMotion(FFmpegBinarySensor):
)
# run
- yield from self.ffmpeg.open_sensor(
+ await self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
)
diff --git a/homeassistant/components/binary_sensor/ffmpeg_noise.py b/homeassistant/components/binary_sensor/ffmpeg_noise.py
index 73c84ac336d..a2625c3de8d 100644
--- a/homeassistant/components/binary_sensor/ffmpeg_noise.py
+++ b/homeassistant/components/binary_sensor/ffmpeg_noise.py
@@ -4,7 +4,6 @@ Provides a binary sensor which is a collection of ffmpeg tools.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -43,13 +42,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the FFmpeg noise binary sensor."""
manager = hass.data[DATA_FFMPEG]
- if not manager.async_run_test(config.get(CONF_INPUT)):
+ if not await manager.async_run_test(config.get(CONF_INPUT)):
return
entity = FFmpegNoise(hass, manager, config)
@@ -67,8 +65,7 @@ class FFmpegNoise(FFmpegBinarySensor):
self.ffmpeg = SensorNoise(
manager.binary, hass.loop, self._async_callback)
- @asyncio.coroutine
- def _async_start_ffmpeg(self, entity_ids):
+ async def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance.
This method is a coroutine.
@@ -82,7 +79,7 @@ class FFmpegNoise(FFmpegBinarySensor):
peak=self._config.get(CONF_PEAK),
)
- yield from self.ffmpeg.open_sensor(
+ await self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
diff --git a/homeassistant/components/binary_sensor/fritzbox.py b/homeassistant/components/binary_sensor/fritzbox.py
new file mode 100644
index 00000000000..ab58e6e84bc
--- /dev/null
+++ b/homeassistant/components/binary_sensor/fritzbox.py
@@ -0,0 +1,64 @@
+"""
+Support for Fritzbox binary sensors.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/binary_sensor.fritzbox/
+"""
+import logging
+
+import requests
+
+from homeassistant.components.binary_sensor import BinarySensorDevice
+from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN
+
+DEPENDENCIES = ['fritzbox']
+
+_LOGGER = logging.getLogger(__name__)
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up the Fritzbox binary sensor platform."""
+ devices = []
+ fritz_list = hass.data[FRITZBOX_DOMAIN]
+
+ for fritz in fritz_list:
+ device_list = fritz.get_devices()
+ for device in device_list:
+ if device.has_alarm:
+ devices.append(FritzboxBinarySensor(device, fritz))
+
+ add_entities(devices, True)
+
+
+class FritzboxBinarySensor(BinarySensorDevice):
+ """Representation of a binary Fritzbox device."""
+
+ def __init__(self, device, fritz):
+ """Initialize the Fritzbox binary sensor."""
+ self._device = device
+ self._fritz = fritz
+
+ @property
+ def name(self):
+ """Return the name of the entity."""
+ return self._device.name
+
+ @property
+ def device_class(self):
+ """Return the class of this sensor."""
+ return 'window'
+
+ @property
+ def is_on(self):
+ """Return true if sensor is on."""
+ if not self._device.present:
+ return False
+ return self._device.alert_state
+
+ def update(self):
+ """Get latest data from the Fritzbox."""
+ try:
+ self._device.update()
+ except requests.exceptions.HTTPError as ex:
+ _LOGGER.warning("Connection error: %s", ex)
+ self._fritz.login()
diff --git a/homeassistant/components/binary_sensor/insteon.py b/homeassistant/components/binary_sensor/insteon.py
index 533ff2d76c0..c399d31a95b 100644
--- a/homeassistant/components/binary_sensor/insteon.py
+++ b/homeassistant/components/binary_sensor/insteon.py
@@ -4,7 +4,6 @@ Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.insteon/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -22,9 +21,8 @@ SENSOR_TYPES = {'openClosedSensor': 'opening',
'batterySensor': 'battery'}
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data['insteon'].get('modem')
diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/binary_sensor/isy994.py
index 36dacb06738..31a9606d950 100644
--- a/homeassistant/components/binary_sensor/isy994.py
+++ b/homeassistant/components/binary_sensor/isy994.py
@@ -4,8 +4,6 @@ Support for ISY994 binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.isy994/
"""
-
-import asyncio
import logging
from datetime import timedelta
from typing import Callable
@@ -121,10 +119,9 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
self._computed_state = bool(self._node.status._val)
self._status_was_unknown = False
- @asyncio.coroutine
- def async_added_to_hass(self) -> None:
+ async def async_added_to_hass(self) -> None:
"""Subscribe to the node and subnode event emitters."""
- yield from super().async_added_to_hass()
+ await super().async_added_to_hass()
self._node.controlEvents.subscribe(self._positive_node_control_handler)
@@ -261,10 +258,9 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
self._parent_device = parent_device
self._heartbeat_timer = None
- @asyncio.coroutine
- def async_added_to_hass(self) -> None:
+ async def async_added_to_hass(self) -> None:
"""Subscribe to the node and subnode event emitters."""
- yield from super().async_added_to_hass()
+ await super().async_added_to_hass()
self._node.controlEvents.subscribe(
self._heartbeat_node_control_handler)
diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py
index 37a26a27214..baaf6a9a567 100644
--- a/homeassistant/components/binary_sensor/mqtt.py
+++ b/homeassistant/components/binary_sensor/mqtt.py
@@ -4,23 +4,26 @@ Support for MQTT binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/
"""
-import asyncio
import logging
from typing import Optional
import voluptuous as vol
from homeassistant.core import callback
-from homeassistant.components import mqtt
+from homeassistant.components import mqtt, binary_sensor
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import (
- CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
- CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
+ CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
+ MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -44,13 +47,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up the MQTT binary sensor."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT binary sensor through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT binary sensor dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add a MQTT binary sensor."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(binary_sensor.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities,
+ discovery_hash=None):
+ """Set up the MQTT binary sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
@@ -68,19 +86,22 @@ def async_setup_platform(hass, config, async_add_entities,
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template,
config.get(CONF_UNIQUE_ID),
+ discovery_hash,
)])
-class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
+class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
+ BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class,
qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template,
- unique_id: Optional[str]):
+ unique_id: Optional[str], discovery_hash):
"""Initialize the MQTT binary sensor."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._name = name
self._state = None
self._state_topic = state_topic
@@ -91,11 +112,12 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._force_update = force_update
self._template = value_template
self._unique_id = unique_id
+ self._discovery_hash = discovery_hash
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe mqtt events."""
- yield from super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
@callback
def state_message_received(topic, payload, qos):
@@ -115,7 +137,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self.async_schedule_update_ha_state()
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._state_topic, state_message_received, self._qos)
@property
diff --git a/homeassistant/components/binary_sensor/mychevy.py b/homeassistant/components/binary_sensor/mychevy.py
index d1438379da1..c1e3b6f0aac 100644
--- a/homeassistant/components/binary_sensor/mychevy.py
+++ b/homeassistant/components/binary_sensor/mychevy.py
@@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mychevy/
"""
-
-import asyncio
import logging
from homeassistant.components.mychevy import (
@@ -22,9 +20,8 @@ SENSORS = [
]
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the MyChevy sensors."""
if discovery_info is None:
return
@@ -75,8 +72,7 @@ class EVBinarySensor(BinarySensorDevice):
"""Return the car."""
return self._conn.get_car(self._car_vid)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.async_update_callback)
diff --git a/homeassistant/components/binary_sensor/mystrom.py b/homeassistant/components/binary_sensor/mystrom.py
index 23f40ce0a7f..5785ed464fd 100644
--- a/homeassistant/components/binary_sensor/mystrom.py
+++ b/homeassistant/components/binary_sensor/mystrom.py
@@ -4,7 +4,6 @@ Support for the myStrom buttons.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mystrom/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
@@ -16,9 +15,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up myStrom Binary Sensor."""
hass.http.register_view(MyStromView(async_add_entities))
@@ -37,14 +35,12 @@ class MyStromView(HomeAssistantView):
self.buttons = {}
self.add_entities = add_entities
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Handle the GET request received from a myStrom button."""
- res = yield from self._handle(request.app['hass'], request.query)
+ res = await self._handle(request.app['hass'], request.query)
return res
- @asyncio.coroutine
- def _handle(self, hass, data):
+ async def _handle(self, hass, data):
"""Handle requests to the myStrom endpoint."""
button_action = next((
parameter for parameter in data
diff --git a/homeassistant/components/binary_sensor/openuv.py b/homeassistant/components/binary_sensor/openuv.py
index c7c27d73ee4..bd6e4d1d5dc 100644
--- a/homeassistant/components/binary_sensor/openuv.py
+++ b/homeassistant/components/binary_sensor/openuv.py
@@ -93,7 +93,11 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
async def async_update(self):
"""Update the state."""
- data = self.openuv.data[DATA_PROTECTION_WINDOW]['result']
+ data = self.openuv.data[DATA_PROTECTION_WINDOW]
+
+ if not data:
+ return
+
if self._sensor_type == TYPE_PROTECTION_WINDOW:
self._state = parse_datetime(
data['from_time']) <= utcnow() <= parse_datetime(
diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/binary_sensor/ping.py
index 4c597dd63e1..f12957e6129 100644
--- a/homeassistant/components/binary_sensor/ping.py
+++ b/homeassistant/components/binary_sensor/ping.py
@@ -4,18 +4,19 @@ Tracks the latency of a host by sending ICMP echo requests (ping).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ping/
"""
-import logging
-import subprocess
-import re
-import sys
+import asyncio
from datetime import timedelta
+import logging
+import re
+import subprocess
+import sys
import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import (
- BinarySensorDevice, PLATFORM_SCHEMA)
-from homeassistant.const import CONF_NAME, CONF_HOST
+ PLATFORM_SCHEMA, BinarySensorDevice)
+from homeassistant.const import CONF_HOST, CONF_NAME
+import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -48,13 +49,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-def setup_platform(hass, config, add_entities, discovery_info=None):
+async def async_setup_platform(
+ hass, config, async_add_entities, discovery_info=None):
"""Set up the Ping Binary sensor."""
name = config.get(CONF_NAME)
host = config.get(CONF_HOST)
count = config.get(CONF_PING_COUNT)
- add_entities([PingBinarySensor(name, PingData(host, count))], True)
+ async_add_entities([PingBinarySensor(name, PingData(host, count))], True)
class PingBinarySensor(BinarySensorDevice):
@@ -91,9 +93,9 @@ class PingBinarySensor(BinarySensorDevice):
ATTR_ROUND_TRIP_TIME_MIN: self.ping.data['min'],
}
- def update(self):
+ async def async_update(self):
"""Get the latest data."""
- self.ping.update()
+ await self.ping.update()
class PingData:
@@ -114,12 +116,13 @@ class PingData:
'ping', '-n', '-q', '-c', str(self._count), '-W1',
self._ip_address]
- def ping(self):
+ async def ping(self):
"""Send ICMP echo request and return details if success."""
- pinger = subprocess.Popen(
- self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ pinger = await asyncio.create_subprocess_shell(
+ ' '.join(self._ping_cmd), stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE)
try:
- out = pinger.communicate()
+ out = await pinger.communicate()
_LOGGER.debug("Output is %s", str(out))
if sys.platform == 'win32':
match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1])
@@ -128,7 +131,8 @@ class PingData:
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
- 'mdev': ''}
+ 'mdev': '',
+ }
if 'max/' not in str(out):
match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max = match.groups()
@@ -136,18 +140,20 @@ class PingData:
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
- 'mdev': ''}
+ 'mdev': '',
+ }
match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return {
'min': rtt_min,
'avg': rtt_avg,
'max': rtt_max,
- 'mdev': rtt_mdev}
+ 'mdev': rtt_mdev,
+ }
except (subprocess.CalledProcessError, AttributeError):
return False
- def update(self):
+ async def update(self):
"""Retrieve the latest details from the host."""
- self.data = self.ping()
+ self.data = await self.ping()
self.available = bool(self.data)
diff --git a/homeassistant/components/binary_sensor/rachio.py b/homeassistant/components/binary_sensor/rachio.py
index 798b6a754d1..36a32c79c5c 100644
--- a/homeassistant/components/binary_sensor/rachio.py
+++ b/homeassistant/components/binary_sensor/rachio.py
@@ -92,6 +92,11 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
"""Return the name of this sensor including the controller name."""
return "{} online".format(self._controller.name)
+ @property
+ def unique_id(self) -> str:
+ """Return a unique id for this entity."""
+ return "{}-online".format(self._controller.controller_id)
+
@property
def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES."""
diff --git a/homeassistant/components/binary_sensor/satel_integra.py b/homeassistant/components/binary_sensor/satel_integra.py
index 3500f0a0576..8aff02d55a7 100644
--- a/homeassistant/components/binary_sensor/satel_integra.py
+++ b/homeassistant/components/binary_sensor/satel_integra.py
@@ -4,7 +4,6 @@ Support for Satel Integra zone states- represented as binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.satel_integra/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -20,9 +19,8 @@ DEPENDENCIES = ['satel_integra']
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Satel Integra binary sensor devices."""
if not discovery_info:
return
@@ -50,8 +48,7 @@ class SatelIntegraBinarySensor(BinarySensorDevice):
self._zone_type = zone_type
self._state = 0
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated)
diff --git a/homeassistant/components/binary_sensor/spc.py b/homeassistant/components/binary_sensor/spc.py
index c1be72db374..baa25266804 100644
--- a/homeassistant/components/binary_sensor/spc.py
+++ b/homeassistant/components/binary_sensor/spc.py
@@ -9,8 +9,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback
-from homeassistant.components.spc import (
- ATTR_DISCOVER_DEVICES, SIGNAL_UPDATE_SENSOR)
+from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_SENSOR)
_LOGGER = logging.getLogger(__name__)
@@ -27,13 +26,12 @@ def _get_device_class(zone_type):
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the SPC binary sensor."""
- if (discovery_info is None or
- discovery_info[ATTR_DISCOVER_DEVICES] is None):
+ if discovery_info is None:
return
-
- async_add_entities(SpcBinarySensor(zone)
- for zone in discovery_info[ATTR_DISCOVER_DEVICES]
- if _get_device_class(zone.type))
+ api = hass.data[DATA_API]
+ async_add_entities([SpcBinarySensor(zone)
+ for zone in api.zones.values()
+ if _get_device_class(zone.type)])
class SpcBinarySensor(BinarySensorDevice):
diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py
index c5bfa593022..89547dffbc9 100644
--- a/homeassistant/components/binary_sensor/template.py
+++ b/homeassistant/components/binary_sensor/template.py
@@ -4,7 +4,6 @@ Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -46,9 +45,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up template binary sensors."""
sensors = []
@@ -109,8 +107,7 @@ class BinarySensorTemplate(BinarySensorDevice):
self._delay_on = delay_on
self._delay_off = delay_off
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def template_bsensor_state_listener(entity, old_state, new_state):
diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py
index fd7ead08822..0dadf3a61fd 100644
--- a/homeassistant/components/binary_sensor/threshold.py
+++ b/homeassistant/components/binary_sensor/threshold.py
@@ -4,7 +4,6 @@ Support for monitoring if a sensor value is below/above a threshold.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.threshold/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -54,9 +53,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Threshold sensor."""
entity_id = config.get(CONF_ENTITY_ID)
name = config.get(CONF_NAME)
@@ -147,8 +145,7 @@ class ThresholdSensor(BinarySensorDevice):
ATTR_UPPER: self._threshold_upper,
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data and updates the states."""
def below(threshold):
"""Determine if the sensor value is below a threshold."""
diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py
index ae6fd5562bf..0b168e45b4d 100644
--- a/homeassistant/components/binary_sensor/trend.py
+++ b/homeassistant/components/binary_sensor/trend.py
@@ -4,7 +4,6 @@ A sensor that monitors trends in other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.trend/
"""
-import asyncio
from collections import deque
import logging
import math
@@ -138,8 +137,7 @@ class SensorTrend(BinarySensorDevice):
"""No polling needed."""
return False
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Complete device setup after being added to hass."""
@callback
def trend_sensor_state_listener(entity, old_state, new_state):
@@ -160,8 +158,7 @@ class SensorTrend(BinarySensorDevice):
self.hass, self._entity_id,
trend_sensor_state_listener)
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data and update the states."""
# Remove outdated samples
if self._sample_duration > 0:
@@ -173,7 +170,7 @@ class SensorTrend(BinarySensorDevice):
return
# Calculate gradient of linear trend
- yield from self.hass.async_add_job(self._calculate_gradient)
+ await self.hass.async_add_job(self._calculate_gradient)
# Update state
self._state = (
diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py
index 1976e49f446..a950289789e 100644
--- a/homeassistant/components/binary_sensor/wink.py
+++ b/homeassistant/components/binary_sensor/wink.py
@@ -4,7 +4,6 @@ Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/binary_sensor.wink/
"""
-import asyncio
import logging
from homeassistant.components.binary_sensor import BinarySensorDevice
@@ -101,8 +100,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
else:
self.capability = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self)
diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py
index 1d85d9c9a47..82b5e66629a 100644
--- a/homeassistant/components/binary_sensor/workday.py
+++ b/homeassistant/components/binary_sensor/workday.py
@@ -4,7 +4,6 @@ Sensor to indicate whether the current day is a workday.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.workday/
"""
-import asyncio
import logging
from datetime import datetime, timedelta
@@ -162,8 +161,7 @@ class IsWorkdaySensor(BinarySensorDevice):
CONF_OFFSET: self._days_offset
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get date and look whether it is a holiday."""
# Default is no workday
self._state = False
diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py
deleted file mode 100644
index e84643711eb..00000000000
--- a/homeassistant/components/blink.py
+++ /dev/null
@@ -1,89 +0,0 @@
-"""
-Support for Blink Home Camera System.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/blink/
-"""
-import logging
-
-import voluptuous as vol
-
-import homeassistant.helpers.config_validation as cv
-from homeassistant.const import (
- CONF_USERNAME, CONF_PASSWORD, ATTR_FRIENDLY_NAME, ATTR_ARMED)
-from homeassistant.helpers import discovery
-
-REQUIREMENTS = ['blinkpy==0.6.0']
-
-_LOGGER = logging.getLogger(__name__)
-
-DOMAIN = 'blink'
-
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Required(CONF_USERNAME): cv.string,
- vol.Required(CONF_PASSWORD): cv.string
- })
-}, extra=vol.ALLOW_EXTRA)
-
-ARM_SYSTEM_SCHEMA = vol.Schema({
- vol.Optional(ATTR_ARMED): cv.boolean
-})
-
-ARM_CAMERA_SCHEMA = vol.Schema({
- vol.Required(ATTR_FRIENDLY_NAME): cv.string,
- vol.Optional(ATTR_ARMED): cv.boolean
-})
-
-SNAP_PICTURE_SCHEMA = vol.Schema({
- vol.Required(ATTR_FRIENDLY_NAME): cv.string
-})
-
-
-class BlinkSystem:
- """Blink System class."""
-
- def __init__(self, config_info):
- """Initialize the system."""
- import blinkpy
- self.blink = blinkpy.Blink(username=config_info[DOMAIN][CONF_USERNAME],
- password=config_info[DOMAIN][CONF_PASSWORD])
- self.blink.setup_system()
-
-
-def setup(hass, config):
- """Set up Blink System."""
- hass.data[DOMAIN] = BlinkSystem(config)
- discovery.load_platform(hass, 'camera', DOMAIN, {}, config)
- discovery.load_platform(hass, 'sensor', DOMAIN, {}, config)
- discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config)
-
- def snap_picture(call):
- """Take a picture."""
- cameras = hass.data[DOMAIN].blink.cameras
- name = call.data.get(ATTR_FRIENDLY_NAME, '')
- if name in cameras:
- cameras[name].snap_picture()
-
- def arm_camera(call):
- """Arm a camera."""
- cameras = hass.data[DOMAIN].blink.cameras
- name = call.data.get(ATTR_FRIENDLY_NAME, '')
- value = call.data.get(ATTR_ARMED, True)
- if name in cameras:
- cameras[name].set_motion_detect(value)
-
- def arm_system(call):
- """Arm the system."""
- value = call.data.get(ATTR_ARMED, True)
- hass.data[DOMAIN].blink.arm = value
- hass.data[DOMAIN].blink.refresh()
-
- hass.services.register(
- DOMAIN, 'snap_picture', snap_picture, schema=SNAP_PICTURE_SCHEMA)
- hass.services.register(
- DOMAIN, 'arm_camera', arm_camera, schema=ARM_CAMERA_SCHEMA)
- hass.services.register(
- DOMAIN, 'arm_system', arm_system, schema=ARM_SYSTEM_SCHEMA)
-
- return True
diff --git a/homeassistant/components/blink/__init__.py b/homeassistant/components/blink/__init__.py
new file mode 100644
index 00000000000..1d84b5be113
--- /dev/null
+++ b/homeassistant/components/blink/__init__.py
@@ -0,0 +1,161 @@
+"""
+Support for Blink Home Camera System.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/blink/
+"""
+import logging
+from datetime import timedelta
+import voluptuous as vol
+
+from homeassistant.helpers import (
+ config_validation as cv, discovery)
+from homeassistant.const import (
+ CONF_USERNAME, CONF_PASSWORD, CONF_NAME, CONF_SCAN_INTERVAL,
+ CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
+ CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
+
+REQUIREMENTS = ['blinkpy==0.9.0']
+
+_LOGGER = logging.getLogger(__name__)
+
+DOMAIN = 'blink'
+BLINK_DATA = 'blink'
+
+CONF_CAMERA = 'camera'
+CONF_ALARM_CONTROL_PANEL = 'alarm_control_panel'
+
+DEFAULT_BRAND = 'Blink'
+DEFAULT_ATTRIBUTION = "Data provided by immedia-semi.com"
+SIGNAL_UPDATE_BLINK = "blink_update"
+
+DEFAULT_SCAN_INTERVAL = timedelta(seconds=60)
+
+TYPE_CAMERA_ARMED = 'motion_enabled'
+TYPE_MOTION_DETECTED = 'motion_detected'
+TYPE_TEMPERATURE = 'temperature'
+TYPE_BATTERY = 'battery'
+TYPE_WIFI_STRENGTH = 'wifi_strength'
+TYPE_STATUS = 'status'
+
+SERVICE_REFRESH = 'blink_update'
+SERVICE_TRIGGER = 'trigger_camera'
+SERVICE_SAVE_VIDEO = 'save_video'
+
+BINARY_SENSORS = {
+ TYPE_CAMERA_ARMED: ['Camera Armed', 'mdi:verified'],
+ TYPE_MOTION_DETECTED: ['Motion Detected', 'mdi:run-fast'],
+}
+
+SENSORS = {
+ TYPE_TEMPERATURE: ['Temperature', TEMP_FAHRENHEIT, 'mdi:thermometer'],
+ TYPE_BATTERY: ['Battery', '%', 'mdi:battery-80'],
+ TYPE_WIFI_STRENGTH: ['Wifi Signal', 'dBm', 'mdi:wifi-strength-2'],
+ TYPE_STATUS: ['Status', '', 'mdi:bell']
+}
+
+BINARY_SENSOR_SCHEMA = vol.Schema({
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)):
+ vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)])
+})
+
+SENSOR_SCHEMA = vol.Schema({
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSORS)):
+ vol.All(cv.ensure_list, [vol.In(SENSORS)])
+})
+
+SERVICE_TRIGGER_SCHEMA = vol.Schema({
+ vol.Required(CONF_NAME): cv.string
+})
+
+SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema({
+ vol.Required(CONF_NAME): cv.string,
+ vol.Required(CONF_FILENAME): cv.string,
+})
+
+CONFIG_SCHEMA = vol.Schema(
+ {
+ DOMAIN:
+ vol.Schema({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL):
+ cv.time_period,
+ vol.Optional(CONF_BINARY_SENSORS, default={}):
+ BINARY_SENSOR_SCHEMA,
+ vol.Optional(CONF_SENSORS, default={}): SENSOR_SCHEMA,
+ })
+ },
+ extra=vol.ALLOW_EXTRA)
+
+
+def setup(hass, config):
+ """Set up Blink System."""
+ from blinkpy import blinkpy
+ conf = config[BLINK_DATA]
+ username = conf[CONF_USERNAME]
+ password = conf[CONF_PASSWORD]
+ scan_interval = conf[CONF_SCAN_INTERVAL]
+ hass.data[BLINK_DATA] = blinkpy.Blink(username=username,
+ password=password)
+ hass.data[BLINK_DATA].refresh_rate = scan_interval.total_seconds()
+ hass.data[BLINK_DATA].start()
+
+ platforms = [
+ ('alarm_control_panel', {}),
+ ('binary_sensor', conf[CONF_BINARY_SENSORS]),
+ ('camera', {}),
+ ('sensor', conf[CONF_SENSORS]),
+ ]
+
+ for component, schema in platforms:
+ discovery.load_platform(hass, component, DOMAIN, schema, config)
+
+ def trigger_camera(call):
+ """Trigger a camera."""
+ cameras = hass.data[BLINK_DATA].sync.cameras
+ name = call.data[CONF_NAME]
+ if name in cameras:
+ cameras[name].snap_picture()
+ hass.data[BLINK_DATA].refresh(force_cache=True)
+
+ def blink_refresh(event_time):
+ """Call blink to refresh info."""
+ hass.data[BLINK_DATA].refresh(force_cache=True)
+
+ async def async_save_video(call):
+ """Call save video service handler."""
+ await async_handle_save_video_service(hass, call)
+
+ hass.services.register(DOMAIN, SERVICE_REFRESH, blink_refresh)
+ hass.services.register(DOMAIN,
+ SERVICE_TRIGGER,
+ trigger_camera,
+ schema=SERVICE_TRIGGER_SCHEMA)
+ hass.services.register(DOMAIN,
+ SERVICE_SAVE_VIDEO,
+ async_save_video,
+ schema=SERVICE_SAVE_VIDEO_SCHEMA)
+ return True
+
+
+async def async_handle_save_video_service(hass, call):
+ """Handle save video service calls."""
+ camera_name = call.data[CONF_NAME]
+ video_path = call.data[CONF_FILENAME]
+ if not hass.config.is_allowed_path(video_path):
+ _LOGGER.error(
+ "Can't write %s, no access to path!", video_path)
+ return
+
+ def _write_video(camera_name, video_path):
+ """Call video write."""
+ all_cameras = hass.data[BLINK_DATA].sync.cameras
+ if camera_name in all_cameras:
+ all_cameras[camera_name].video_to_file(video_path)
+
+ try:
+ await hass.async_add_executor_job(
+ _write_video, camera_name, video_path)
+ except OSError as err:
+ _LOGGER.error("Can't write image to file: %s", err)
diff --git a/homeassistant/components/blink/services.yaml b/homeassistant/components/blink/services.yaml
new file mode 100644
index 00000000000..fc042b0d598
--- /dev/null
+++ b/homeassistant/components/blink/services.yaml
@@ -0,0 +1,21 @@
+# Describes the format for available Blink services
+
+blink_update:
+ description: Force a refresh.
+
+trigger_camera:
+ description: Request named camera to take new image.
+ fields:
+ name:
+ description: Name of camera to take new image.
+ example: 'Living Room'
+
+save_video:
+ description: Save last recorded video clip to local file.
+ fields:
+ name:
+ description: Name of camera to grab video from.
+ example: 'Living Room'
+ filename:
+ description: Filename to writable path (directory may need to be included in whitelist_dirs in config)
+ example: '/tmp/video.mp4'
diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py
index 12363627003..dce5961d70d 100644
--- a/homeassistant/components/bmw_connected_drive/__init__.py
+++ b/homeassistant/components/bmw_connected_drive/__init__.py
@@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['bimmer_connected==0.5.2']
+REQUIREMENTS = ['bimmer_connected==0.5.3']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py
index a8a486013d4..5897d972b31 100644
--- a/homeassistant/components/camera/__init__.py
+++ b/homeassistant/components/camera/__init__.py
@@ -83,62 +83,6 @@ class Image:
content = attr.ib(type=bytes)
-@bind_hass
-def turn_off(hass, entity_id=None):
- """Turn off camera."""
- hass.add_job(async_turn_off, hass, entity_id)
-
-
-@bind_hass
-async def async_turn_off(hass, entity_id=None):
- """Turn off camera."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)
-
-
-@bind_hass
-def turn_on(hass, entity_id=None):
- """Turn on camera."""
- hass.add_job(async_turn_on, hass, entity_id)
-
-
-@bind_hass
-async def async_turn_on(hass, entity_id=None):
- """Turn on camera, and set operation mode."""
- data = {}
- if entity_id is not None:
- data[ATTR_ENTITY_ID] = entity_id
-
- await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)
-
-
-@bind_hass
-def enable_motion_detection(hass, entity_id=None):
- """Enable Motion Detection."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_ENABLE_MOTION, data))
-
-
-@bind_hass
-def disable_motion_detection(hass, entity_id=None):
- """Disable Motion Detection."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_DISABLE_MOTION, data))
-
-
-@bind_hass
-@callback
-def async_snapshot(hass, filename, entity_id=None):
- """Make a snapshot from a camera."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- data[ATTR_FILENAME] = filename
-
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_SNAPSHOT, data))
-
-
@bind_hass
async def async_get_image(hass, entity_id, timeout=10):
"""Fetch an image from a camera entity."""
@@ -239,7 +183,7 @@ async def async_setup(hass, config):
"""Update tokens of the entities."""
for entity in component.entities:
entity.async_update_token()
- hass.async_add_job(entity.async_update_ha_state())
+ hass.async_create_task(entity.async_update_ha_state())
hass.helpers.event.async_track_time_interval(
update_tokens, TOKEN_CHANGE_INTERVAL)
@@ -508,27 +452,23 @@ class CameraMjpegStream(CameraView):
raise web.HTTPBadRequest()
-@callback
-def websocket_camera_thumbnail(hass, connection, msg):
+@websocket_api.async_response
+async def websocket_camera_thumbnail(hass, connection, msg):
"""Handle get camera thumbnail websocket command.
Async friendly.
"""
- async def send_camera_still():
- """Send a camera still."""
- try:
- image = await async_get_image(hass, msg['entity_id'])
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], {
- 'content_type': image.content_type,
- 'content': base64.b64encode(image.content).decode('utf-8')
- }
- ))
- except HomeAssistantError:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'image_fetch_failed', 'Unable to fetch image'))
-
- hass.async_add_job(send_camera_still())
+ try:
+ image = await async_get_image(hass, msg['entity_id'])
+ connection.send_message(websocket_api.result_message(
+ msg['id'], {
+ 'content_type': image.content_type,
+ 'content': base64.b64encode(image.content).decode('utf-8')
+ }
+ ))
+ except HomeAssistantError:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'image_fetch_failed', 'Unable to fetch image'))
async def async_handle_snapshot_service(camera, service):
diff --git a/homeassistant/components/camera/abode.py b/homeassistant/components/camera/abode.py
index fbab1620a39..39681760d4d 100644
--- a/homeassistant/components/camera/abode.py
+++ b/homeassistant/components/camera/abode.py
@@ -4,7 +4,6 @@ This component provides HA camera support for Abode Security System.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.abode/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -51,10 +50,9 @@ class AbodeCamera(AbodeDevice, Camera):
self._event = event
self._response = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe Abode events."""
- yield from super().async_added_to_hass()
+ await super().async_added_to_hass()
self.hass.async_add_job(
self._data.abode.events.add_timeline_callback,
diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py
index 9f4b84db2cc..55a9c2e4294 100644
--- a/homeassistant/components/camera/amcrest.py
+++ b/homeassistant/components/camera/amcrest.py
@@ -4,7 +4,6 @@ This component provides basic support for Amcrest IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.amcrest/
"""
-import asyncio
import logging
from homeassistant.components.amcrest import (
@@ -21,9 +20,8 @@ DEPENDENCIES = ['amcrest', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up an Amcrest IP Camera."""
if discovery_info is None:
return
@@ -57,12 +55,11 @@ class AmcrestCam(Camera):
response = self._camera.snapshot(channel=self._resolution)
return response.data
- @asyncio.coroutine
- def handle_async_mjpeg_stream(self, request):
+ async def handle_async_mjpeg_stream(self, request):
"""Return an MJPEG stream."""
# The snapshot implementation is handled by the parent class
if self._stream_source == STREAM_SOURCE_LIST['snapshot']:
- yield from super().handle_async_mjpeg_stream(request)
+ await super().handle_async_mjpeg_stream(request)
return
if self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
@@ -72,7 +69,7 @@ class AmcrestCam(Camera):
stream_coro = websession.get(
streaming_url, auth=self._token, timeout=TIMEOUT)
- yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
+ await async_aiohttp_proxy_web(self.hass, request, stream_coro)
else:
# streaming via fmpeg
@@ -80,13 +77,13 @@ class AmcrestCam(Camera):
streaming_url = self._camera.rtsp_url(typeno=self._resolution)
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
- yield from stream.open_camera(
+ await stream.open_camera(
streaming_url, extra_cmd=self._ffmpeg_arguments)
- yield from async_aiohttp_proxy_stream(
+ await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
- yield from stream.close()
+ await stream.close()
@property
def name(self):
diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/camera/blink.py
index 217849138c3..5a728e92ce3 100644
--- a/homeassistant/components/camera/blink.py
+++ b/homeassistant/components/camera/blink.py
@@ -4,31 +4,27 @@ Support for Blink system camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.blink/
"""
-from datetime import timedelta
import logging
-import requests
-
-from homeassistant.components.blink import DOMAIN
+from homeassistant.components.blink import BLINK_DATA, DEFAULT_BRAND
from homeassistant.components.camera import Camera
-from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['blink']
-MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
+ATTR_VIDEO_CLIP = 'video'
+ATTR_IMAGE = 'image'
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a Blink Camera."""
if discovery_info is None:
return
-
- data = hass.data[DOMAIN].blink
- devs = list()
- for name in data.cameras:
- devs.append(BlinkCamera(hass, config, data, name))
+ data = hass.data[BLINK_DATA]
+ devs = []
+ for name, camera in data.sync.cameras.items():
+ devs.append(BlinkCamera(data, name, camera))
add_entities(devs)
@@ -36,15 +32,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class BlinkCamera(Camera):
"""An implementation of a Blink Camera."""
- def __init__(self, hass, config, data, name):
+ def __init__(self, data, name, camera):
"""Initialize a camera."""
super().__init__()
self.data = data
- self.hass = hass
- self._name = name
- self.notifications = self.data.cameras[self._name].notifications
+ self._name = "{} {}".format(BLINK_DATA, name)
+ self._camera = camera
self.response = None
-
+ self.current_image = None
+ self.last_image = None
_LOGGER.debug("Initialized blink camera %s", self._name)
@property
@@ -52,30 +48,29 @@ class BlinkCamera(Camera):
"""Return the camera name."""
return self._name
- @Throttle(MIN_TIME_BETWEEN_UPDATES)
- def request_image(self):
- """Request a new image from Blink servers."""
- _LOGGER.debug("Requesting new image from blink servers")
- image_url = self.check_for_motion()
- header = self.data.cameras[self._name].header
- self.response = requests.get(image_url, headers=header, stream=True)
+ @property
+ def device_state_attributes(self):
+ """Return the camera attributes."""
+ return self._camera.attributes
- def check_for_motion(self):
- """Check if motion has been detected since last update."""
- self.data.refresh()
- notifs = self.data.cameras[self._name].notifications
- if notifs > self.notifications:
- # We detected motion at some point
- self.data.last_motion()
- self.notifications = notifs
- # Returning motion image currently not working
- # return self.data.cameras[self._name].motion['image']
- elif notifs < self.notifications:
- self.notifications = notifs
+ def enable_motion_detection(self):
+ """Enable motion detection for the camera."""
+ self._camera.set_motion_detect(True)
- return self.data.camera_thumbs[self._name]
+ def disable_motion_detection(self):
+ """Disable motion detection for the camera."""
+ self._camera.set_motion_detect(False)
+
+ @property
+ def motion_detection_enabled(self):
+ """Return the state of the camera."""
+ return self._camera.armed
+
+ @property
+ def brand(self):
+ """Return the camera brand."""
+ return DEFAULT_BRAND
def camera_image(self):
"""Return a still image response from the camera."""
- self.request_image()
- return self.response.content
+ return self._camera.image_from_cache.content
diff --git a/homeassistant/components/camera/canary.py b/homeassistant/components/camera/canary.py
index 9031c27b1a9..b9951d8efa2 100644
--- a/homeassistant/components/camera/canary.py
+++ b/homeassistant/components/camera/canary.py
@@ -75,35 +75,33 @@ class CanaryCamera(Camera):
"""Return the camera motion detection status."""
return not self._location.is_recording
- @asyncio.coroutine
- def async_camera_image(self):
+ async def async_camera_image(self):
"""Return a still image response from the camera."""
self.renew_live_stream_session()
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
- image = yield from asyncio.shield(ffmpeg.get_image(
+ image = await asyncio.shield(ffmpeg.get_image(
self._live_stream_session.live_stream_url,
output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
- @asyncio.coroutine
- def handle_async_mjpeg_stream(self, request):
+ async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
if self._live_stream_session is None:
return
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
- yield from stream.open_camera(
+ await stream.open_camera(
self._live_stream_session.live_stream_url,
extra_cmd=self._ffmpeg_arguments)
- yield from async_aiohttp_proxy_stream(
+ await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
- yield from stream.close()
+ await stream.close()
@Throttle(MIN_TIME_BETWEEN_SESSION_RENEW)
def renew_live_stream_session(self):
diff --git a/homeassistant/components/camera/doorbird.py b/homeassistant/components/camera/doorbird.py
index 7af3e7634d0..8982e6d0847 100644
--- a/homeassistant/components/camera/doorbird.py
+++ b/homeassistant/components/camera/doorbird.py
@@ -27,9 +27,8 @@ _LOGGER = logging.getLogger(__name__)
_TIMEOUT = 10 # seconds
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the DoorBird camera platform."""
for doorstation in hass.data[DOORBIRD_DOMAIN]:
device = doorstation.device
@@ -66,8 +65,7 @@ class DoorBirdCamera(Camera):
"""Get the name of the camera."""
return self._name
- @asyncio.coroutine
- def async_camera_image(self):
+ async def async_camera_image(self):
"""Pull a still image from the camera."""
now = datetime.datetime.now()
@@ -77,9 +75,9 @@ class DoorBirdCamera(Camera):
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(_TIMEOUT, loop=self.hass.loop):
- response = yield from websession.get(self._url)
+ response = await websession.get(self._url)
- self._last_image = yield from response.read()
+ self._last_image = await response.read()
self._last_update = now
return self._last_image
except asyncio.TimeoutError:
diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py
index b707c913435..f89e5ff29c2 100644
--- a/homeassistant/components/camera/generic.py
+++ b/homeassistant/components/camera/generic.py
@@ -46,9 +46,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up a generic IP Camera."""
async_add_entities([GenericCamera(hass, config)])
@@ -93,8 +92,7 @@ class GenericCamera(Camera):
return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result()
- @asyncio.coroutine
- def async_camera_image(self):
+ async def async_camera_image(self):
"""Return a still image response from the camera."""
try:
url = self._still_image_url.async_render()
@@ -118,7 +116,7 @@ class GenericCamera(Camera):
_LOGGER.error("Error getting camera image: %s", error)
return self._last_image
- self._last_image = yield from self.hass.async_add_job(
+ self._last_image = await self.hass.async_add_job(
fetch)
# async
else:
@@ -126,9 +124,9 @@ class GenericCamera(Camera):
websession = async_get_clientsession(
self.hass, verify_ssl=self.verify_ssl)
with async_timeout.timeout(10, loop=self.hass.loop):
- response = yield from websession.get(
+ response = await websession.get(
url, auth=self._auth)
- self._last_image = yield from response.read()
+ self._last_image = await response.read()
except asyncio.TimeoutError:
_LOGGER.error("Timeout getting camera image")
return self._last_image
diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py
index f1917aaf23e..1df06b546cd 100644
--- a/homeassistant/components/camera/mjpeg.py
+++ b/homeassistant/components/camera/mjpeg.py
@@ -41,10 +41,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up a MJPEG IP Camera."""
+ # Filter header errors from urllib3 due to a urllib3 bug
+ urllib3_logger = logging.getLogger("urllib3.connectionpool")
+ if not any(isinstance(x, NoHeaderErrorFilter)
+ for x in urllib3_logger.filters):
+ urllib3_logger.addFilter(
+ NoHeaderErrorFilter()
+ )
+
if discovery_info:
config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(config)])
@@ -82,23 +89,22 @@ class MjpegCamera(Camera):
self._username, password=self._password
)
- @asyncio.coroutine
- def async_camera_image(self):
+ async def async_camera_image(self):
"""Return a still image response from the camera."""
# DigestAuth is not supported
if self._authentication == HTTP_DIGEST_AUTHENTICATION or \
self._still_image_url is None:
- image = yield from self.hass.async_add_job(
+ image = await self.hass.async_add_job(
self.camera_image)
return image
websession = async_get_clientsession(self.hass)
try:
with async_timeout.timeout(10, loop=self.hass.loop):
- response = yield from websession.get(
+ response = await websession.get(
self._still_image_url, auth=self._auth)
- image = yield from response.read()
+ image = await response.read()
return image
except asyncio.TimeoutError:
@@ -141,3 +147,11 @@ class MjpegCamera(Camera):
def name(self):
"""Return the name of this camera."""
return self._name
+
+
+class NoHeaderErrorFilter(logging.Filter):
+ """Filter out urllib3 Header Parsing Errors due to a urllib3 bug."""
+
+ def filter(self, record):
+ """Filter out Header Parsing Errors."""
+ return "Failed to parse headers" not in record.getMessage()
diff --git a/homeassistant/components/camera/mqtt.py b/homeassistant/components/camera/mqtt.py
index 13c1745615d..42ad7d6fa66 100644
--- a/homeassistant/components/camera/mqtt.py
+++ b/homeassistant/components/camera/mqtt.py
@@ -10,10 +10,13 @@ import logging
import voluptuous as vol
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.core import callback
-from homeassistant.components import mqtt
from homeassistant.const import CONF_NAME
+from homeassistant.components import mqtt, camera
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__)
@@ -31,13 +34,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up the MQTT Camera."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT camera through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT camera dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add a MQTT camera."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities)
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(camera.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities):
+ """Set up the MQTT Camera."""
async_add_entities([MqttCamera(
config.get(CONF_NAME),
config.get(CONF_UNIQUE_ID),
diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py
index 9cf21dca9f9..2576dfa7f92 100644
--- a/homeassistant/components/camera/onvif.py
+++ b/homeassistant/components/camera/onvif.py
@@ -46,6 +46,7 @@ DIR_LEFT = "LEFT"
DIR_RIGHT = "RIGHT"
ZOOM_OUT = "ZOOM_OUT"
ZOOM_IN = "ZOOM_IN"
+PTZ_NONE = "NONE"
SERVICE_PTZ = "onvif_ptz"
@@ -65,9 +66,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
SERVICE_PTZ_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids,
- ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]),
- ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]),
- ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN])
+ ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT, PTZ_NONE]),
+ ATTR_TILT: vol.In([DIR_UP, DIR_DOWN, PTZ_NONE]),
+ ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN, PTZ_NONE])
})
diff --git a/homeassistant/components/camera/ring.py b/homeassistant/components/camera/ring.py
index d0cb6443fc7..ae886bd0669 100644
--- a/homeassistant/components/camera/ring.py
+++ b/homeassistant/components/camera/ring.py
@@ -110,8 +110,7 @@ class RingCam(Camera):
'video_url': self._video_url,
}
- @asyncio.coroutine
- def async_camera_image(self):
+ async def async_camera_image(self):
"""Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
@@ -119,13 +118,12 @@ class RingCam(Camera):
if self._video_url is None:
return
- image = yield from asyncio.shield(ffmpeg.get_image(
+ image = await asyncio.shield(ffmpeg.get_image(
self._video_url, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
- @asyncio.coroutine
- def handle_async_mjpeg_stream(self, request):
+ async def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg
@@ -133,13 +131,13 @@ class RingCam(Camera):
return
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
- yield from stream.open_camera(
+ await stream.open_camera(
self._video_url, extra_cmd=self._ffmpeg_arguments)
- yield from async_aiohttp_proxy_stream(
+ await async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
- yield from stream.close()
+ await stream.close()
@property
def should_poll(self):
diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py
index 3e587fff234..b504fe34d86 100644
--- a/homeassistant/components/camera/synology.py
+++ b/homeassistant/components/camera/synology.py
@@ -4,7 +4,6 @@ Support for Synology Surveillance Station Cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.synology/
"""
-import asyncio
import logging
import requests
@@ -38,9 +37,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up a Synology IP Camera."""
verify_ssl = config.get(CONF_VERIFY_SSL)
timeout = config.get(CONF_TIMEOUT)
@@ -87,15 +85,14 @@ class SynologyCamera(Camera):
"""Return bytes of camera image."""
return self._surveillance.get_camera_image(self._camera_id)
- @asyncio.coroutine
- def handle_async_mjpeg_stream(self, request):
+ async def handle_async_mjpeg_stream(self, request):
"""Return a MJPEG stream image response directly from the camera."""
streaming_url = self._camera.video_stream_url
websession = async_get_clientsession(self.hass, self._verify_ssl)
stream_coro = websession.get(streaming_url)
- yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
+ await async_aiohttp_proxy_web(self.hass, request, stream_coro)
@property
def name(self):
diff --git a/homeassistant/components/cast/.translations/ru.json b/homeassistant/components/cast/.translations/ru.json
index 9c9353da37e..da03eae701d 100644
--- a/homeassistant/components/cast/.translations/ru.json
+++ b/homeassistant/components/cast/.translations/ru.json
@@ -2,11 +2,11 @@
"config": {
"abort": {
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Google Cast \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.",
- "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Google Cast."
+ "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."
},
"step": {
"confirm": {
- "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
+ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
"title": "Google Cast"
}
},
diff --git a/homeassistant/components/cast/.translations/zh-Hant.json b/homeassistant/components/cast/.translations/zh-Hant.json
index 711ac320397..d5383fb1a2b 100644
--- a/homeassistant/components/cast/.translations/zh-Hant.json
+++ b/homeassistant/components/cast/.translations/zh-Hant.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u8a2d\u5099\u3002",
+ "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u88dd\u7f6e\u3002",
"single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Google Cast \u5373\u53ef\u3002"
},
"step": {
diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py
index a3273f67cc2..98483c454bc 100644
--- a/homeassistant/components/climate/__init__.py
+++ b/homeassistant/components/climate/__init__.py
@@ -10,7 +10,6 @@ import functools as ft
import voluptuous as vol
-from homeassistant.loader import bind_hass
from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent
@@ -20,7 +19,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
- PRECISION_TENTHS, )
+ PRECISION_TENTHS)
DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35
@@ -142,107 +141,6 @@ SET_SWING_MODE_SCHEMA = vol.Schema({
})
-@bind_hass
-def set_away_mode(hass, away_mode, entity_id=None):
- """Turn all or specified climate devices away mode on."""
- data = {
- ATTR_AWAY_MODE: away_mode
- }
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
-
-
-@bind_hass
-def set_hold_mode(hass, hold_mode, entity_id=None):
- """Set new hold mode."""
- data = {
- ATTR_HOLD_MODE: hold_mode
- }
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
-
-
-@bind_hass
-def set_aux_heat(hass, aux_heat, entity_id=None):
- """Turn all or specified climate devices auxiliary heater on."""
- data = {
- ATTR_AUX_HEAT: aux_heat
- }
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
-
-
-@bind_hass
-def set_temperature(hass, temperature=None, entity_id=None,
- target_temp_high=None, target_temp_low=None,
- operation_mode=None):
- """Set new target temperature."""
- kwargs = {
- key: value for key, value in [
- (ATTR_TEMPERATURE, temperature),
- (ATTR_TARGET_TEMP_HIGH, target_temp_high),
- (ATTR_TARGET_TEMP_LOW, target_temp_low),
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_OPERATION_MODE, operation_mode)
- ] if value is not None
- }
- _LOGGER.debug("set_temperature start data=%s", kwargs)
- hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs)
-
-
-@bind_hass
-def set_humidity(hass, humidity, entity_id=None):
- """Set new target humidity."""
- data = {ATTR_HUMIDITY: humidity}
-
- if entity_id is not None:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data)
-
-
-@bind_hass
-def set_fan_mode(hass, fan, entity_id=None):
- """Set all or specified climate devices fan mode on."""
- data = {ATTR_FAN_MODE: fan}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
-
-
-@bind_hass
-def set_operation_mode(hass, operation_mode, entity_id=None):
- """Set new target operation mode."""
- data = {ATTR_OPERATION_MODE: operation_mode}
-
- if entity_id is not None:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
-
-
-@bind_hass
-def set_swing_mode(hass, swing_mode, entity_id=None):
- """Set new target swing mode."""
- data = {ATTR_SWING_MODE: swing_mode}
-
- if entity_id is not None:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
-
-
async def async_setup(hass, config):
"""Set up climate devices."""
component = hass.data[DOMAIN] = \
diff --git a/homeassistant/components/climate/evohome.py b/homeassistant/components/climate/evohome.py
new file mode 100644
index 00000000000..f0631228fd8
--- /dev/null
+++ b/homeassistant/components/climate/evohome.py
@@ -0,0 +1,371 @@
+"""Support for Honeywell evohome (EMEA/EU-based systems only).
+
+Support for a temperature control system (TCS, controller) with 0+ heating
+zones (e.g. TRVs, relays) and, optionally, a DHW controller.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/climate.evohome/
+"""
+
+from datetime import datetime, timedelta
+import logging
+
+from requests.exceptions import HTTPError
+
+from homeassistant.components.climate import (
+ ClimateDevice,
+ STATE_AUTO,
+ STATE_ECO,
+ STATE_OFF,
+ SUPPORT_OPERATION_MODE,
+ SUPPORT_AWAY_MODE,
+)
+from homeassistant.components.evohome import (
+ CONF_LOCATION_IDX,
+ DATA_EVOHOME,
+ MAX_TEMP,
+ MIN_TEMP,
+ SCAN_INTERVAL_MAX
+)
+from homeassistant.const import (
+ CONF_SCAN_INTERVAL,
+ PRECISION_TENTHS,
+ TEMP_CELSIUS,
+ HTTP_TOO_MANY_REQUESTS,
+)
+_LOGGER = logging.getLogger(__name__)
+
+# these are for the controller's opmode/state and the zone's state
+EVO_RESET = 'AutoWithReset'
+EVO_AUTO = 'Auto'
+EVO_AUTOECO = 'AutoWithEco'
+EVO_AWAY = 'Away'
+EVO_DAYOFF = 'DayOff'
+EVO_CUSTOM = 'Custom'
+EVO_HEATOFF = 'HeatingOff'
+
+EVO_STATE_TO_HA = {
+ EVO_RESET: STATE_AUTO,
+ EVO_AUTO: STATE_AUTO,
+ EVO_AUTOECO: STATE_ECO,
+ EVO_AWAY: STATE_AUTO,
+ EVO_DAYOFF: STATE_AUTO,
+ EVO_CUSTOM: STATE_AUTO,
+ EVO_HEATOFF: STATE_OFF
+}
+
+HA_STATE_TO_EVO = {
+ STATE_AUTO: EVO_AUTO,
+ STATE_ECO: EVO_AUTOECO,
+ STATE_OFF: EVO_HEATOFF
+}
+
+HA_OP_LIST = list(HA_STATE_TO_EVO)
+
+# these are used to help prevent E501 (line too long) violations
+GWS = 'gateways'
+TCS = 'temperatureControlSystems'
+
+# debug codes - these happen occasionally, but the cause is unknown
+EVO_DEBUG_NO_RECENT_UPDATES = '0x01'
+EVO_DEBUG_NO_STATUS = '0x02'
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Create a Honeywell (EMEA/EU) evohome CH/DHW system.
+
+ An evohome system consists of: a controller, with 0-12 heating zones (e.g.
+ TRVs, relays) and, optionally, a DHW controller (a HW boiler).
+
+ Here, we add the controller only.
+ """
+ evo_data = hass.data[DATA_EVOHOME]
+
+ client = evo_data['client']
+ loc_idx = evo_data['params'][CONF_LOCATION_IDX]
+
+ # evohomeclient has no defined way of accessing non-default location other
+ # than using a protected member, such as below
+ tcs_obj_ref = client.locations[loc_idx]._gateways[0]._control_systems[0] # noqa E501; pylint: disable=protected-access
+
+ _LOGGER.debug(
+ "setup_platform(): Found Controller: id: %s [%s], type: %s",
+ tcs_obj_ref.systemId,
+ tcs_obj_ref.location.name,
+ tcs_obj_ref.modelType
+ )
+ parent = EvoController(evo_data, client, tcs_obj_ref)
+ add_entities([parent], update_before_add=True)
+
+
+class EvoController(ClimateDevice):
+ """Base for a Honeywell evohome hub/Controller device.
+
+ The Controller (aka TCS, temperature control system) is the parent of all
+ the child (CH/DHW) devices.
+ """
+
+ def __init__(self, evo_data, client, obj_ref):
+ """Initialize the evohome entity.
+
+ Most read-only properties are set here. So are pseudo read-only,
+ for example name (which _could_ change between update()s).
+ """
+ self.client = client
+ self._obj = obj_ref
+
+ self._id = obj_ref.systemId
+ self._name = evo_data['config']['locationInfo']['name']
+
+ self._config = evo_data['config'][GWS][0][TCS][0]
+ self._params = evo_data['params']
+ self._timers = evo_data['timers']
+
+ self._timers['statusUpdated'] = datetime.min
+ self._status = {}
+
+ self._available = False # should become True after first update()
+
+ def _handle_requests_exceptions(self, err):
+ # evohomeclient v2 api (>=0.2.7) exposes requests exceptions, incl.:
+ # - HTTP_BAD_REQUEST, is usually Bad user credentials
+ # - HTTP_TOO_MANY_REQUESTS, is api usuage limit exceeded
+ # - HTTP_SERVICE_UNAVAILABLE, is often Vendor's fault
+
+ if err.response.status_code == HTTP_TOO_MANY_REQUESTS:
+ # execute a back off: pause, and reduce rate
+ old_scan_interval = self._params[CONF_SCAN_INTERVAL]
+ new_scan_interval = min(old_scan_interval * 2, SCAN_INTERVAL_MAX)
+ self._params[CONF_SCAN_INTERVAL] = new_scan_interval
+
+ _LOGGER.warning(
+ "API rate limit has been exceeded: increasing '%s' from %s to "
+ "%s seconds, and suspending polling for %s seconds.",
+ CONF_SCAN_INTERVAL,
+ old_scan_interval,
+ new_scan_interval,
+ new_scan_interval * 3
+ )
+
+ self._timers['statusUpdated'] = datetime.now() + \
+ timedelta(seconds=new_scan_interval * 3)
+
+ else:
+ raise err
+
+ @property
+ def name(self):
+ """Return the name to use in the frontend UI."""
+ return self._name
+
+ @property
+ def available(self):
+ """Return True if the device is available.
+
+ All evohome entities are initially unavailable. Once HA has started,
+ state data is then retrieved by the Controller, and then the children
+ will get a state (e.g. operating_mode, current_temperature).
+
+ However, evohome entities can become unavailable for other reasons.
+ """
+ return self._available
+
+ @property
+ def supported_features(self):
+ """Get the list of supported features of the Controller."""
+ return SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE
+
+ @property
+ def device_state_attributes(self):
+ """Return the device state attributes of the controller.
+
+ This is operating mode state data that is not available otherwise, due
+ to the restrictions placed upon ClimateDevice properties, etc by HA.
+ """
+ data = {}
+ data['systemMode'] = self._status['systemModeStatus']['mode']
+ data['isPermanent'] = self._status['systemModeStatus']['isPermanent']
+ if 'timeUntil' in self._status['systemModeStatus']:
+ data['timeUntil'] = self._status['systemModeStatus']['timeUntil']
+ data['activeFaults'] = self._status['activeFaults']
+ return data
+
+ @property
+ def operation_list(self):
+ """Return the list of available operations."""
+ return HA_OP_LIST
+
+ @property
+ def current_operation(self):
+ """Return the operation mode of the evohome entity."""
+ return EVO_STATE_TO_HA.get(self._status['systemModeStatus']['mode'])
+
+ @property
+ def target_temperature(self):
+ """Return the average target temperature of the Heating/DHW zones."""
+ temps = [zone['setpointStatus']['targetHeatTemperature']
+ for zone in self._status['zones']]
+
+ avg_temp = round(sum(temps) / len(temps), 1) if temps else None
+ return avg_temp
+
+ @property
+ def current_temperature(self):
+ """Return the average current temperature of the Heating/DHW zones."""
+ tmp_list = [x for x in self._status['zones']
+ if x['temperatureStatus']['isAvailable'] is True]
+ temps = [zone['temperatureStatus']['temperature'] for zone in tmp_list]
+
+ avg_temp = round(sum(temps) / len(temps), 1) if temps else None
+ return avg_temp
+
+ @property
+ def temperature_unit(self):
+ """Return the temperature unit to use in the frontend UI."""
+ return TEMP_CELSIUS
+
+ @property
+ def precision(self):
+ """Return the temperature precision to use in the frontend UI."""
+ return PRECISION_TENTHS
+
+ @property
+ def min_temp(self):
+ """Return the minimum target temp (setpoint) of a evohome entity."""
+ return MIN_TEMP
+
+ @property
+ def max_temp(self):
+ """Return the maximum target temp (setpoint) of a evohome entity."""
+ return MAX_TEMP
+
+ @property
+ def is_on(self):
+ """Return true as evohome controllers are always on.
+
+ Operating modes can include 'HeatingOff', but (for example) DHW would
+ remain on.
+ """
+ return True
+
+ @property
+ def is_away_mode_on(self):
+ """Return true if away mode is on."""
+ return self._status['systemModeStatus']['mode'] == EVO_AWAY
+
+ def turn_away_mode_on(self):
+ """Turn away mode on."""
+ self._set_operation_mode(EVO_AWAY)
+
+ def turn_away_mode_off(self):
+ """Turn away mode off."""
+ self._set_operation_mode(EVO_AUTO)
+
+ def _set_operation_mode(self, operation_mode):
+ # Set new target operation mode for the TCS.
+ _LOGGER.debug(
+ "_set_operation_mode(): API call [1 request(s)]: "
+ "tcs._set_status(%s)...",
+ operation_mode
+ )
+ try:
+ self._obj._set_status(operation_mode) # noqa: E501; pylint: disable=protected-access
+ except HTTPError as err:
+ self._handle_requests_exceptions(err)
+
+ def set_operation_mode(self, operation_mode):
+ """Set new target operation mode for the TCS.
+
+ Currently limited to 'Auto', 'AutoWithEco' & 'HeatingOff'. If 'Away'
+ mode is needed, it can be enabled via turn_away_mode_on method.
+ """
+ self._set_operation_mode(HA_STATE_TO_EVO.get(operation_mode))
+
+ def _update_state_data(self, evo_data):
+ client = evo_data['client']
+ loc_idx = evo_data['params'][CONF_LOCATION_IDX]
+
+ _LOGGER.debug(
+ "_update_state_data(): API call [1 request(s)]: "
+ "client.locations[loc_idx].status()..."
+ )
+
+ try:
+ evo_data['status'].update(
+ client.locations[loc_idx].status()[GWS][0][TCS][0])
+ except HTTPError as err: # check if we've exceeded the api rate limit
+ self._handle_requests_exceptions(err)
+ else:
+ evo_data['timers']['statusUpdated'] = datetime.now()
+
+ _LOGGER.debug(
+ "_update_state_data(): evo_data['status'] = %s",
+ evo_data['status']
+ )
+
+ def update(self):
+ """Get the latest state data of the installation.
+
+ This includes state data for the Controller and its child devices, such
+ as the operating_mode of the Controller and the current_temperature
+ of its children.
+
+ This is not asyncio-friendly due to the underlying client api.
+ """
+ evo_data = self.hass.data[DATA_EVOHOME]
+
+ timeout = datetime.now() + timedelta(seconds=55)
+ expired = timeout > self._timers['statusUpdated'] + \
+ timedelta(seconds=evo_data['params'][CONF_SCAN_INTERVAL])
+
+ if not expired:
+ return
+
+ was_available = self._available or \
+ self._timers['statusUpdated'] == datetime.min
+
+ self._update_state_data(evo_data)
+ self._status = evo_data['status']
+
+ if _LOGGER.isEnabledFor(logging.DEBUG):
+ tmp_dict = dict(self._status)
+ if 'zones' in tmp_dict:
+ tmp_dict['zones'] = '...'
+ if 'dhw' in tmp_dict:
+ tmp_dict['dhw'] = '...'
+
+ _LOGGER.debug(
+ "update(%s), self._status = %s",
+ self._id + " [" + self._name + "]",
+ tmp_dict
+ )
+
+ no_recent_updates = self._timers['statusUpdated'] < datetime.now() - \
+ timedelta(seconds=self._params[CONF_SCAN_INTERVAL] * 3.1)
+
+ if no_recent_updates:
+ self._available = False
+ debug_code = EVO_DEBUG_NO_RECENT_UPDATES
+
+ elif not self._status:
+ # unavailable because no status (but how? other than at startup?)
+ self._available = False
+ debug_code = EVO_DEBUG_NO_STATUS
+
+ else:
+ self._available = True
+
+ if not self._available and was_available:
+ # only warn if available went from True to False
+ _LOGGER.warning(
+ "The entity, %s, has become unavailable, debug code is: %s",
+ self._id + " [" + self._name + "]",
+ debug_code
+ )
+
+ elif self._available and not was_available:
+ # this isn't the first re-available (e.g. _after_ STARTUP)
+ _LOGGER.debug(
+ "The entity, %s, has become available",
+ self._id + " [" + self._name + "]"
+ )
diff --git a/homeassistant/components/climate/fritzbox.py b/homeassistant/components/climate/fritzbox.py
index 3eedb89a3b7..f2d13ee92f6 100644
--- a/homeassistant/components/climate/fritzbox.py
+++ b/homeassistant/components/climate/fritzbox.py
@@ -10,13 +10,15 @@ import requests
from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN
from homeassistant.components.fritzbox import (
- ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
+ ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_HOLIDAY_MODE,
+ ATTR_STATE_LOCKED, ATTR_STATE_SUMMER_MODE,
+ ATTR_STATE_WINDOW_OPEN)
from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import (
- ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
+ ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
DEPENDENCIES = ['fritzbox']
_LOGGER = logging.getLogger(__name__)
@@ -151,10 +153,21 @@ class FritzboxThermostat(ClimateDevice):
def device_state_attributes(self):
"""Return the device specific state attributes."""
attrs = {
+ ATTR_STATE_BATTERY_LOW: self._device.battery_low,
ATTR_STATE_DEVICE_LOCKED: self._device.device_lock,
ATTR_STATE_LOCKED: self._device.lock,
- ATTR_STATE_BATTERY_LOW: self._device.battery_low,
}
+
+ # the following attributes are available since fritzos 7
+ if self._device.battery_level is not None:
+ attrs[ATTR_BATTERY_LEVEL] = self._device.battery_level
+ if self._device.holiday_active is not None:
+ attrs[ATTR_STATE_HOLIDAY_MODE] = self._device.holiday_active
+ if self._device.summer_active is not None:
+ attrs[ATTR_STATE_SUMMER_MODE] = self._device.summer_active
+ if ATTR_STATE_WINDOW_OPEN is not None:
+ attrs[ATTR_STATE_WINDOW_OPEN] = self._device.window_open
+
return attrs
def update(self):
diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py
index 85879b8122a..258699ff90a 100644
--- a/homeassistant/components/climate/generic_thermostat.py
+++ b/homeassistant/components/climate/generic_thermostat.py
@@ -67,9 +67,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the generic thermostat platform."""
name = config.get(CONF_NAME)
heater_entity_id = config.get(CONF_HEATER)
@@ -147,12 +146,10 @@ class GenericThermostat(ClimateDevice):
if sensor_state and sensor_state.state != STATE_UNKNOWN:
self._async_update_temp(sensor_state)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Run when entity about to be added."""
# Check If we have an old state
- old_state = yield from async_get_last_state(self.hass,
- self.entity_id)
+ old_state = await async_get_last_state(self.hass, self.entity_id)
if old_state is not None:
# If we have no initial temperature, restore
if self._target_temp is None:
diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py
index 6d54695fa7a..c445a495073 100644
--- a/homeassistant/components/climate/honeywell.py
+++ b/homeassistant/components/climate/honeywell.py
@@ -20,7 +20,7 @@ from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT,
ATTR_TEMPERATURE, CONF_REGION)
-REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.5.2']
+REQUIREMENTS = ['evohomeclient==0.2.7', 'somecomfort==0.5.2']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/climate/mqtt.py b/homeassistant/components/climate/mqtt.py
index 9e227e002b5..79c49db7955 100644
--- a/homeassistant/components/climate/mqtt.py
+++ b/homeassistant/components/climate/mqtt.py
@@ -4,13 +4,12 @@ Support for MQTT climate devices.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.mqtt/
"""
-import asyncio
import logging
import voluptuous as vol
from homeassistant.core import callback
-from homeassistant.components import mqtt
+from homeassistant.components import mqtt, climate
from homeassistant.components.climate import (
STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice,
@@ -21,9 +20,13 @@ from homeassistant.components.climate import (
from homeassistant.const import (
STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE)
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE,
- CONF_PAYLOAD_NOT_AVAILABLE, MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN,
+ CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
+ MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
SPEED_HIGH)
@@ -126,13 +129,28 @@ PLATFORM_SCHEMA = SCHEMA_BASE.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up the MQTT climate devices."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT climate device through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT climate device dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add a MQTT climate device."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(climate.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities,
+ discovery_hash=None):
+ """Set up the MQTT climate devices."""
template_keys = (
CONF_POWER_STATE_TEMPLATE,
CONF_MODE_STATE_TEMPLATE,
@@ -194,11 +212,12 @@ def async_setup_platform(hass, config, async_add_entities,
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
config.get(CONF_MIN_TEMP),
- config.get(CONF_MAX_TEMP))
- ])
+ config.get(CONF_MAX_TEMP),
+ discovery_hash,
+ )])
-class MqttClimate(MqttAvailability, ClimateDevice):
+class MqttClimate(MqttAvailability, MqttDiscoveryUpdate, ClimateDevice):
"""Representation of an MQTT climate device."""
def __init__(self, hass, name, topic, value_templates, qos, retain,
@@ -207,10 +226,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
current_swing_mode, current_operation, aux, send_if_off,
payload_on, payload_off, availability_topic,
payload_available, payload_not_available,
- min_temp, max_temp):
+ min_temp, max_temp, discovery_hash):
"""Initialize the climate device."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self.hass = hass
self._name = name
self._topic = topic
@@ -235,11 +255,12 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._payload_off = payload_off
self._min_temp = min_temp
self._max_temp = max_temp
+ self._discovery_hash = discovery_hash
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle being added to home assistant."""
- yield from super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
@callback
def handle_current_temp_received(topic, payload, qos):
@@ -256,7 +277,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
_LOGGER.error("Could not parse temperature from %s", payload)
if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_CURRENT_TEMPERATURE_TOPIC],
handle_current_temp_received, self._qos)
@@ -274,7 +295,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_MODE_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_MODE_STATE_TOPIC],
handle_mode_received, self._qos)
@@ -293,7 +314,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
_LOGGER.error("Could not parse temperature from %s", payload)
if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_TEMPERATURE_STATE_TOPIC],
handle_temperature_received, self._qos)
@@ -312,7 +333,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_FAN_MODE_STATE_TOPIC],
handle_fan_mode_received, self._qos)
@@ -331,7 +352,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_SWING_MODE_STATE_TOPIC],
handle_swing_mode_received, self._qos)
@@ -357,7 +378,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_AWAY_MODE_STATE_TOPIC],
handle_away_mode_received, self._qos)
@@ -382,7 +403,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_AUX_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_AUX_STATE_TOPIC],
handle_aux_mode_received, self._qos)
@@ -397,7 +418,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
if self._topic[CONF_HOLD_STATE_TOPIC] is not None:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._topic[CONF_HOLD_STATE_TOPIC],
handle_hold_mode_received, self._qos)
@@ -466,12 +487,11 @@ class MqttClimate(MqttAvailability, ClimateDevice):
"""Return the list of available fan modes."""
return self._fan_list
- @asyncio.coroutine
- def async_set_temperature(self, **kwargs):
+ async def async_set_temperature(self, **kwargs):
"""Set new target temperatures."""
if kwargs.get(ATTR_OPERATION_MODE) is not None:
operation_mode = kwargs.get(ATTR_OPERATION_MODE)
- yield from self.async_set_operation_mode(operation_mode)
+ await self.async_set_operation_mode(operation_mode)
if kwargs.get(ATTR_TEMPERATURE) is not None:
if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is None:
@@ -485,8 +505,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_swing_mode(self, swing_mode):
+ async def async_set_swing_mode(self, swing_mode):
"""Set new swing mode."""
if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish(
@@ -497,8 +516,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._current_swing_mode = swing_mode
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_fan_mode(self, fan_mode):
+ async def async_set_fan_mode(self, fan_mode):
"""Set new target temperature."""
if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish(
@@ -509,8 +527,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._current_fan_mode = fan_mode
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_operation_mode(self, operation_mode) -> None:
+ async def async_set_operation_mode(self, operation_mode) -> None:
"""Set new operation mode."""
if self._topic[CONF_POWER_COMMAND_TOPIC] is not None:
if (self._current_operation == STATE_OFF and
@@ -543,8 +560,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
"""List of available swing modes."""
return self._swing_list
- @asyncio.coroutine
- def async_turn_away_mode_on(self):
+ async def async_turn_away_mode_on(self):
"""Turn away mode on."""
if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
@@ -555,8 +571,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._away = True
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_turn_away_mode_off(self):
+ async def async_turn_away_mode_off(self):
"""Turn away mode off."""
if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
@@ -567,8 +582,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._away = False
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_hold_mode(self, hold_mode):
+ async def async_set_hold_mode(self, hold_mode):
"""Update hold mode on."""
if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass,
@@ -579,8 +593,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._hold = hold_mode
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_turn_aux_heat_on(self):
+ async def async_turn_aux_heat_on(self):
"""Turn auxiliary heater on."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
@@ -590,8 +603,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self._aux = True
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_turn_aux_heat_off(self):
+ async def async_turn_aux_heat_off(self):
"""Turn auxiliary heater off."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py
index 14cd2a0f02e..f914b9b4762 100644
--- a/homeassistant/components/climate/radiotherm.py
+++ b/homeassistant/components/climate/radiotherm.py
@@ -4,7 +4,6 @@ Support for Radio Thermostat wifi-enabled home thermostats.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.radiotherm/
"""
-import asyncio
import datetime
import logging
@@ -145,8 +144,7 @@ class RadioThermostat(ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
# Set the time on the device. This shouldn't be in the
# constructor because it's a network call. We can't put it in
diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/climate/sensibo.py
index ef33ee8495e..8532c611d25 100644
--- a/homeassistant/components/climate/sensibo.py
+++ b/homeassistant/components/climate/sensibo.py
@@ -58,9 +58,8 @@ FIELD_TO_FLAG = {
}
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up Sensibo devices."""
import pysensibo
@@ -70,7 +69,7 @@ def async_setup_platform(hass, config, async_add_entities,
devices = []
try:
for dev in (
- yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)):
+ await client.async_get_devices(_INITIAL_FETCH_FIELDS)):
if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]:
devices.append(SensiboClimate(
client, dev, hass.config.units.temperature_unit))
@@ -82,8 +81,7 @@ def async_setup_platform(hass, config, async_add_entities,
if devices:
async_add_entities(devices)
- @asyncio.coroutine
- def async_assume_state(service):
+ async def async_assume_state(service):
"""Set state according to external service call.."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
if entity_ids:
@@ -94,12 +92,12 @@ def async_setup_platform(hass, config, async_add_entities,
update_tasks = []
for climate in target_climate:
- yield from climate.async_assume_state(
+ await climate.async_assume_state(
service.data.get(ATTR_STATE))
update_tasks.append(climate.async_update_ha_state(True))
if update_tasks:
- yield from asyncio.wait(update_tasks, loop=hass.loop)
+ await asyncio.wait(update_tasks, loop=hass.loop)
hass.services.async_register(
DOMAIN, SERVICE_ASSUME_STATE, async_assume_state,
schema=ASSUME_STATE_SCHEMA)
@@ -262,8 +260,7 @@ class SensiboClimate(ClimateDevice):
"""Return unique ID based on Sensibo ID."""
return self._id
- @asyncio.coroutine
- def async_set_temperature(self, **kwargs):
+ async def async_set_temperature(self, **kwargs):
"""Set new target temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None:
@@ -283,52 +280,46 @@ class SensiboClimate(ClimateDevice):
return
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id, 'targetTemperature', temperature, self._ac_states)
- @asyncio.coroutine
- def async_set_fan_mode(self, fan_mode):
+ async def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode."""
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id, 'fanLevel', fan_mode, self._ac_states)
- @asyncio.coroutine
- def async_set_operation_mode(self, operation_mode):
+ async def async_set_operation_mode(self, operation_mode):
"""Set new target operation mode."""
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id, 'mode', operation_mode, self._ac_states)
- @asyncio.coroutine
- def async_set_swing_mode(self, swing_mode):
+ async def async_set_swing_mode(self, swing_mode):
"""Set new target swing operation."""
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id, 'swing', swing_mode, self._ac_states)
- @asyncio.coroutine
- def async_turn_on(self):
+ async def async_turn_on(self):
"""Turn Sensibo unit on."""
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id, 'on', True, self._ac_states)
- @asyncio.coroutine
- def async_turn_off(self):
+ async def async_turn_off(self):
"""Turn Sensibo unit on."""
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id, 'on', False, self._ac_states)
- @asyncio.coroutine
- def async_assume_state(self, state):
+ async def async_assume_state(self, state):
"""Set external state."""
change_needed = (state != STATE_OFF and not self.is_on) \
or (state == STATE_OFF and self.is_on)
if change_needed:
with async_timeout.timeout(TIMEOUT):
- yield from self._client.async_set_ac_state_property(
+ await self._client.async_set_ac_state_property(
self._id,
'on',
state != STATE_OFF, # value
@@ -341,12 +332,11 @@ class SensiboClimate(ClimateDevice):
else:
self._external_state = state
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Retrieve latest state."""
try:
with async_timeout.timeout(TIMEOUT):
- data = yield from self._client.async_get_device(
+ data = await self._client.async_get_device(
self._id, _FETCH_FIELDS)
self._do_update(data)
except aiohttp.client_exceptions.ClientError:
diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py
index 3013a155380..cb6204d3ba3 100644
--- a/homeassistant/components/climate/wink.py
+++ b/homeassistant/components/climate/wink.py
@@ -4,7 +4,6 @@ Support for Wink thermostats, Air Conditioners, and Water Heaters.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/climate.wink/
"""
-import asyncio
import logging
from homeassistant.components.climate import (
@@ -92,8 +91,7 @@ class WinkThermostat(WinkDevice, ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS_THERMOSTAT
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['climate'].append(self)
diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py
index 33a939bf9d0..217b39aff62 100644
--- a/homeassistant/components/cloud/__init__.py
+++ b/homeassistant/components/cloud/__init__.py
@@ -92,8 +92,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize the Home Assistant cloud."""
if DOMAIN in config:
kwargs = dict(config[DOMAIN])
@@ -112,7 +111,7 @@ def async_setup(hass, config):
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
- yield from http_api.async_setup(hass)
+ await http_api.async_setup(hass)
return True
@@ -226,17 +225,16 @@ class Cloud:
'authorization': self.id_token
})
- @asyncio.coroutine
- def logout(self):
+ async def logout(self):
"""Close connection and remove all credentials."""
- yield from self.iot.disconnect()
+ await self.iot.disconnect()
self.id_token = None
self.access_token = None
self.refresh_token = None
self._gactions_config = None
- yield from self.hass.async_add_job(
+ await self.hass.async_add_job(
lambda: os.remove(self.user_info_path))
def write_user_info(self):
@@ -313,8 +311,7 @@ class Cloud:
self._prefs[STORAGE_ENABLE_ALEXA] = alexa_enabled
await self._store.async_save(self._prefs)
- @asyncio.coroutine
- def _fetch_jwt_keyset(self):
+ async def _fetch_jwt_keyset(self):
"""Fetch the JWT keyset for the Cognito instance."""
session = async_get_clientsession(self.hass)
url = ("https://cognito-idp.us-east-1.amazonaws.com/"
@@ -322,8 +319,8 @@ class Cloud:
try:
with async_timeout.timeout(10, loop=self.hass.loop):
- req = yield from session.get(url)
- self.jwt_keyset = yield from req.json()
+ req = await session.get(url)
+ self.jwt_keyset = await req.json()
return True
diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py
index c81ec38bace..720ca00cf52 100644
--- a/homeassistant/components/cloud/http_api.py
+++ b/homeassistant/components/cloud/http_api.py
@@ -231,7 +231,7 @@ def websocket_cloud_status(hass, connection, msg):
Async friendly.
"""
cloud = hass.data[DOMAIN]
- connection.to_write.put_nowait(
+ connection.send_message(
websocket_api.result_message(msg['id'], _account_data(cloud)))
@@ -241,7 +241,7 @@ async def websocket_subscription(hass, connection, msg):
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
- connection.to_write.put_nowait(websocket_api.error_message(
+ connection.send_message(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
@@ -250,10 +250,10 @@ async def websocket_subscription(hass, connection, msg):
response = await cloud.fetch_subscription_info()
if response.status == 200:
- connection.send_message_outside(websocket_api.result_message(
+ connection.send_message(websocket_api.result_message(
msg['id'], await response.json()))
else:
- connection.send_message_outside(websocket_api.error_message(
+ connection.send_message(websocket_api.error_message(
msg['id'], 'request_failed', 'Failed to request subscription'))
@@ -263,7 +263,7 @@ async def websocket_update_prefs(hass, connection, msg):
cloud = hass.data[DOMAIN]
if not cloud.is_logged_in:
- connection.to_write.put_nowait(websocket_api.error_message(
+ connection.send_message(websocket_api.error_message(
msg['id'], 'not_logged_in',
'You need to be logged in to the cloud.'))
return
@@ -273,7 +273,7 @@ async def websocket_update_prefs(hass, connection, msg):
changes.pop('type')
await cloud.update_preferences(**changes)
- connection.send_message_outside(websocket_api.result_message(
+ connection.send_message(websocket_api.result_message(
msg['id'], {'success': True}))
diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py
index fd525ed33a8..fe89c263488 100644
--- a/homeassistant/components/cloud/iot.py
+++ b/homeassistant/components/cloud/iot.py
@@ -79,7 +79,7 @@ class CloudIoT:
try:
# Sleep 2^tries seconds between retries
- self.retry_task = hass.async_add_job(asyncio.sleep(
+ self.retry_task = hass.async_create_task(asyncio.sleep(
2**min(9, self.tries), loop=hass.loop))
yield from self.retry_task
self.retry_task = None
@@ -106,7 +106,7 @@ class CloudIoT:
'cloud_subscription_expired')
# Don't await it because it will cancel this task
- hass.async_add_job(self.cloud.logout())
+ hass.async_create_task(self.cloud.logout())
return
except auth_api.CloudError as err:
_LOGGER.warning("Unable to refresh token: %s", err)
diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py
index df0e2f13ac1..f2cfff1f342 100644
--- a/homeassistant/components/config/__init__.py
+++ b/homeassistant/components/config/__init__.py
@@ -52,7 +52,7 @@ async def async_setup(hass, config):
"""Respond to components being loaded."""
panel_name = event.data.get(ATTR_COMPONENT)
if panel_name in ON_DEMAND:
- hass.async_add_job(setup_panel(panel_name))
+ hass.async_create_task(setup_panel(panel_name))
hass.bus.async_listen(EVENT_COMPONENT_LOADED, component_loaded)
@@ -136,7 +136,7 @@ class BaseEditConfigView(HomeAssistantView):
await hass.async_add_job(_write, path, current)
if self.post_write_hook is not None:
- hass.async_add_job(self.post_write_hook(hass))
+ hass.async_create_task(self.post_write_hook(hass))
return self.json({
'result': 'ok',
diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py
index 6f00b03dedb..fb60b4075ef 100644
--- a/homeassistant/components/config/auth.py
+++ b/homeassistant/components/config/auth.py
@@ -1,7 +1,6 @@
"""Offer API to configure Home Assistant auth."""
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.components import websocket_api
@@ -40,61 +39,49 @@ async def async_setup(hass):
return True
-@callback
@websocket_api.require_owner
-def websocket_list(hass, connection, msg):
+@websocket_api.async_response
+async def websocket_list(hass, connection, msg):
"""Return a list of users."""
- async def send_users():
- """Send users."""
- result = [_user_info(u) for u in await hass.auth.async_get_users()]
+ result = [_user_info(u) for u in await hass.auth.async_get_users()]
- connection.send_message_outside(
- websocket_api.result_message(msg['id'], result))
-
- hass.async_add_job(send_users())
+ connection.send_message(
+ websocket_api.result_message(msg['id'], result))
-@callback
@websocket_api.require_owner
-def websocket_delete(hass, connection, msg):
+@websocket_api.async_response
+async def websocket_delete(hass, connection, msg):
"""Delete a user."""
- async def delete_user():
- """Delete user."""
- if msg['user_id'] == connection.request.get('hass_user').id:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'no_delete_self',
- 'Unable to delete your own account'))
- return
+ if msg['user_id'] == connection.user.id:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'no_delete_self',
+ 'Unable to delete your own account'))
+ return
- user = await hass.auth.async_get_user(msg['user_id'])
+ user = await hass.auth.async_get_user(msg['user_id'])
- if not user:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'not_found', 'User not found'))
- return
+ if not user:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'not_found', 'User not found'))
+ return
- await hass.auth.async_remove_user(user)
+ await hass.auth.async_remove_user(user)
- connection.send_message_outside(
- websocket_api.result_message(msg['id']))
-
- hass.async_add_job(delete_user())
+ connection.send_message(
+ websocket_api.result_message(msg['id']))
-@callback
@websocket_api.require_owner
-def websocket_create(hass, connection, msg):
+@websocket_api.async_response
+async def websocket_create(hass, connection, msg):
"""Create a user."""
- async def create_user():
- """Create a user."""
- user = await hass.auth.async_create_user(msg['name'])
+ user = await hass.auth.async_create_user(msg['name'])
- connection.send_message_outside(
- websocket_api.result_message(msg['id'], {
- 'user': _user_info(user)
- }))
-
- hass.async_add_job(create_user())
+ connection.send_message(
+ websocket_api.result_message(msg['id'], {
+ 'user': _user_info(user)
+ }))
def _user_info(user):
diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py
index 960e8f5e7b4..3495a959f49 100644
--- a/homeassistant/components/config/auth_provider_homeassistant.py
+++ b/homeassistant/components/config/auth_provider_homeassistant.py
@@ -2,8 +2,8 @@
import voluptuous as vol
from homeassistant.auth.providers import homeassistant as auth_ha
-from homeassistant.core import callback
from homeassistant.components import websocket_api
+from homeassistant.components.websocket_api.decorators import require_owner
WS_TYPE_CREATE = 'config/auth_provider/homeassistant/create'
@@ -54,121 +54,109 @@ def _get_provider(hass):
raise RuntimeError('Provider not found')
-@callback
-@websocket_api.require_owner
-def websocket_create(hass, connection, msg):
+@require_owner
+@websocket_api.async_response
+async def websocket_create(hass, connection, msg):
"""Create credentials and attach to a user."""
- async def create_creds():
- """Create credentials."""
- provider = _get_provider(hass)
- await provider.async_initialize()
+ provider = _get_provider(hass)
+ await provider.async_initialize()
- user = await hass.auth.async_get_user(msg['user_id'])
+ user = await hass.auth.async_get_user(msg['user_id'])
- if user is None:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'not_found', 'User not found'))
- return
+ if user is None:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'not_found', 'User not found'))
+ return
- if user.system_generated:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'system_generated',
- 'Cannot add credentials to a system generated user.'))
- return
-
- try:
- await hass.async_add_executor_job(
- provider.data.add_auth, msg['username'], msg['password'])
- except auth_ha.InvalidUser:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'username_exists', 'Username already exists'))
- return
-
- credentials = await provider.async_get_or_create_credentials({
- 'username': msg['username']
- })
- await hass.auth.async_link_user(user, credentials)
-
- await provider.data.async_save()
- connection.to_write.put_nowait(websocket_api.result_message(msg['id']))
-
- hass.async_add_job(create_creds())
-
-
-@callback
-@websocket_api.require_owner
-def websocket_delete(hass, connection, msg):
- """Delete username and related credential."""
- async def delete_creds():
- """Delete user credentials."""
- provider = _get_provider(hass)
- await provider.async_initialize()
-
- credentials = await provider.async_get_or_create_credentials({
- 'username': msg['username']
- })
-
- # if not new, an existing credential exists.
- # Removing the credential will also remove the auth.
- if not credentials.is_new:
- await hass.auth.async_remove_credentials(credentials)
-
- connection.to_write.put_nowait(
- websocket_api.result_message(msg['id']))
- return
-
- try:
- provider.data.async_remove_auth(msg['username'])
- await provider.data.async_save()
- except auth_ha.InvalidUser:
- connection.to_write.put_nowait(websocket_api.error_message(
- msg['id'], 'auth_not_found', 'Given username was not found.'))
- return
-
- connection.to_write.put_nowait(
- websocket_api.result_message(msg['id']))
-
- hass.async_add_job(delete_creds())
-
-
-@callback
-def websocket_change_password(hass, connection, msg):
- """Change user password."""
- async def change_password():
- """Change user password."""
- user = connection.request.get('hass_user')
- if user is None:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'user_not_found', 'User not found'))
- return
-
- provider = _get_provider(hass)
- await provider.async_initialize()
-
- username = None
- for credential in user.credentials:
- if credential.auth_provider_type == provider.type:
- username = credential.data['username']
- break
-
- if username is None:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'credentials_not_found', 'Credentials not found'))
- return
-
- try:
- await provider.async_validate_login(
- username, msg['current_password'])
- except auth_ha.InvalidAuth:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'invalid_password', 'Invalid password'))
- return
+ if user.system_generated:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'system_generated',
+ 'Cannot add credentials to a system generated user.'))
+ return
+ try:
await hass.async_add_executor_job(
- provider.data.change_password, username, msg['new_password'])
- await provider.data.async_save()
+ provider.data.add_auth, msg['username'], msg['password'])
+ except auth_ha.InvalidUser:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'username_exists', 'Username already exists'))
+ return
- connection.send_message_outside(
+ credentials = await provider.async_get_or_create_credentials({
+ 'username': msg['username']
+ })
+ await hass.auth.async_link_user(user, credentials)
+
+ await provider.data.async_save()
+ connection.send_message(websocket_api.result_message(msg['id']))
+
+
+@require_owner
+@websocket_api.async_response
+async def websocket_delete(hass, connection, msg):
+ """Delete username and related credential."""
+ provider = _get_provider(hass)
+ await provider.async_initialize()
+
+ credentials = await provider.async_get_or_create_credentials({
+ 'username': msg['username']
+ })
+
+ # if not new, an existing credential exists.
+ # Removing the credential will also remove the auth.
+ if not credentials.is_new:
+ await hass.auth.async_remove_credentials(credentials)
+
+ connection.send_message(
websocket_api.result_message(msg['id']))
+ return
- hass.async_add_job(change_password())
+ try:
+ provider.data.async_remove_auth(msg['username'])
+ await provider.data.async_save()
+ except auth_ha.InvalidUser:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'auth_not_found', 'Given username was not found.'))
+ return
+
+ connection.send_message(
+ websocket_api.result_message(msg['id']))
+
+
+@websocket_api.async_response
+async def websocket_change_password(hass, connection, msg):
+ """Change user password."""
+ user = connection.user
+ if user is None:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'user_not_found', 'User not found'))
+ return
+
+ provider = _get_provider(hass)
+ await provider.async_initialize()
+
+ username = None
+ for credential in user.credentials:
+ if credential.auth_provider_type == provider.type:
+ username = credential.data['username']
+ break
+
+ if username is None:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'credentials_not_found', 'Credentials not found'))
+ return
+
+ try:
+ await provider.async_validate_login(
+ username, msg['current_password'])
+ except auth_ha.InvalidAuth:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'invalid_password', 'Invalid password'))
+ return
+
+ await hass.async_add_executor_job(
+ provider.data.change_password, username, msg['new_password'])
+ await provider.data.async_save()
+
+ connection.send_message(
+ websocket_api.result_message(msg['id']))
diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py
index 223159eb415..7836cba6cf9 100644
--- a/homeassistant/components/config/automation.py
+++ b/homeassistant/components/config/automation.py
@@ -1,24 +1,25 @@
"""Provide configuration end points for Automations."""
-import asyncio
from collections import OrderedDict
import uuid
-from homeassistant.const import CONF_ID
from homeassistant.components.config import EditIdBasedConfigView
-from homeassistant.components.automation import (
- PLATFORM_SCHEMA, DOMAIN, async_reload)
+from homeassistant.const import CONF_ID, SERVICE_RELOAD
+from homeassistant.components.automation import DOMAIN, PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'automations.yaml'
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the Automation config API."""
+ async def hook(hass):
+ """post_write_hook for Config View that reloads automations."""
+ await hass.services.async_call(DOMAIN, SERVICE_RELOAD)
+
hass.http.register_view(EditAutomationConfigView(
DOMAIN, 'config', CONFIG_PATH, cv.string,
- PLATFORM_SCHEMA, post_write_hook=async_reload
+ PLATFORM_SCHEMA, post_write_hook=hook
))
return True
diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py
index 73b2767be4b..644990d7185 100644
--- a/homeassistant/components/config/config_entries.py
+++ b/homeassistant/components/config/config_entries.py
@@ -1,5 +1,4 @@
"""Http views to control the config manager."""
-import asyncio
from homeassistant import config_entries, data_entry_flow
from homeassistant.components.http import HomeAssistantView
@@ -7,8 +6,7 @@ from homeassistant.helpers.data_entry_flow import (
FlowManagerIndexView, FlowManagerResourceView)
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Enable the Home Assistant views."""
hass.http.register_view(ConfigManagerEntryIndexView)
hass.http.register_view(ConfigManagerEntryResourceView)
@@ -44,8 +42,7 @@ class ConfigManagerEntryIndexView(HomeAssistantView):
url = '/api/config/config_entries/entry'
name = 'api:config:config_entries:entry'
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""List flows in progress."""
hass = request.app['hass']
return self.json([{
@@ -64,13 +61,12 @@ class ConfigManagerEntryResourceView(HomeAssistantView):
url = '/api/config/config_entries/entry/{entry_id}'
name = 'api:config:config_entries:entry:resource'
- @asyncio.coroutine
- def delete(self, request, entry_id):
+ async def delete(self, request, entry_id):
"""Delete a config entry."""
hass = request.app['hass']
try:
- result = yield from hass.config_entries.async_remove(entry_id)
+ result = await hass.config_entries.async_remove(entry_id)
except config_entries.UnknownEntry:
return self.json_message('Invalid entry specified', 404)
@@ -83,8 +79,7 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView):
url = '/api/config/config_entries/flow'
name = 'api:config:config_entries:flow'
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""List flows that are in progress but not started by a user.
Example of a non-user initiated flow is a discovered Hue hub that
@@ -110,7 +105,6 @@ class ConfigManagerAvailableFlowView(HomeAssistantView):
url = '/api/config/config_entries/flow_handlers'
name = 'api:config:config_entries:flow_handlers'
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""List available flow handlers."""
return self.json(config_entries.FLOWS)
diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py
index 4ff530ad2bc..ce7675c41f4 100644
--- a/homeassistant/components/config/core.py
+++ b/homeassistant/components/config/core.py
@@ -1,12 +1,10 @@
"""Component to interact with Hassbian tools."""
-import asyncio
from homeassistant.components.http import HomeAssistantView
from homeassistant.config import async_check_ha_config_file
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the Hassbian config."""
hass.http.register_view(CheckConfigView)
return True
@@ -18,10 +16,9 @@ class CheckConfigView(HomeAssistantView):
url = '/api/config/core/check_config'
name = 'api:config:core:check_config'
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Validate configuration and return results."""
- errors = yield from async_check_ha_config_file(request.app['hass'])
+ errors = await async_check_ha_config_file(request.app['hass'])
state = 'invalid' if errors else 'valid'
diff --git a/homeassistant/components/config/customize.py b/homeassistant/components/config/customize.py
index d25992ecc90..b7a8c9c070a 100644
--- a/homeassistant/components/config/customize.py
+++ b/homeassistant/components/config/customize.py
@@ -1,21 +1,24 @@
"""Provide configuration end points for Customize."""
-import asyncio
from homeassistant.components.config import EditKeyBasedConfigView
-from homeassistant.components import async_reload_core_config
+from homeassistant.components import SERVICE_RELOAD_CORE_CONFIG
from homeassistant.config import DATA_CUSTOMIZE
+from homeassistant.core import DOMAIN
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'customize.yaml'
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the Customize config API."""
+ async def hook(hass):
+ """post_write_hook for Config View that reloads groups."""
+ await hass.services.async_call(DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
+
hass.http.register_view(CustomizeConfigView(
'customize', 'config', CONFIG_PATH, cv.entity_id, dict,
- post_write_hook=async_reload_core_config
+ post_write_hook=hook
))
return True
diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py
index 88aa5727a97..ecbac703296 100644
--- a/homeassistant/components/config/device_registry.py
+++ b/homeassistant/components/config/device_registry.py
@@ -1,7 +1,6 @@
"""HTTP views to interact with the device registry."""
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.helpers.device_registry import async_get_registry
from homeassistant.components import websocket_api
@@ -22,26 +21,19 @@ async def async_setup(hass):
return True
-@callback
-def websocket_list_devices(hass, connection, msg):
- """Handle list devices command.
-
- Async friendly.
- """
- async def retrieve_entities():
- """Get devices from registry."""
- registry = await async_get_registry(hass)
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], [{
- 'config_entries': list(entry.config_entries),
- 'connections': list(entry.connections),
- 'manufacturer': entry.manufacturer,
- 'model': entry.model,
- 'name': entry.name,
- 'sw_version': entry.sw_version,
- 'id': entry.id,
- 'hub_device_id': entry.hub_device_id,
- } for entry in registry.devices.values()]
- ))
-
- hass.async_add_job(retrieve_entities())
+@websocket_api.async_response
+async def websocket_list_devices(hass, connection, msg):
+ """Handle list devices command."""
+ registry = await async_get_registry(hass)
+ connection.send_message(websocket_api.result_message(
+ msg['id'], [{
+ 'config_entries': list(entry.config_entries),
+ 'connections': list(entry.connections),
+ 'manufacturer': entry.manufacturer,
+ 'model': entry.model,
+ 'name': entry.name,
+ 'sw_version': entry.sw_version,
+ 'id': entry.id,
+ 'hub_device_id': entry.hub_device_id,
+ } for entry in registry.devices.values()]
+ ))
diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py
index 0f9abf167e5..1ede76d0fd8 100644
--- a/homeassistant/components/config/entity_registry.py
+++ b/homeassistant/components/config/entity_registry.py
@@ -4,6 +4,8 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.helpers.entity_registry import async_get_registry
from homeassistant.components import websocket_api
+from homeassistant.components.websocket_api.const import ERR_NOT_FOUND
+from homeassistant.components.websocket_api.decorators import async_response
from homeassistant.helpers import config_validation as cv
DEPENDENCIES = ['websocket_api']
@@ -46,89 +48,77 @@ async def async_setup(hass):
return True
-@callback
-def websocket_list_entities(hass, connection, msg):
+@async_response
+async def websocket_list_entities(hass, connection, msg):
"""Handle list registry entries command.
Async friendly.
"""
- async def retrieve_entities():
- """Get entities from registry."""
- registry = await async_get_registry(hass)
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], [{
- 'config_entry_id': entry.config_entry_id,
- 'device_id': entry.device_id,
- 'disabled_by': entry.disabled_by,
- 'entity_id': entry.entity_id,
- 'name': entry.name,
- 'platform': entry.platform,
- } for entry in registry.entities.values()]
- ))
-
- hass.async_add_job(retrieve_entities())
+ registry = await async_get_registry(hass)
+ connection.send_message(websocket_api.result_message(
+ msg['id'], [{
+ 'config_entry_id': entry.config_entry_id,
+ 'device_id': entry.device_id,
+ 'disabled_by': entry.disabled_by,
+ 'entity_id': entry.entity_id,
+ 'name': entry.name,
+ 'platform': entry.platform,
+ } for entry in registry.entities.values()]
+ ))
-@callback
-def websocket_get_entity(hass, connection, msg):
+@async_response
+async def websocket_get_entity(hass, connection, msg):
"""Handle get entity registry entry command.
Async friendly.
"""
- async def retrieve_entity():
- """Get entity from registry."""
- registry = await async_get_registry(hass)
- entry = registry.entities.get(msg['entity_id'])
+ registry = await async_get_registry(hass)
+ entry = registry.entities.get(msg['entity_id'])
- if entry is None:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
- return
+ if entry is None:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], ERR_NOT_FOUND, 'Entity not found'))
+ return
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], _entry_dict(entry)
- ))
-
- hass.async_add_job(retrieve_entity())
+ connection.send_message(websocket_api.result_message(
+ msg['id'], _entry_dict(entry)
+ ))
-@callback
-def websocket_update_entity(hass, connection, msg):
+@async_response
+async def websocket_update_entity(hass, connection, msg):
"""Handle get camera thumbnail websocket command.
Async friendly.
"""
- async def update_entity():
- """Get entity from registry."""
- registry = await async_get_registry(hass)
+ registry = await async_get_registry(hass)
- if msg['entity_id'] not in registry.entities:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found'))
- return
+ if msg['entity_id'] not in registry.entities:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], ERR_NOT_FOUND, 'Entity not found'))
+ return
- changes = {}
+ changes = {}
- if 'name' in msg:
- changes['name'] = msg['name']
+ if 'name' in msg:
+ changes['name'] = msg['name']
- if 'new_entity_id' in msg:
- changes['new_entity_id'] = msg['new_entity_id']
+ if 'new_entity_id' in msg:
+ changes['new_entity_id'] = msg['new_entity_id']
- try:
- if changes:
- entry = registry.async_update_entity(
- msg['entity_id'], **changes)
- except ValueError as err:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'invalid_info', str(err)
- ))
- else:
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], _entry_dict(entry)
- ))
-
- hass.async_create_task(update_entity())
+ try:
+ if changes:
+ entry = registry.async_update_entity(
+ msg['entity_id'], **changes)
+ except ValueError as err:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'invalid_info', str(err)
+ ))
+ else:
+ connection.send_message(websocket_api.result_message(
+ msg['id'], _entry_dict(entry)
+ ))
@callback
diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py
index 8b327faa95f..f9b9a2c4918 100644
--- a/homeassistant/components/config/group.py
+++ b/homeassistant/components/config/group.py
@@ -1,5 +1,4 @@
"""Provide configuration end points for Groups."""
-import asyncio
from homeassistant.const import SERVICE_RELOAD
from homeassistant.components.config import EditKeyBasedConfigView
from homeassistant.components.group import DOMAIN, GROUP_SCHEMA
@@ -9,13 +8,11 @@ import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'groups.yaml'
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the Group config API."""
- @asyncio.coroutine
- def hook(hass):
+ async def hook(hass):
"""post_write_hook for Config View that reloads groups."""
- yield from hass.services.async_call(DOMAIN, SERVICE_RELOAD)
+ await hass.services.async_call(DOMAIN, SERVICE_RELOAD)
hass.http.register_view(EditKeyBasedConfigView(
'group', 'config', CONFIG_PATH, cv.slug, GROUP_SCHEMA,
diff --git a/homeassistant/components/config/hassbian.py b/homeassistant/components/config/hassbian.py
index 8de5f62d915..c475dc317f7 100644
--- a/homeassistant/components/config/hassbian.py
+++ b/homeassistant/components/config/hassbian.py
@@ -1,5 +1,4 @@
"""Component to interact with Hassbian tools."""
-import asyncio
import json
import os
@@ -30,8 +29,7 @@ _TEST_OUTPUT = """
""" # noqa
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the Hassbian config."""
# Test if is Hassbian
test_mode = 'FORCE_HASSBIAN' in os.environ
@@ -46,8 +44,7 @@ def async_setup(hass):
return True
-@asyncio.coroutine
-def hassbian_status(hass, test_mode=False):
+async def hassbian_status(hass, test_mode=False):
"""Query for the Hassbian status."""
# Fetch real output when not in test mode
if test_mode:
@@ -66,10 +63,9 @@ class HassbianSuitesView(HomeAssistantView):
"""Initialize suites view."""
self._test_mode = test_mode
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Request suite status."""
- inp = yield from hassbian_status(request.app['hass'], self._test_mode)
+ inp = await hassbian_status(request.app['hass'], self._test_mode)
return self.json(inp['suites'])
@@ -84,8 +80,7 @@ class HassbianSuiteInstallView(HomeAssistantView):
"""Initialize suite view."""
self._test_mode = test_mode
- @asyncio.coroutine
- def post(self, request, suite):
+ async def post(self, request, suite):
"""Request suite status."""
# do real install if not in test mode
return self.json({"status": "ok"})
diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py
index 345c8e4a849..3adc6f14233 100644
--- a/homeassistant/components/config/script.py
+++ b/homeassistant/components/config/script.py
@@ -1,19 +1,22 @@
"""Provide configuration end points for scripts."""
-import asyncio
from homeassistant.components.config import EditKeyBasedConfigView
-from homeassistant.components.script import SCRIPT_ENTRY_SCHEMA, async_reload
+from homeassistant.components.script import DOMAIN, SCRIPT_ENTRY_SCHEMA
+from homeassistant.const import SERVICE_RELOAD
import homeassistant.helpers.config_validation as cv
CONFIG_PATH = 'scripts.yaml'
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the script config API."""
+ async def hook(hass):
+ """post_write_hook for Config View that reloads scripts."""
+ await hass.services.async_call(DOMAIN, SERVICE_RELOAD)
+
hass.http.register_view(EditKeyBasedConfigView(
'script', 'config', CONFIG_PATH, cv.slug, SCRIPT_ENTRY_SCHEMA,
- post_write_hook=async_reload
+ post_write_hook=hook
))
return True
diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py
index fcdab835052..57123ee12de 100644
--- a/homeassistant/components/config/zwave.py
+++ b/homeassistant/components/config/zwave.py
@@ -1,5 +1,4 @@
"""Provide configuration end points for Z-Wave."""
-import asyncio
import logging
from collections import deque
@@ -16,8 +15,7 @@ CONFIG_PATH = 'zwave_device_config.yaml'
OZW_LOG_FILENAME = 'OZW_Log.txt'
-@asyncio.coroutine
-def async_setup(hass):
+async def async_setup(hass):
"""Set up the Z-Wave config API."""
hass.http.register_view(EditKeyBasedConfigView(
'zwave', 'device_config', CONFIG_PATH, cv.entity_id,
@@ -41,8 +39,7 @@ class ZWaveLogView(HomeAssistantView):
name = "api:zwave:ozwlog"
# pylint: disable=no-self-use
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Retrieve the lines from ZWave log."""
try:
lines = int(request.query.get('lines', 0))
@@ -50,7 +47,7 @@ class ZWaveLogView(HomeAssistantView):
return Response(text='Invalid datetime', status=400)
hass = request.app['hass']
- response = yield from hass.async_add_job(self._get_log, hass, lines)
+ response = await hass.async_add_job(self._get_log, hass, lines)
return Response(text='\n'.join(response))
diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py
index 56fb7b4247b..74d8339b1fa 100644
--- a/homeassistant/components/configurator.py
+++ b/homeassistant/components/configurator.py
@@ -6,7 +6,6 @@ This will return a request id that has to be used for future calls.
A callback has to be provided to `request_config` which will be called when
the user has submitted configuration information.
"""
-import asyncio
import functools as ft
import logging
@@ -122,8 +121,7 @@ def request_done(hass, request_id):
).result()
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the configurator component."""
return True
@@ -207,8 +205,7 @@ class Configurator:
self.hass.bus.async_listen_once(EVENT_TIME_CHANGED, deferred_remove)
- @asyncio.coroutine
- def async_handle_service_call(self, call):
+ async def async_handle_service_call(self, call):
"""Handle a configure service call."""
request_id = call.data.get(ATTR_CONFIGURE_ID)
@@ -220,8 +217,8 @@ class Configurator:
# field validation goes here?
if callback:
- yield from self.hass.async_add_job(callback,
- call.data.get(ATTR_FIELDS, {}))
+ await self.hass.async_add_job(callback,
+ call.data.get(ATTR_FIELDS, {}))
def _generate_unique_id(self):
"""Generate a unique configurator ID."""
diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py
index d720819a0ab..d67c93c0d6e 100644
--- a/homeassistant/components/counter/__init__.py
+++ b/homeassistant/components/counter/__init__.py
@@ -9,12 +9,10 @@ import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
-from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
-from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__)
@@ -22,6 +20,7 @@ ATTR_INITIAL = 'initial'
ATTR_STEP = 'step'
CONF_INITIAL = 'initial'
+CONF_RESTORE = 'restore'
CONF_STEP = 'step'
DEFAULT_INITIAL = 0
@@ -45,54 +44,13 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL):
cv.positive_int,
vol.Optional(CONF_NAME): cv.string,
+ vol.Optional(CONF_RESTORE, default=True): cv.boolean,
vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
}, None)
})
}, extra=vol.ALLOW_EXTRA)
-@bind_hass
-def increment(hass, entity_id):
- """Increment a counter."""
- hass.add_job(async_increment, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_increment(hass, entity_id):
- """Increment a counter."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_INCREMENT, {ATTR_ENTITY_ID: entity_id}))
-
-
-@bind_hass
-def decrement(hass, entity_id):
- """Decrement a counter."""
- hass.add_job(async_decrement, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_decrement(hass, entity_id):
- """Decrement a counter."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_DECREMENT, {ATTR_ENTITY_ID: entity_id}))
-
-
-@bind_hass
-def reset(hass, entity_id):
- """Reset a counter."""
- hass.add_job(async_reset, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_reset(hass, entity_id):
- """Reset a counter."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id}))
-
-
async def async_setup(hass, config):
"""Set up the counters."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -105,10 +63,11 @@ async def async_setup(hass, config):
name = cfg.get(CONF_NAME)
initial = cfg.get(CONF_INITIAL)
+ restore = cfg.get(CONF_RESTORE)
step = cfg.get(CONF_STEP)
icon = cfg.get(CONF_ICON)
- entities.append(Counter(object_id, name, initial, step, icon))
+ entities.append(Counter(object_id, name, initial, restore, step, icon))
if not entities:
return False
@@ -130,10 +89,11 @@ async def async_setup(hass, config):
class Counter(Entity):
"""Representation of a counter."""
- def __init__(self, object_id, name, initial, step, icon):
+ def __init__(self, object_id, name, initial, restore, step, icon):
"""Initialize a counter."""
self.entity_id = ENTITY_ID_FORMAT.format(object_id)
self._name = name
+ self._restore = restore
self._step = step
self._state = self._initial = initial
self._icon = icon
@@ -168,12 +128,12 @@ class Counter(Entity):
async def async_added_to_hass(self):
"""Call when entity about to be added to Home Assistant."""
- # If not None, we got an initial value.
- if self._state is not None:
- return
-
- state = await async_get_last_state(self.hass, self.entity_id)
- self._state = state and state.state == state
+ # __init__ will set self._state to self._initial, only override
+ # if needed.
+ if self._restore:
+ state = await async_get_last_state(self.hass, self.entity_id)
+ if state is not None:
+ self._state = int(state.state)
async def async_decrement(self):
"""Decrement the counter."""
diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py
index e9a33c27d34..ec11b139f6b 100644
--- a/homeassistant/components/cover/__init__.py
+++ b/homeassistant/components/cover/__init__.py
@@ -81,64 +81,6 @@ def is_closed(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_CLOSED)
-@bind_hass
-def open_cover(hass, entity_id=None):
- """Open all or specified cover."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_OPEN_COVER, data)
-
-
-@bind_hass
-def close_cover(hass, entity_id=None):
- """Close all or specified cover."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_CLOSE_COVER, data)
-
-
-@bind_hass
-def set_cover_position(hass, position, entity_id=None):
- """Move to specific position all or specified cover."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- data[ATTR_POSITION] = position
- hass.services.call(DOMAIN, SERVICE_SET_COVER_POSITION, data)
-
-
-@bind_hass
-def stop_cover(hass, entity_id=None):
- """Stop all or specified cover."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_STOP_COVER, data)
-
-
-@bind_hass
-def open_cover_tilt(hass, entity_id=None):
- """Open all or specified cover tilt."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_OPEN_COVER_TILT, data)
-
-
-@bind_hass
-def close_cover_tilt(hass, entity_id=None):
- """Close all or specified cover tilt."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_CLOSE_COVER_TILT, data)
-
-
-@bind_hass
-def set_cover_tilt_position(hass, tilt_position, entity_id=None):
- """Move to specific tilt position all or specified cover."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- data[ATTR_TILT_POSITION] = tilt_position
- hass.services.call(DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data)
-
-
-@bind_hass
-def stop_cover_tilt(hass, entity_id=None):
- """Stop all or specified cover tilt."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_STOP_COVER_TILT, data)
-
-
async def async_setup(hass, config):
"""Track states and offer events for covers."""
component = hass.data[DOMAIN] = EntityComponent(
diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py
index 977353cb318..cbc8fbee274 100644
--- a/homeassistant/components/cover/mqtt.py
+++ b/homeassistant/components/cover/mqtt.py
@@ -5,11 +5,12 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.mqtt/
"""
import logging
+from typing import Optional
import voluptuous as vol
from homeassistant.core import callback
-from homeassistant.components import mqtt
+from homeassistant.components import mqtt, cover
from homeassistant.components.cover import (
CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT,
SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION,
@@ -20,10 +21,14 @@ from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN,
STATE_CLOSED, STATE_UNKNOWN)
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
- CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
- valid_publish_topic, valid_subscribe_topic, MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
+ CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
+ CONF_QOS, CONF_RETAIN, valid_publish_topic, valid_subscribe_topic,
+ MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
_LOGGER = logging.getLogger(__name__)
@@ -45,6 +50,7 @@ CONF_TILT_MIN = 'tilt_min'
CONF_TILT_MAX = 'tilt_max'
CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic'
CONF_TILT_INVERT_STATE = 'tilt_invert_state'
+CONF_UNIQUE_ID = 'unique_id'
DEFAULT_NAME = 'MQTT Cover'
DEFAULT_PAYLOAD_OPEN = 'OPEN'
@@ -89,15 +95,32 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
default=DEFAULT_TILT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_INVERT_STATE,
default=DEFAULT_TILT_INVERT_STATE): cv.boolean,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-async def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up the MQTT Cover."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT cover through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT cover dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add an MQTT cover."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(cover.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities,
+ discovery_hash=None):
+ """Set up the MQTT Cover."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
@@ -131,10 +154,12 @@ async def async_setup_platform(hass, config, async_add_entities,
config.get(CONF_TILT_INVERT_STATE),
config.get(CONF_POSITION_TOPIC),
set_position_template,
+ config.get(CONF_UNIQUE_ID),
+ discovery_hash
)])
-class MqttCover(MqttAvailability, CoverDevice):
+class MqttCover(MqttAvailability, MqttDiscoveryUpdate, CoverDevice):
"""Representation of a cover that can be controlled using MQTT."""
def __init__(self, name, state_topic, command_topic, availability_topic,
@@ -143,10 +168,12 @@ class MqttCover(MqttAvailability, CoverDevice):
payload_stop, payload_available, payload_not_available,
optimistic, value_template, tilt_open_position,
tilt_closed_position, tilt_min, tilt_max, tilt_optimistic,
- tilt_invert, position_topic, set_position_template):
+ tilt_invert, position_topic, set_position_template,
+ unique_id: Optional[str], discovery_hash):
"""Initialize the cover."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._position = None
self._state = None
self._name = name
@@ -172,10 +199,13 @@ class MqttCover(MqttAvailability, CoverDevice):
self._tilt_invert = tilt_invert
self._position_topic = position_topic
self._set_position_template = set_position_template
+ self._unique_id = unique_id
+ self._discovery_hash = discovery_hash
async def async_added_to_hass(self):
"""Subscribe MQTT events."""
- await super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
@callback
def tilt_updated(topic, payload, qos):
@@ -387,3 +417,8 @@ class MqttCover(MqttAvailability, CoverDevice):
if self._tilt_invert:
position = self._tilt_max - position + offset
return position
+
+ @property
+ def unique_id(self):
+ """Return a unique ID."""
+ return self._unique_id
diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/cover/myq.py
index 78b6f891f11..5ceb4260d0c 100644
--- a/homeassistant/components/cover/myq.py
+++ b/homeassistant/components/cover/myq.py
@@ -11,8 +11,8 @@ import voluptuous as vol
from homeassistant.components.cover import (
CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN)
from homeassistant.const import (
- CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_OPEN,
- STATE_CLOSING, STATE_OPENING)
+ CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING,
+ STATE_OPEN, STATE_OPENING)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pymyq==0.0.15']
@@ -23,8 +23,8 @@ DEFAULT_NAME = 'myq'
MYQ_TO_HASS = {
'closed': STATE_CLOSED,
- 'open': STATE_OPEN,
'closing': STATE_CLOSING,
+ 'open': STATE_OPEN,
'opening': STATE_OPENING
}
@@ -76,7 +76,7 @@ class MyQDevice(CoverDevice):
self.myq = myq
self.device_id = device['deviceid']
self._name = device['name']
- self._status = STATE_CLOSED
+ self._status = None
@property
def device_class(self):
@@ -96,17 +96,19 @@ class MyQDevice(CoverDevice):
@property
def is_closed(self):
"""Return true if cover is closed, else False."""
- return MYQ_TO_HASS[self._status] == STATE_CLOSED
+ if self._status in [None, False]:
+ return None
+ return MYQ_TO_HASS.get(self._status) == STATE_CLOSED
@property
def is_closing(self):
"""Return if the cover is closing or not."""
- return MYQ_TO_HASS[self._status] == STATE_CLOSING
+ return MYQ_TO_HASS.get(self._status) == STATE_CLOSING
@property
def is_opening(self):
"""Return if the cover is opening or not."""
- return MYQ_TO_HASS[self._status] == STATE_OPENING
+ return MYQ_TO_HASS.get(self._status) == STATE_OPENING
def close_cover(self, **kwargs):
"""Issue close command to cover."""
diff --git a/homeassistant/components/deconz/.translations/hu.json b/homeassistant/components/deconz/.translations/hu.json
index c1fd76c5035..ca2466e9921 100644
--- a/homeassistant/components/deconz/.translations/hu.json
+++ b/homeassistant/components/deconz/.translations/hu.json
@@ -19,8 +19,13 @@
"link": {
"description": "Oldja fel a deCONZ \u00e1tj\u00e1r\u00f3t a Home Assistant-ban val\u00f3 regisztr\u00e1l\u00e1shoz.\n\n1. Menjen a deCONZ rendszer be\u00e1ll\u00edt\u00e1sokhoz\n2. Nyomja meg az \"\u00c1tj\u00e1r\u00f3 felold\u00e1sa\" gombot",
"title": "Kapcsol\u00f3d\u00e1s a deCONZ-hoz"
+ },
+ "options": {
+ "data": {
+ "allow_clip_sensor": "Virtu\u00e1lis szenzorok import\u00e1l\u00e1s\u00e1nak enged\u00e9lyez\u00e9se"
+ }
}
},
- "title": "deCONZ"
+ "title": "deCONZ Zigbee gateway"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/deconz/.translations/ru.json b/homeassistant/components/deconz/.translations/ru.json
index 56490f67cb3..4cbc9594ead 100644
--- a/homeassistant/components/deconz/.translations/ru.json
+++ b/homeassistant/components/deconz/.translations/ru.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "already_configured": "\u0428\u043b\u044e\u0437 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d",
+ "already_configured": "\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.",
"no_bridges": "\u0428\u043b\u044e\u0437\u044b deCONZ \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b",
"one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 deCONZ"
},
@@ -28,6 +28,6 @@
"title": "\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435 \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f deCONZ"
}
},
- "title": "deCONZ"
+ "title": "\u041e\u043f\u0440\u0435\u0434\u0435\u043b\u0438\u0442\u044c \u0448\u043b\u044e\u0437 deCONZ"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/deconz/.translations/zh-Hant.json b/homeassistant/components/deconz/.translations/zh-Hant.json
index 5cd1a14d499..524f68d41bc 100644
--- a/homeassistant/components/deconz/.translations/zh-Hant.json
+++ b/homeassistant/components/deconz/.translations/zh-Hant.json
@@ -3,7 +3,7 @@
"abort": {
"already_configured": "Bridge \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
"no_bridges": "\u672a\u641c\u5c0b\u5230 deCONZ Bridfe",
- "one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u5be6\u4f8b"
+ "one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 deCONZ \u7269\u4ef6"
},
"error": {
"no_key": "\u7121\u6cd5\u53d6\u5f97 API key"
diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py
index c2c7866148f..8999087a137 100644
--- a/homeassistant/components/demo.py
+++ b/homeassistant/components/demo.py
@@ -35,8 +35,7 @@ COMPONENTS_WITH_DEMO_PLATFORM = [
]
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the demo environment."""
group = hass.components.group
configurator = hass.components.configurator
@@ -101,7 +100,7 @@ def async_setup(hass, config):
{'weblink': {'entities': [{'name': 'Router',
'url': 'http://192.168.1.1'}]}}))
- results = yield from asyncio.gather(*tasks, loop=hass.loop)
+ results = await asyncio.gather(*tasks, loop=hass.loop)
if any(not result for result in results):
return False
@@ -192,7 +191,7 @@ def async_setup(hass, config):
'climate.ecobee',
], view=True))
- results = yield from asyncio.gather(*tasks2, loop=hass.loop)
+ results = await asyncio.gather(*tasks2, loop=hass.loop)
if any(not result for result in results):
return False
diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py
index 641ade7308b..40a602056bf 100644
--- a/homeassistant/components/device_sun_light_trigger.py
+++ b/homeassistant/components/device_sun_light_trigger.py
@@ -4,7 +4,6 @@ Provides functionality to turn on lights based on the states.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_sun_light_trigger/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -12,7 +11,11 @@ import voluptuous as vol
from homeassistant.core import callback
import homeassistant.util.dt as dt_util
-from homeassistant.const import STATE_HOME, STATE_NOT_HOME
+from homeassistant.components.light import (
+ ATTR_PROFILE, ATTR_TRANSITION, DOMAIN as DOMAIN_LIGHT)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_HOME,
+ STATE_NOT_HOME)
from homeassistant.helpers.event import (
async_track_point_in_utc_time, async_track_state_change)
from homeassistant.helpers.sun import is_up, get_astral_event_next
@@ -43,8 +46,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the triggers to control lights based on device presence."""
logger = logging.getLogger(__name__)
device_tracker = hass.components.device_tracker
@@ -86,9 +88,12 @@ def async_setup(hass, config):
"""Turn on lights."""
if not device_tracker.is_on() or light.is_on(light_id):
return
- light.async_turn_on(light_id,
- transition=LIGHT_TRANSITION_TIME.seconds,
- profile=light_profile)
+ hass.async_create_task(
+ hass.services.async_call(
+ DOMAIN_LIGHT, SERVICE_TURN_ON, {
+ ATTR_ENTITY_ID: light_id,
+ ATTR_TRANSITION: LIGHT_TRANSITION_TIME.seconds,
+ ATTR_PROFILE: light_profile}))
def async_turn_on_factory(light_id):
"""Generate turn on callbacks as factory."""
@@ -138,7 +143,10 @@ def async_setup(hass, config):
# Do we need lights?
if light_needed:
logger.info("Home coming event for %s. Turning lights on", entity)
- light.async_turn_on(light_ids, profile=light_profile)
+ hass.async_create_task(
+ hass.services.async_call(
+ DOMAIN_LIGHT, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: light_ids, ATTR_PROFILE: light_profile}))
# Are we in the time span were we would turn on the lights
# if someone would be home?
@@ -151,7 +159,10 @@ def async_setup(hass, config):
# when the fading in started and turn it on if so
for index, light_id in enumerate(light_ids):
if now > start_point + index * LIGHT_TRANSITION_TIME:
- light.async_turn_on(light_id)
+ hass.async_create_task(
+ hass.services.async_call(
+ DOMAIN_LIGHT, SERVICE_TURN_ON,
+ {ATTR_ENTITY_ID: light_id}))
else:
# If this light didn't happen to be turned on yet so
@@ -173,7 +184,9 @@ def async_setup(hass, config):
logger.info(
"Everyone has left but there are lights on. Turning them off")
- light.async_turn_off(light_ids)
+ hass.async_create_task(
+ hass.services.async_call(
+ DOMAIN_LIGHT, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: light_ids}))
async_track_state_change(
hass, device_group, turn_off_lights_when_all_leave,
diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py
index 408672a974f..cbf32b4cd5a 100644
--- a/homeassistant/components/device_tracker/__init__.py
+++ b/homeassistant/components/device_tracker/__init__.py
@@ -15,6 +15,9 @@ from homeassistant.setup import async_prepare_setup_platform
from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.components import group, zone
+from homeassistant.components.group import (
+ ATTR_ADD_ENTITIES, ATTR_ENTITIES, ATTR_OBJECT_ID, ATTR_VISIBLE,
+ DOMAIN as DOMAIN_GROUP, SERVICE_SET)
from homeassistant.components.zone.zone import async_active_zone
from homeassistant.config import load_yaml_config_file, async_log_exception
from homeassistant.exceptions import HomeAssistantError
@@ -31,9 +34,9 @@ from homeassistant.util.yaml import dump
from homeassistant.helpers.event import async_track_utc_time_change
from homeassistant.const import (
- ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME, CONF_MAC,
- DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_ID,
- CONF_ICON, ATTR_ICON, ATTR_NAME)
+ ATTR_ENTITY_ID, ATTR_GPS_ACCURACY, ATTR_ICON, ATTR_LATITUDE,
+ ATTR_LONGITUDE, ATTR_NAME, CONF_ICON, CONF_MAC, CONF_NAME,
+ DEVICE_DEFAULT_NAME, STATE_NOT_HOME, STATE_HOME)
_LOGGER = logging.getLogger(__name__)
@@ -138,8 +141,7 @@ def see(hass: HomeAssistantType, mac: str = None, dev_id: str = None,
hass.services.call(DOMAIN, SERVICE_SEE, data)
-@asyncio.coroutine
-def async_setup(hass: HomeAssistantType, config: ConfigType):
+async def async_setup(hass: HomeAssistantType, config: ConfigType):
"""Set up the device tracker."""
yaml_path = hass.config.path(YAML_DEVICES)
@@ -152,14 +154,13 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
if track_new is None:
track_new = defaults.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW)
- devices = yield from async_load_config(yaml_path, hass, consider_home)
+ devices = await async_load_config(yaml_path, hass, consider_home)
tracker = DeviceTracker(
hass, consider_home, track_new, defaults, devices)
- @asyncio.coroutine
- def async_setup_platform(p_type, p_config, disc_info=None):
+ async def async_setup_platform(p_type, p_config, disc_info=None):
"""Set up a device tracker platform."""
- platform = yield from async_prepare_setup_platform(
+ platform = await async_prepare_setup_platform(
hass, config, DOMAIN, p_type)
if platform is None:
return
@@ -169,16 +170,16 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
scanner = None
setup = None
if hasattr(platform, 'async_get_scanner'):
- scanner = yield from platform.async_get_scanner(
+ scanner = await platform.async_get_scanner(
hass, {DOMAIN: p_config})
elif hasattr(platform, 'get_scanner'):
- scanner = yield from hass.async_add_job(
+ scanner = await hass.async_add_job(
platform.get_scanner, hass, {DOMAIN: p_config})
elif hasattr(platform, 'async_setup_scanner'):
- setup = yield from platform.async_setup_scanner(
+ setup = await platform.async_setup_scanner(
hass, p_config, tracker.async_see, disc_info)
elif hasattr(platform, 'setup_scanner'):
- setup = yield from hass.async_add_job(
+ setup = await hass.async_add_job(
platform.setup_scanner, hass, p_config, tracker.see,
disc_info)
else:
@@ -199,14 +200,13 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config
in config_per_platform(config, DOMAIN)]
if setup_tasks:
- yield from asyncio.wait(setup_tasks, loop=hass.loop)
+ await asyncio.wait(setup_tasks, loop=hass.loop)
tracker.async_setup_group()
- @asyncio.coroutine
- def async_platform_discovered(platform, info):
+ async def async_platform_discovered(platform, info):
"""Load a platform."""
- yield from async_setup_platform(platform, {}, disc_info=info)
+ await async_setup_platform(platform, {}, disc_info=info)
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
@@ -214,20 +214,19 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
async_track_utc_time_change(
hass, tracker.async_update_stale, second=range(0, 60, 5))
- @asyncio.coroutine
- def async_see_service(call):
+ async def async_see_service(call):
"""Service to see a device."""
# Temp workaround for iOS, introduced in 0.65
data = dict(call.data)
data.pop('hostname', None)
data.pop('battery_status', None)
- yield from tracker.async_see(**data)
+ await tracker.async_see(**data)
hass.services.async_register(
DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA)
# restore
- yield from tracker.async_setup_tracked_device()
+ await tracker.async_setup_tracked_device()
return True
@@ -268,8 +267,7 @@ class DeviceTracker:
picture, icon, consider_home)
)
- @asyncio.coroutine
- def async_see(
+ async def async_see(
self, mac: str = None, dev_id: str = None, host_name: str = None,
location_name: str = None, gps: GPSType = None,
gps_accuracy: int = None, battery: int = None,
@@ -292,11 +290,11 @@ class DeviceTracker:
device = self.devices.get(dev_id)
if device:
- yield from device.async_seen(
+ await device.async_seen(
host_name, location_name, gps, gps_accuracy, battery,
attributes, source_type, consider_home)
if device.track:
- yield from device.async_update_ha_state()
+ await device.async_update_ha_state()
return
# If no device can be found, create it
@@ -310,18 +308,22 @@ class DeviceTracker:
if mac is not None:
self.mac_to_dev[mac] = device
- yield from device.async_seen(
+ await device.async_seen(
host_name, location_name, gps, gps_accuracy, battery, attributes,
source_type)
if device.track:
- yield from device.async_update_ha_state()
+ await device.async_update_ha_state()
# During init, we ignore the group
if self.group and self.track_new:
- self.group.async_set_group(
- util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
- name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id])
+ self.hass.async_create_task(
+ self.hass.async_call(
+ DOMAIN_GROUP, SERVICE_SET, {
+ ATTR_OBJECT_ID: util.slugify(GROUP_NAME_ALL_DEVICES),
+ ATTR_VISIBLE: False,
+ ATTR_NAME: GROUP_NAME_ALL_DEVICES,
+ ATTR_ADD_ENTITIES: [device.entity_id]}))
self.hass.bus.async_fire(EVENT_NEW_DEVICE, {
ATTR_ENTITY_ID: device.entity_id,
@@ -354,10 +356,13 @@ class DeviceTracker:
entity_ids = [dev.entity_id for dev in self.devices.values()
if dev.track]
- self.group = self.hass.components.group
- self.group.async_set_group(
- util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
- name=GROUP_NAME_ALL_DEVICES, entity_ids=entity_ids)
+ self.hass.async_create_task(
+ self.hass.services.async_call(
+ DOMAIN_GROUP, SERVICE_SET, {
+ ATTR_OBJECT_ID: util.slugify(GROUP_NAME_ALL_DEVICES),
+ ATTR_VISIBLE: False,
+ ATTR_NAME: GROUP_NAME_ALL_DEVICES,
+ ATTR_ENTITIES: entity_ids}))
@callback
def async_update_stale(self, now: dt_util.dt.datetime):
@@ -368,28 +373,26 @@ class DeviceTracker:
for device in self.devices.values():
if (device.track and device.last_update_home) and \
device.stale(now):
- self.hass.async_add_job(device.async_update_ha_state(True))
+ self.hass.async_create_task(device.async_update_ha_state(True))
- @asyncio.coroutine
- def async_setup_tracked_device(self):
+ async def async_setup_tracked_device(self):
"""Set up all not exists tracked devices.
This method is a coroutine.
"""
- @asyncio.coroutine
- def async_init_single_device(dev):
+ async def async_init_single_device(dev):
"""Init a single device_tracker entity."""
- yield from dev.async_added_to_hass()
- yield from dev.async_update_ha_state()
+ await dev.async_added_to_hass()
+ await dev.async_update_ha_state()
tasks = []
for device in self.devices.values():
if device.track and not device.last_seen:
- tasks.append(self.hass.async_add_job(
+ tasks.append(self.hass.async_create_task(
async_init_single_device(device)))
if tasks:
- yield from asyncio.wait(tasks, loop=self.hass.loop)
+ await asyncio.wait(tasks, loop=self.hass.loop)
class Device(Entity):
@@ -487,12 +490,12 @@ class Device(Entity):
"""If device should be hidden."""
return self.away_hide and self.state != STATE_HOME
- @asyncio.coroutine
- def async_seen(self, host_name: str = None, location_name: str = None,
- gps: GPSType = None, gps_accuracy=0, battery: int = None,
- attributes: dict = None,
- source_type: str = SOURCE_TYPE_GPS,
- consider_home: timedelta = None):
+ async def async_seen(
+ self, host_name: str = None, location_name: str = None,
+ gps: GPSType = None, gps_accuracy=0, battery: int = None,
+ attributes: dict = None,
+ source_type: str = SOURCE_TYPE_GPS,
+ consider_home: timedelta = None):
"""Mark the device as seen."""
self.source_type = source_type
self.last_seen = dt_util.utcnow()
@@ -518,7 +521,7 @@ class Device(Entity):
"Could not parse gps value for %s: %s", self.dev_id, gps)
# pylint: disable=not-an-iterable
- yield from self.async_update()
+ await self.async_update()
def stale(self, now: dt_util.dt.datetime = None):
"""Return if device state is stale.
@@ -528,8 +531,7 @@ class Device(Entity):
return self.last_seen and \
(now or dt_util.utcnow()) - self.last_seen > self.consider_home
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update state of entity.
This method is a coroutine.
@@ -555,10 +557,9 @@ class Device(Entity):
self._state = STATE_HOME
self.last_update_home = True
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Add an entity."""
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
if not state:
return
self._state = state.state
@@ -621,9 +622,8 @@ def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
async_load_config(path, hass, consider_home), hass.loop).result()
-@asyncio.coroutine
-def async_load_config(path: str, hass: HomeAssistantType,
- consider_home: timedelta):
+async def async_load_config(path: str, hass: HomeAssistantType,
+ consider_home: timedelta):
"""Load devices from YAML configuration file.
This method is a coroutine.
@@ -643,7 +643,7 @@ def async_load_config(path: str, hass: HomeAssistantType,
try:
result = []
try:
- devices = yield from hass.async_add_job(
+ devices = await hass.async_add_job(
load_yaml_config_file, path)
except HomeAssistantError as err:
_LOGGER.error("Unable to load %s: %s", path, str(err))
@@ -720,10 +720,10 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType,
zone_home.attributes[ATTR_LONGITUDE]]
kwargs['gps_accuracy'] = 0
- hass.async_add_job(async_see_device(**kwargs))
+ hass.async_create_task(async_see_device(**kwargs))
async_track_time_interval(hass, async_device_tracker_scan, interval)
- hass.async_add_job(async_device_tracker_scan(None))
+ hass.async_create_task(async_device_tracker_scan(None))
def update_config(path: str, dev_id: str, device: Device):
diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py
index 4fcc550d7db..9f20eb6d493 100644
--- a/homeassistant/components/device_tracker/automatic.py
+++ b/homeassistant/components/device_tracker/automatic.py
@@ -113,7 +113,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
# Load the initial vehicle data
vehicles = yield from session.get_vehicles()
for vehicle in vehicles:
- hass.async_add_job(data.load_vehicle(vehicle))
+ hass.async_create_task(data.load_vehicle(vehicle))
# Create a task instead of adding a tracking job, since this task will
# run until the websocket connection is closed.
@@ -188,7 +188,7 @@ class AutomaticAuthCallbackView(HomeAssistantView):
code = params['code']
state = params['state']
initialize_callback = hass.data[DATA_CONFIGURING][state]
- hass.async_add_job(initialize_callback(code, state))
+ hass.async_create_task(initialize_callback(code, state))
return response
@@ -209,7 +209,7 @@ class AutomaticData:
self.ws_close_requested = False
self.client.on_app_event(
- lambda name, event: self.hass.async_add_job(
+ lambda name, event: self.hass.async_create_task(
self.handle_event(name, event)))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close())
diff --git a/homeassistant/components/device_tracker/geofency.py b/homeassistant/components/device_tracker/geofency.py
index 7231c5127be..3687571c118 100644
--- a/homeassistant/components/device_tracker/geofency.py
+++ b/homeassistant/components/device_tracker/geofency.py
@@ -4,7 +4,6 @@ Support for the Geofency platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.geofency/
"""
-import asyncio
from functools import partial
import logging
@@ -58,10 +57,9 @@ class GeofencyView(HomeAssistantView):
self.see = see
self.mobile_beacons = [slugify(beacon) for beacon in mobile_beacons]
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Handle Geofency requests."""
- data = yield from request.post()
+ data = await request.post()
hass = request.app['hass']
data = self._validate_data(data)
@@ -69,7 +67,7 @@ class GeofencyView(HomeAssistantView):
return ("Invalid data", HTTP_UNPROCESSABLE_ENTITY)
if self._is_mobile_beacon(data):
- return (yield from self._set_location(hass, data, None))
+ return await self._set_location(hass, data, None)
if data['entry'] == LOCATION_ENTRY:
location_name = data['name']
else:
@@ -78,7 +76,7 @@ class GeofencyView(HomeAssistantView):
data[ATTR_LATITUDE] = data[ATTR_CURRENT_LATITUDE]
data[ATTR_LONGITUDE] = data[ATTR_CURRENT_LONGITUDE]
- return (yield from self._set_location(hass, data, location_name))
+ return await self._set_location(hass, data, location_name)
@staticmethod
def _validate_data(data):
@@ -121,12 +119,11 @@ class GeofencyView(HomeAssistantView):
return "{}_{}".format(BEACON_DEV_PREFIX, data['name'])
return data['device']
- @asyncio.coroutine
- def _set_location(self, hass, data, location_name):
+ async def _set_location(self, hass, data, location_name):
"""Fire HA event to set location."""
device = self._device_name(data)
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(self.see, dev_id=device,
gps=(data[ATTR_LATITUDE], data[ATTR_LONGITUDE]),
location_name=location_name,
diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py
index 170d3de6800..77f499dcf6b 100644
--- a/homeassistant/components/device_tracker/google_maps.py
+++ b/homeassistant/components/device_tracker/google_maps.py
@@ -11,13 +11,15 @@ import voluptuous as vol
from homeassistant.components.device_tracker import (
PLATFORM_SCHEMA, SOURCE_TYPE_GPS)
-from homeassistant.const import ATTR_ID, CONF_PASSWORD, CONF_USERNAME
+from homeassistant.const import (
+ ATTR_ID, CONF_PASSWORD, CONF_USERNAME, ATTR_BATTERY_CHARGING,
+ ATTR_BATTERY_LEVEL)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import slugify, dt as dt_util
-REQUIREMENTS = ['locationsharinglib==2.0.11']
+REQUIREMENTS = ['locationsharinglib==3.0.3']
_LOGGER = logging.getLogger(__name__)
@@ -94,6 +96,8 @@ class GoogleMapsScanner:
ATTR_ID: person.id,
ATTR_LAST_SEEN: dt_util.as_utc(person.datetime),
ATTR_NICKNAME: person.nickname,
+ ATTR_BATTERY_CHARGING: person.charging,
+ ATTR_BATTERY_LEVEL: person.battery_level
}
self.see(
dev_id=dev_id,
diff --git a/homeassistant/components/device_tracker/gpslogger.py b/homeassistant/components/device_tracker/gpslogger.py
index 6336ba51d23..f39684aa834 100644
--- a/homeassistant/components/device_tracker/gpslogger.py
+++ b/homeassistant/components/device_tracker/gpslogger.py
@@ -98,7 +98,7 @@ class GPSLoggerView(HomeAssistantView):
if 'activity' in data:
attrs['activity'] = data['activity']
- hass.async_add_job(self.async_see(
+ hass.async_create_task(self.async_see(
dev_id=device,
gps=gps_location, battery=battery,
gps_accuracy=accuracy,
diff --git a/homeassistant/components/device_tracker/locative.py b/homeassistant/components/device_tracker/locative.py
index 354d3b0980c..aa91f0d3d71 100644
--- a/homeassistant/components/device_tracker/locative.py
+++ b/homeassistant/components/device_tracker/locative.py
@@ -4,7 +4,6 @@ Support for the Locative platform.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.locative/
"""
-import asyncio
from functools import partial
import logging
@@ -38,21 +37,18 @@ class LocativeView(HomeAssistantView):
"""Initialize Locative URL endpoints."""
self.see = see
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Locative message received as GET."""
- res = yield from self._handle(request.app['hass'], request.query)
+ res = await self._handle(request.app['hass'], request.query)
return res
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Locative message received."""
- data = yield from request.post()
- res = yield from self._handle(request.app['hass'], data)
+ data = await request.post()
+ res = await self._handle(request.app['hass'], data)
return res
- @asyncio.coroutine
- def _handle(self, hass, data):
+ async def _handle(self, hass, data):
"""Handle locative request."""
if 'latitude' not in data or 'longitude' not in data:
return ('Latitude and longitude not specified.',
@@ -79,7 +75,7 @@ class LocativeView(HomeAssistantView):
gps_location = (data[ATTR_LATITUDE], data[ATTR_LONGITUDE])
if direction == 'enter':
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(self.see, dev_id=device, location_name=location_name,
gps=gps_location))
return 'Setting location to {}'.format(location_name)
@@ -90,7 +86,7 @@ class LocativeView(HomeAssistantView):
if current_state is None or current_state.state == location_name:
location_name = STATE_NOT_HOME
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(self.see, dev_id=device,
location_name=location_name, gps=gps_location))
return 'Setting location to not home'
diff --git a/homeassistant/components/device_tracker/meraki.py b/homeassistant/components/device_tracker/meraki.py
index c996b7e643b..d12aff1127a 100644
--- a/homeassistant/components/device_tracker/meraki.py
+++ b/homeassistant/components/device_tracker/meraki.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.meraki/
"""
-import asyncio
import logging
import json
@@ -33,8 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_scanner(hass, config, async_see, discovery_info=None):
+async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an endpoint for the Meraki tracker."""
hass.http.register_view(
MerakiView(config, async_see))
@@ -54,16 +52,14 @@ class MerakiView(HomeAssistantView):
self.validator = config[CONF_VALIDATOR]
self.secret = config[CONF_SECRET]
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Meraki message received as GET."""
return self.validator
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Meraki CMX message received."""
try:
- data = yield from request.json()
+ data = await request.json()
except ValueError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
_LOGGER.debug("Meraki Data from Post: %s", json.dumps(data))
@@ -125,7 +121,7 @@ class MerakiView(HomeAssistantView):
attrs['seenTime'] = i['seenTime']
if i.get('ssid', False):
attrs['ssid'] = i['ssid']
- hass.async_add_job(self.async_see(
+ hass.async_create_task(self.async_see(
gps=gps_location,
mac=mac,
source_type=SOURCE_TYPE_ROUTER,
diff --git a/homeassistant/components/device_tracker/mqtt.py b/homeassistant/components/device_tracker/mqtt.py
index b5031e8ccfb..06bd6d771a4 100644
--- a/homeassistant/components/device_tracker/mqtt.py
+++ b/homeassistant/components/device_tracker/mqtt.py
@@ -4,7 +4,6 @@ Support for tracking MQTT enabled devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -25,8 +24,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
})
-@asyncio.coroutine
-def async_setup_scanner(hass, config, async_see, discovery_info=None):
+async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up the MQTT tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
@@ -35,10 +33,10 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
@callback
def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message."""
- hass.async_add_job(
+ hass.async_create_task(
async_see(dev_id=dev_id, location_name=payload))
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
hass, topic, async_message_received, qos)
return True
diff --git a/homeassistant/components/device_tracker/mqtt_json.py b/homeassistant/components/device_tracker/mqtt_json.py
index 7e5ae7c9227..3a820d189f4 100644
--- a/homeassistant/components/device_tracker/mqtt_json.py
+++ b/homeassistant/components/device_tracker/mqtt_json.py
@@ -4,7 +4,6 @@ Support for GPS tracking MQTT enabled devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mqtt_json/
"""
-import asyncio
import json
import logging
@@ -35,8 +34,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(mqtt.SCHEMA_BASE).extend({
})
-@asyncio.coroutine
-def async_setup_scanner(hass, config, async_see, discovery_info=None):
+async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up the MQTT JSON tracker."""
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
@@ -57,9 +55,9 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
return
kwargs = _parse_see_args(dev_id, data)
- hass.async_add_job(async_see(**kwargs))
+ hass.async_create_task(async_see(**kwargs))
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
hass, topic, async_message_received, qos)
return True
diff --git a/homeassistant/components/device_tracker/netgear.py b/homeassistant/components/device_tracker/netgear.py
index 87be70b2040..2e1b96dffad 100644
--- a/homeassistant/components/device_tracker/netgear.py
+++ b/homeassistant/components/device_tracker/netgear.py
@@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_SSL,
CONF_DEVICES, CONF_EXCLUDE)
-REQUIREMENTS = ['pynetgear==0.4.1']
+REQUIREMENTS = ['pynetgear==0.4.2']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/device_tracker/owntracks.py b/homeassistant/components/device_tracker/owntracks.py
index 2d7f1e80406..10f71450f69 100644
--- a/homeassistant/components/device_tracker/owntracks.py
+++ b/homeassistant/components/device_tracker/owntracks.py
@@ -4,7 +4,6 @@ Device tracker platform that adds support for OwnTracks over MQTT.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks/
"""
-import asyncio
import base64
import json
import logging
@@ -73,13 +72,11 @@ def get_cipher():
return (KEYLEN, decrypt)
-@asyncio.coroutine
-def async_setup_scanner(hass, config, async_see, discovery_info=None):
+async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
context = context_from_config(async_see, config)
- @asyncio.coroutine
- def async_handle_mqtt_message(topic, payload, qos):
+ async def async_handle_mqtt_message(topic, payload, qos):
"""Handle incoming OwnTracks message."""
try:
message = json.loads(payload)
@@ -90,9 +87,9 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
message['topic'] = topic
- yield from async_handle_message(hass, context, message)
+ await async_handle_message(hass, context, message)
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
hass, context.mqtt_topic, async_handle_mqtt_message, 1)
return True
@@ -266,8 +263,7 @@ class OwnTracksContext:
return True
- @asyncio.coroutine
- def async_see_beacons(self, hass, dev_id, kwargs_param):
+ async def async_see_beacons(self, hass, dev_id, kwargs_param):
"""Set active beacons to the current location."""
kwargs = kwargs_param.copy()
@@ -290,12 +286,11 @@ class OwnTracksContext:
for beacon in self.mobile_beacons_active[dev_id]:
kwargs['dev_id'] = "{}_{}".format(BEACON_DEV_ID, beacon)
kwargs['host_name'] = beacon
- yield from self.async_see(**kwargs)
+ await self.async_see(**kwargs)
@HANDLERS.register('location')
-@asyncio.coroutine
-def async_handle_location_message(hass, context, message):
+async def async_handle_location_message(hass, context, message):
"""Handle a location message."""
if not context.async_valid_accuracy(message):
return
@@ -312,12 +307,11 @@ def async_handle_location_message(hass, context, message):
context.regions_entered[-1])
return
- yield from context.async_see(**kwargs)
- yield from context.async_see_beacons(hass, dev_id, kwargs)
+ await context.async_see(**kwargs)
+ await context.async_see_beacons(hass, dev_id, kwargs)
-@asyncio.coroutine
-def _async_transition_message_enter(hass, context, message, location):
+async def _async_transition_message_enter(hass, context, message, location):
"""Execute enter event."""
zone = hass.states.get("zone.{}".format(slugify(location)))
dev_id, kwargs = _parse_see_args(message, context.mqtt_topic)
@@ -331,7 +325,7 @@ def _async_transition_message_enter(hass, context, message, location):
if location not in beacons:
beacons.add(location)
_LOGGER.info("Added beacon %s", location)
- yield from context.async_see_beacons(hass, dev_id, kwargs)
+ await context.async_see_beacons(hass, dev_id, kwargs)
else:
# Normal region
regions = context.regions_entered[dev_id]
@@ -339,12 +333,11 @@ def _async_transition_message_enter(hass, context, message, location):
regions.append(location)
_LOGGER.info("Enter region %s", location)
_set_gps_from_zone(kwargs, location, zone)
- yield from context.async_see(**kwargs)
- yield from context.async_see_beacons(hass, dev_id, kwargs)
+ await context.async_see(**kwargs)
+ await context.async_see_beacons(hass, dev_id, kwargs)
-@asyncio.coroutine
-def _async_transition_message_leave(hass, context, message, location):
+async def _async_transition_message_leave(hass, context, message, location):
"""Execute leave event."""
dev_id, kwargs = _parse_see_args(message, context.mqtt_topic)
regions = context.regions_entered[dev_id]
@@ -356,7 +349,7 @@ def _async_transition_message_leave(hass, context, message, location):
if location in beacons:
beacons.remove(location)
_LOGGER.info("Remove beacon %s", location)
- yield from context.async_see_beacons(hass, dev_id, kwargs)
+ await context.async_see_beacons(hass, dev_id, kwargs)
else:
new_region = regions[-1] if regions else None
if new_region:
@@ -365,21 +358,20 @@ def _async_transition_message_leave(hass, context, message, location):
"zone.{}".format(slugify(new_region)))
_set_gps_from_zone(kwargs, new_region, zone)
_LOGGER.info("Exit to %s", new_region)
- yield from context.async_see(**kwargs)
- yield from context.async_see_beacons(hass, dev_id, kwargs)
+ await context.async_see(**kwargs)
+ await context.async_see_beacons(hass, dev_id, kwargs)
return
_LOGGER.info("Exit to GPS")
# Check for GPS accuracy
if context.async_valid_accuracy(message):
- yield from context.async_see(**kwargs)
- yield from context.async_see_beacons(hass, dev_id, kwargs)
+ await context.async_see(**kwargs)
+ await context.async_see_beacons(hass, dev_id, kwargs)
@HANDLERS.register('transition')
-@asyncio.coroutine
-def async_handle_transition_message(hass, context, message):
+async def async_handle_transition_message(hass, context, message):
"""Handle a transition message."""
if message.get('desc') is None:
_LOGGER.error(
@@ -399,10 +391,10 @@ def async_handle_transition_message(hass, context, message):
location = STATE_HOME
if message['event'] == 'enter':
- yield from _async_transition_message_enter(
+ await _async_transition_message_enter(
hass, context, message, location)
elif message['event'] == 'leave':
- yield from _async_transition_message_leave(
+ await _async_transition_message_leave(
hass, context, message, location)
else:
_LOGGER.error(
@@ -410,8 +402,7 @@ def async_handle_transition_message(hass, context, message):
message['event'])
-@asyncio.coroutine
-def async_handle_waypoint(hass, name_base, waypoint):
+async def async_handle_waypoint(hass, name_base, waypoint):
"""Handle a waypoint."""
name = waypoint['desc']
pretty_name = '{} - {}'.format(name_base, name)
@@ -429,13 +420,12 @@ def async_handle_waypoint(hass, name_base, waypoint):
zone = zone_comp.Zone(hass, pretty_name, lat, lon, rad,
zone_comp.ICON_IMPORT, False)
zone.entity_id = entity_id
- yield from zone.async_update_ha_state()
+ await zone.async_update_ha_state()
@HANDLERS.register('waypoint')
@HANDLERS.register('waypoints')
-@asyncio.coroutine
-def async_handle_waypoints_message(hass, context, message):
+async def async_handle_waypoints_message(hass, context, message):
"""Handle a waypoints message."""
if not context.import_waypoints:
return
@@ -456,12 +446,11 @@ def async_handle_waypoints_message(hass, context, message):
name_base = ' '.join(_parse_topic(message['topic'], context.mqtt_topic))
for wayp in wayps:
- yield from async_handle_waypoint(hass, name_base, wayp)
+ await async_handle_waypoint(hass, name_base, wayp)
@HANDLERS.register('encrypted')
-@asyncio.coroutine
-def async_handle_encrypted_message(hass, context, message):
+async def async_handle_encrypted_message(hass, context, message):
"""Handle an encrypted message."""
plaintext_payload = _decrypt_payload(context.secret, message['topic'],
message['data'])
@@ -472,7 +461,7 @@ def async_handle_encrypted_message(hass, context, message):
decrypted = json.loads(plaintext_payload)
decrypted['topic'] = message['topic']
- yield from async_handle_message(hass, context, decrypted)
+ await async_handle_message(hass, context, decrypted)
@HANDLERS.register('lwt')
@@ -481,24 +470,21 @@ def async_handle_encrypted_message(hass, context, message):
@HANDLERS.register('cmd')
@HANDLERS.register('steps')
@HANDLERS.register('card')
-@asyncio.coroutine
-def async_handle_not_impl_msg(hass, context, message):
+async def async_handle_not_impl_msg(hass, context, message):
"""Handle valid but not implemented message types."""
_LOGGER.debug('Not handling %s message: %s', message.get("_type"), message)
-@asyncio.coroutine
-def async_handle_unsupported_msg(hass, context, message):
+async def async_handle_unsupported_msg(hass, context, message):
"""Handle an unsupported or invalid message type."""
_LOGGER.warning('Received unsupported message type: %s.',
message.get('_type'))
-@asyncio.coroutine
-def async_handle_message(hass, context, message):
+async def async_handle_message(hass, context, message):
"""Handle an OwnTracks message."""
msgtype = message.get('_type')
handler = HANDLERS.get(msgtype, async_handle_unsupported_msg)
- yield from handler(hass, context, message)
+ await handler(hass, context, message)
diff --git a/homeassistant/components/device_tracker/owntracks_http.py b/homeassistant/components/device_tracker/owntracks_http.py
index d74e1fc6d95..b9a813738ad 100644
--- a/homeassistant/components/device_tracker/owntracks_http.py
+++ b/homeassistant/components/device_tracker/owntracks_http.py
@@ -4,7 +4,6 @@ Device tracker platform that adds support for OwnTracks over HTTP.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.owntracks_http/
"""
-import asyncio
import re
from aiohttp.web_exceptions import HTTPInternalServerError
@@ -19,8 +18,7 @@ from .owntracks import ( # NOQA
DEPENDENCIES = ['http']
-@asyncio.coroutine
-def async_setup_scanner(hass, config, async_see, discovery_info=None):
+async def async_setup_scanner(hass, config, async_see, discovery_info=None):
"""Set up an OwnTracks tracker."""
context = context_from_config(async_see, config)
@@ -39,19 +37,18 @@ class OwnTracksView(HomeAssistantView):
"""Initialize OwnTracks URL endpoints."""
self.context = context
- @asyncio.coroutine
- def post(self, request, user, device):
+ async def post(self, request, user, device):
"""Handle an OwnTracks message."""
hass = request.app['hass']
subscription = self.context.mqtt_topic
topic = re.sub('/#$', '', subscription)
- message = yield from request.json()
+ message = await request.json()
message['topic'] = '{}/{}/{}'.format(topic, user, device)
try:
- yield from async_handle_message(hass, self.context, message)
+ await async_handle_message(hass, self.context, message)
return self.json([])
except ValueError:
diff --git a/homeassistant/components/device_tracker/tile.py b/homeassistant/components/device_tracker/tile.py
index 07f15e7e88a..224aee4363b 100644
--- a/homeassistant/components/device_tracker/tile.py
+++ b/homeassistant/components/device_tracker/tile.py
@@ -29,10 +29,12 @@ ATTR_IS_DEAD = 'is_dead'
ATTR_IS_LOST = 'is_lost'
ATTR_RING_STATE = 'ring_state'
ATTR_VOIP_STATE = 'voip_state'
+ATTR_TILE_ID = 'tile_identifier'
+ATTR_TILE_NAME = 'tile_name'
CONF_SHOW_INACTIVE = 'show_inactive'
-DEFAULT_ICON = 'mdi:bluetooth'
+DEFAULT_ICON = 'mdi:view-grid'
DEFAULT_SCAN_INTERVAL = timedelta(minutes=2)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -50,8 +52,10 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
websession = aiohttp_client.async_get_clientsession(hass)
+ config_file = hass.config.path(".{}{}".format(
+ slugify(config[CONF_USERNAME]), CLIENT_UUID_CONFIG_FILE))
config_data = await hass.async_add_job(
- load_json, hass.config.path(CLIENT_UUID_CONFIG_FILE))
+ load_json, config_file)
if config_data:
client = Client(
config[CONF_USERNAME],
@@ -63,10 +67,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
config[CONF_USERNAME], config[CONF_PASSWORD], websession)
config_data = {'client_uuid': client.client_uuid}
- config_saved = await hass.async_add_job(
- save_json, hass.config.path(CLIENT_UUID_CONFIG_FILE), config_data)
- if not config_saved:
- _LOGGER.error('Failed to save the client UUID')
+ await hass.async_add_job(save_json, config_file, config_data)
scanner = TileScanner(
client, hass, async_see, config[CONF_MONITORED_VARIABLES],
@@ -125,7 +126,7 @@ class TileScanner:
for tile in tiles:
await self._async_see(
- dev_id='tile_{0}'.format(slugify(tile['name'])),
+ dev_id='tile_{0}'.format(slugify(tile['tile_uuid'])),
gps=(
tile['tileState']['latitude'],
tile['tileState']['longitude']
@@ -138,5 +139,7 @@ class TileScanner:
ATTR_IS_LOST: tile['tileState']['is_lost'],
ATTR_RING_STATE: tile['tileState']['ring_state'],
ATTR_VOIP_STATE: tile['tileState']['voip_state'],
+ ATTR_TILE_ID: tile['tile_uuid'],
+ ATTR_TILE_NAME: tile['name']
},
icon=DEFAULT_ICON)
diff --git a/homeassistant/components/device_tracker/upc_connect.py b/homeassistant/components/device_tracker/upc_connect.py
index ea0645e012f..2ee6d64730d 100644
--- a/homeassistant/components/device_tracker/upc_connect.py
+++ b/homeassistant/components/device_tracker/upc_connect.py
@@ -31,11 +31,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_get_scanner(hass, config):
+async def async_get_scanner(hass, config):
"""Return the UPC device scanner."""
scanner = UPCDeviceScanner(hass, config[DOMAIN])
- success_init = yield from scanner.async_initialize_token()
+ success_init = await scanner.async_initialize_token()
return scanner if success_init else None
@@ -61,18 +60,17 @@ class UPCDeviceScanner(DeviceScanner):
self.websession = async_get_clientsession(hass)
- @asyncio.coroutine
- def async_scan_devices(self):
+ async def async_scan_devices(self):
"""Scan for new devices and return a list with found device IDs."""
import defusedxml.ElementTree as ET
if self.token is None:
- token_initialized = yield from self.async_initialize_token()
+ token_initialized = await self.async_initialize_token()
if not token_initialized:
_LOGGER.error("Not connected to %s", self.host)
return []
- raw = yield from self._async_ws_function(CMD_DEVICES)
+ raw = await self._async_ws_function(CMD_DEVICES)
try:
xml_root = ET.fromstring(raw)
@@ -82,22 +80,20 @@ class UPCDeviceScanner(DeviceScanner):
self.token = None
return []
- @asyncio.coroutine
- def async_get_device_name(self, device):
+ async def async_get_device_name(self, device):
"""Get the device name (the name of the wireless device not used)."""
return None
- @asyncio.coroutine
- def async_initialize_token(self):
+ async def async_initialize_token(self):
"""Get first token."""
try:
# get first token
with async_timeout.timeout(10, loop=self.hass.loop):
- response = yield from self.websession.get(
+ response = await self.websession.get(
"http://{}/common_page/login.html".format(self.host),
headers=self.headers)
- yield from response.text()
+ await response.text()
self.token = response.cookies['sessionToken'].value
@@ -107,14 +103,13 @@ class UPCDeviceScanner(DeviceScanner):
_LOGGER.error("Can not load login page from %s", self.host)
return False
- @asyncio.coroutine
- def _async_ws_function(self, function):
+ async def _async_ws_function(self, function):
"""Execute a command on UPC firmware webservice."""
try:
with async_timeout.timeout(10, loop=self.hass.loop):
# The 'token' parameter has to be first, and 'fun' second
# or the UPC firmware will return an error
- response = yield from self.websession.post(
+ response = await self.websession.post(
"http://{}/xml/getter.xml".format(self.host),
data="token={}&fun={}".format(self.token, function),
headers=self.headers, allow_redirects=False)
@@ -127,7 +122,7 @@ class UPCDeviceScanner(DeviceScanner):
# Load data, store token for next request
self.token = response.cookies['sessionToken'].value
- return (yield from response.text())
+ return await response.text()
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error on %s", function)
diff --git a/homeassistant/components/discovery.py b/homeassistant/components/discovery.py
index 91f9dea704b..0640eb262cd 100644
--- a/homeassistant/components/discovery.py
+++ b/homeassistant/components/discovery.py
@@ -50,6 +50,7 @@ CONFIG_ENTRY_HANDLERS = {
SERVICE_HUE: 'hue',
SERVICE_IKEA_TRADFRI: 'tradfri',
'sonos': 'sonos',
+ 'igd': 'upnp',
}
SERVICE_HANDLERS = {
@@ -168,7 +169,7 @@ async def async_setup(hass, config):
results = await hass.async_add_job(_discover, netdisco)
for result in results:
- hass.async_add_job(new_service_found(*result))
+ hass.async_create_task(new_service_found(*result))
async_track_point_in_utc_time(hass, scan_devices,
dt_util.utcnow() + SCAN_INTERVAL)
@@ -180,7 +181,7 @@ async def async_setup(hass, config):
# discovery local services
if 'HASSIO' in os.environ:
- hass.async_add_job(new_service_found(SERVICE_HASSIO, {}))
+ hass.async_create_task(new_service_found(SERVICE_HASSIO, {}))
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, schedule_first)
diff --git a/homeassistant/components/doorbird.py b/homeassistant/components/doorbird.py
index c97289b9f07..ab929eb90bb 100644
--- a/homeassistant/components/doorbird.py
+++ b/homeassistant/components/doorbird.py
@@ -6,7 +6,6 @@ https://home-assistant.io/components/doorbird/
"""
import logging
-import asyncio
import voluptuous as vol
from homeassistant.components.http import HomeAssistantView
@@ -170,8 +169,7 @@ class DoorbirdRequestView(HomeAssistantView):
extra_urls = [API_URL + '/{sensor}']
# pylint: disable=no-self-use
- @asyncio.coroutine
- def get(self, request, sensor):
+ async def get(self, request, sensor):
"""Respond to requests from the device."""
hass = request.app['hass']
diff --git a/homeassistant/components/duckdns.py b/homeassistant/components/duckdns.py
index 178e1579538..3420bbed1bc 100644
--- a/homeassistant/components/duckdns.py
+++ b/homeassistant/components/duckdns.py
@@ -4,14 +4,12 @@ Integrate with DuckDNS.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/duckdns/
"""
-import asyncio
from datetime import timedelta
import logging
import voluptuous as vol
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_DOMAIN
-from homeassistant.loader import bind_hass
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -40,36 +38,24 @@ SERVICE_TXT_SCHEMA = vol.Schema({
})
-@bind_hass
-@asyncio.coroutine
-def async_set_txt(hass, txt):
- """Set the txt record. Pass in None to remove it."""
- yield from hass.services.async_call(DOMAIN, SERVICE_SET_TXT, {
- ATTR_TXT: txt
- }, blocking=True)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize the DuckDNS component."""
domain = config[DOMAIN][CONF_DOMAIN]
token = config[DOMAIN][CONF_ACCESS_TOKEN]
session = async_get_clientsession(hass)
- result = yield from _update_duckdns(session, domain, token)
+ result = await _update_duckdns(session, domain, token)
if not result:
return False
- @asyncio.coroutine
- def update_domain_interval(now):
+ async def update_domain_interval(now):
"""Update the DuckDNS entry."""
- yield from _update_duckdns(session, domain, token)
+ await _update_duckdns(session, domain, token)
- @asyncio.coroutine
- def update_domain_service(call):
+ async def update_domain_service(call):
"""Update the DuckDNS entry."""
- yield from _update_duckdns(
+ await _update_duckdns(
session, domain, token, txt=call.data[ATTR_TXT])
async_track_time_interval(hass, update_domain_interval, INTERVAL)
@@ -83,8 +69,8 @@ def async_setup(hass, config):
_SENTINEL = object()
-@asyncio.coroutine
-def _update_duckdns(session, domain, token, *, txt=_SENTINEL, clear=False):
+async def _update_duckdns(session, domain, token, *, txt=_SENTINEL,
+ clear=False):
"""Update DuckDNS."""
params = {
'domains': domain,
@@ -102,8 +88,8 @@ def _update_duckdns(session, domain, token, *, txt=_SENTINEL, clear=False):
if clear:
params['clear'] = 'true'
- resp = yield from session.get(UPDATE_URL, params=params)
- body = yield from resp.text()
+ resp = await session.get(UPDATE_URL, params=params)
+ body = await resp.text()
if body != 'OK':
_LOGGER.warning("Updating DuckDNS domain failed: %s", domain)
diff --git a/homeassistant/components/emulated_hue/__init__.py b/homeassistant/components/emulated_hue/__init__.py
index 8a67b933b9f..5f1d61dd602 100644
--- a/homeassistant/components/emulated_hue/__init__.py
+++ b/homeassistant/components/emulated_hue/__init__.py
@@ -18,6 +18,8 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.deprecation import get_deprecated
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json, save_json
+from homeassistant.components.http import real_ip
+
from .hue_api import (
HueUsernameView, HueAllLightsStateView, HueOneLightStateView,
HueOneLightChangeView, HueGroupView)
@@ -81,12 +83,20 @@ ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden'
-def setup(hass, yaml_config):
+async def async_setup(hass, yaml_config):
"""Activate the emulated_hue component."""
config = Config(hass, yaml_config.get(DOMAIN, {}))
app = web.Application()
app['hass'] = hass
+
+ real_ip.setup_real_ip(app, False, [])
+ # We misunderstood the startup signal. You're not allowed to change
+ # anything during startup. Temp workaround.
+ # pylint: disable=protected-access
+ app._on_startup.freeze()
+ await app.startup()
+
handler = None
server = None
@@ -131,7 +141,8 @@ def setup(hass, yaml_config):
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, stop_emulated_hue_bridge)
- hass.bus.listen_once(EVENT_HOMEASSISTANT_START, start_emulated_hue_bridge)
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
+ start_emulated_hue_bridge)
return True
diff --git a/homeassistant/components/emulated_hue/hue_api.py b/homeassistant/components/emulated_hue/hue_api.py
index f7fbe2e15e3..3699a45ef30 100644
--- a/homeassistant/components/emulated_hue/hue_api.py
+++ b/homeassistant/components/emulated_hue/hue_api.py
@@ -1,5 +1,4 @@
"""Provides a Hue API to control Home Assistant."""
-import asyncio
import logging
from aiohttp import web
@@ -21,6 +20,9 @@ from homeassistant.components.fan import (
SPEED_MEDIUM, SPEED_HIGH
)
from homeassistant.components.http import HomeAssistantView
+from homeassistant.components.http.const import KEY_REAL_IP
+from homeassistant.util.network import is_local
+
_LOGGER = logging.getLogger(__name__)
@@ -36,11 +38,10 @@ class HueUsernameView(HomeAssistantView):
extra_urls = ['/api/']
requires_auth = False
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Handle a POST request."""
try:
- data = yield from request.json()
+ data = await request.json()
except ValueError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
@@ -48,6 +49,10 @@ class HueUsernameView(HomeAssistantView):
return self.json_message('devicetype not specified',
HTTP_BAD_REQUEST)
+ if not is_local(request[KEY_REAL_IP]):
+ return self.json_message('only local IPs allowed',
+ HTTP_BAD_REQUEST)
+
return self.json([{'success': {'username': '12345678901234567890'}}])
@@ -65,6 +70,10 @@ class HueGroupView(HomeAssistantView):
@core.callback
def put(self, request, username):
"""Process a request to make the Logitech Pop working."""
+ if not is_local(request[KEY_REAL_IP]):
+ return self.json_message('only local IPs allowed',
+ HTTP_BAD_REQUEST)
+
return self.json([{
'error': {
'address': '/groups/0/action/scene',
@@ -88,6 +97,10 @@ class HueAllLightsStateView(HomeAssistantView):
@core.callback
def get(self, request, username):
"""Process a request to get the list of available lights."""
+ if not is_local(request[KEY_REAL_IP]):
+ return self.json_message('only local IPs allowed',
+ HTTP_BAD_REQUEST)
+
hass = request.app['hass']
json_response = {}
@@ -116,6 +129,10 @@ class HueOneLightStateView(HomeAssistantView):
@core.callback
def get(self, request, username, entity_id):
"""Process a request to get the state of an individual light."""
+ if not is_local(request[KEY_REAL_IP]):
+ return self.json_message('only local IPs allowed',
+ HTTP_BAD_REQUEST)
+
hass = request.app['hass']
entity_id = self.config.number_to_entity_id(entity_id)
entity = hass.states.get(entity_id)
@@ -146,9 +163,12 @@ class HueOneLightChangeView(HomeAssistantView):
"""Initialize the instance of the view."""
self.config = config
- @asyncio.coroutine
- def put(self, request, username, entity_number):
+ async def put(self, request, username, entity_number):
"""Process a request to set the state of an individual light."""
+ if not is_local(request[KEY_REAL_IP]):
+ return self.json_message('only local IPs allowed',
+ HTTP_BAD_REQUEST)
+
config = self.config
hass = request.app['hass']
entity_id = config.number_to_entity_id(entity_number)
@@ -168,7 +188,7 @@ class HueOneLightChangeView(HomeAssistantView):
return web.Response(text="Entity not exposed", status=404)
try:
- request_json = yield from request.json()
+ request_json = await request.json()
except ValueError:
_LOGGER.error('Received invalid json')
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
@@ -257,11 +277,11 @@ class HueOneLightChangeView(HomeAssistantView):
# Separate call to turn on needed
if turn_on_needed:
- hass.async_add_job(hass.services.async_call(
+ hass.async_create_task(hass.services.async_call(
core.DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id},
blocking=True))
- hass.async_add_job(hass.services.async_call(
+ hass.async_create_task(hass.services.async_call(
domain, service, data, blocking=True))
json_response = \
diff --git a/homeassistant/components/envisalink.py b/homeassistant/components/envisalink.py
index 9b5b25c934c..e96810a8083 100644
--- a/homeassistant/components/envisalink.py
+++ b/homeassistant/components/envisalink.py
@@ -16,7 +16,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
-REQUIREMENTS = ['pyenvisalink==2.3']
+REQUIREMENTS = ['pyenvisalink==3.7']
_LOGGER = logging.getLogger(__name__)
@@ -81,8 +81,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up for Envisalink devices."""
from pyenvisalink import EnvisalinkAlarmPanel
@@ -165,7 +164,7 @@ def async_setup(hass, config):
_LOGGER.info("Start envisalink.")
controller.start()
- result = yield from sync_connect
+ result = await sync_connect
if not result:
return False
diff --git a/homeassistant/components/evohome.py b/homeassistant/components/evohome.py
new file mode 100644
index 00000000000..ceeec407b05
--- /dev/null
+++ b/homeassistant/components/evohome.py
@@ -0,0 +1,145 @@
+"""Support for Honeywell evohome (EMEA/EU-based systems only).
+
+Support for a temperature control system (TCS, controller) with 0+ heating
+zones (e.g. TRVs, relays) and, optionally, a DHW controller.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/evohome/
+"""
+
+# Glossary:
+# TCS - temperature control system (a.k.a. Controller, Parent), which can
+# have up to 13 Children:
+# 0-12 Heating zones (a.k.a. Zone), and
+# 0-1 DHW controller, (a.k.a. Boiler)
+
+import logging
+
+from requests.exceptions import HTTPError
+import voluptuous as vol
+
+from homeassistant.const import (
+ CONF_USERNAME,
+ CONF_PASSWORD,
+ CONF_SCAN_INTERVAL,
+ HTTP_BAD_REQUEST
+)
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.discovery import load_platform
+
+REQUIREMENTS = ['evohomeclient==0.2.7']
+# If ever > 0.2.7, re-check the work-around wrapper is still required when
+# instantiating the client, below.
+
+_LOGGER = logging.getLogger(__name__)
+
+DOMAIN = 'evohome'
+DATA_EVOHOME = 'data_' + DOMAIN
+
+CONF_LOCATION_IDX = 'location_idx'
+MAX_TEMP = 28
+MIN_TEMP = 5
+SCAN_INTERVAL_DEFAULT = 180
+SCAN_INTERVAL_MAX = 300
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_USERNAME): cv.string,
+ vol.Required(CONF_PASSWORD): cv.string,
+ vol.Optional(CONF_LOCATION_IDX, default=0): cv.positive_int,
+ }),
+}, extra=vol.ALLOW_EXTRA)
+
+# These are used to help prevent E501 (line too long) violations.
+GWS = 'gateways'
+TCS = 'temperatureControlSystems'
+
+
+def setup(hass, config):
+ """Create a Honeywell (EMEA/EU) evohome CH/DHW system.
+
+ One controller with 0+ heating zones (e.g. TRVs, relays) and, optionally, a
+ DHW controller. Does not work for US-based systems.
+ """
+ evo_data = hass.data[DATA_EVOHOME] = {}
+ evo_data['timers'] = {}
+
+ evo_data['params'] = dict(config[DOMAIN])
+ evo_data['params'][CONF_SCAN_INTERVAL] = SCAN_INTERVAL_DEFAULT
+
+ from evohomeclient2 import EvohomeClient
+
+ _LOGGER.debug("setup(): API call [4 request(s)]: client.__init__()...")
+
+ try:
+ # There's a bug in evohomeclient2 v0.2.7: the client.__init__() sets
+ # the root loglevel when EvohomeClient(debug=?), so remember it now...
+ log_level = logging.getLogger().getEffectiveLevel()
+
+ client = EvohomeClient(
+ evo_data['params'][CONF_USERNAME],
+ evo_data['params'][CONF_PASSWORD],
+ debug=False
+ )
+ # ...then restore it to what it was before instantiating the client
+ logging.getLogger().setLevel(log_level)
+
+ except HTTPError as err:
+ if err.response.status_code == HTTP_BAD_REQUEST:
+ _LOGGER.error(
+ "Failed to establish a connection with evohome web servers, "
+ "Check your username (%s), and password are correct."
+ "Unable to continue. Resolve any errors and restart HA.",
+ evo_data['params'][CONF_USERNAME]
+ )
+ return False # unable to continue
+
+ raise # we dont handle any other HTTPErrors
+
+ finally: # Redact username, password as no longer needed.
+ evo_data['params'][CONF_USERNAME] = 'REDACTED'
+ evo_data['params'][CONF_PASSWORD] = 'REDACTED'
+
+ evo_data['client'] = client
+
+ # Redact any installation data we'll never need.
+ if client.installation_info[0]['locationInfo']['locationId'] != 'REDACTED':
+ for loc in client.installation_info:
+ loc['locationInfo']['streetAddress'] = 'REDACTED'
+ loc['locationInfo']['city'] = 'REDACTED'
+ loc['locationInfo']['locationOwner'] = 'REDACTED'
+ loc[GWS][0]['gatewayInfo'] = 'REDACTED'
+
+ # Pull down the installation configuration.
+ loc_idx = evo_data['params'][CONF_LOCATION_IDX]
+
+ try:
+ evo_data['config'] = client.installation_info[loc_idx]
+
+ except IndexError:
+ _LOGGER.warning(
+ "setup(): Parameter '%s' = %s , is outside its range (0-%s)",
+ CONF_LOCATION_IDX,
+ loc_idx,
+ len(client.installation_info) - 1
+ )
+
+ return False # unable to continue
+
+ evo_data['status'] = {}
+
+ if _LOGGER.isEnabledFor(logging.DEBUG):
+ tmp_loc = dict(evo_data['config'])
+ tmp_loc['locationInfo']['postcode'] = 'REDACTED'
+ tmp_tcs = tmp_loc[GWS][0][TCS][0]
+ if 'zones' in tmp_tcs:
+ tmp_tcs['zones'] = '...'
+ if 'dhw' in tmp_tcs:
+ tmp_tcs['dhw'] = '...'
+
+ _LOGGER.debug("setup(), location = %s", tmp_loc)
+
+ load_platform(hass, 'climate', DOMAIN)
+
+ return True
diff --git a/homeassistant/components/fan/__init__.py b/homeassistant/components/fan/__init__.py
index f2704e84bc5..36b075747e0 100644
--- a/homeassistant/components/fan/__init__.py
+++ b/homeassistant/components/fan/__init__.py
@@ -4,7 +4,6 @@ Provides functionality to interact with fans.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan/
"""
-import asyncio
from datetime import timedelta
import functools as ft
import logging
@@ -98,84 +97,12 @@ def is_on(hass, entity_id: str = None) -> bool:
return state.attributes[ATTR_SPEED] not in [SPEED_OFF, STATE_UNKNOWN]
-@bind_hass
-def turn_on(hass, entity_id: str = None, speed: str = None) -> None:
- """Turn all or specified fan on."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_SPEED, speed),
- ] if value is not None
- }
-
- hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
-
-
-@bind_hass
-def turn_off(hass, entity_id: str = None) -> None:
- """Turn all or specified fan off."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
-
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
-
-
-@bind_hass
-def toggle(hass, entity_id: str = None) -> None:
- """Toggle all or specified fans."""
- data = {
- ATTR_ENTITY_ID: entity_id
- }
-
- hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
-
-
-@bind_hass
-def oscillate(hass, entity_id: str = None,
- should_oscillate: bool = True) -> None:
- """Set oscillation on all or specified fan."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_OSCILLATING, should_oscillate),
- ] if value is not None
- }
-
- hass.services.call(DOMAIN, SERVICE_OSCILLATE, data)
-
-
-@bind_hass
-def set_speed(hass, entity_id: str = None, speed: str = None) -> None:
- """Set speed for all or specified fan."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_SPEED, speed),
- ] if value is not None
- }
-
- hass.services.call(DOMAIN, SERVICE_SET_SPEED, data)
-
-
-@bind_hass
-def set_direction(hass, entity_id: str = None, direction: str = None) -> None:
- """Set direction for all or specified fan."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_DIRECTION, direction),
- ] if value is not None
- }
-
- hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data)
-
-
-@asyncio.coroutine
-def async_setup(hass, config: dict):
+async def async_setup(hass, config: dict):
"""Expose fan control via statemachine and services."""
- component = EntityComponent(
+ component = hass.data[DOMAIN] = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_FANS)
- yield from component.async_setup(config)
+ await component.async_setup(config)
component.async_register_entity_service(
SERVICE_TURN_ON, FAN_TURN_ON_SCHEMA,
@@ -205,6 +132,16 @@ def async_setup(hass, config: dict):
return True
+async def async_setup_entry(hass, entry):
+ """Set up a config entry."""
+ return await hass.data[DOMAIN].async_setup_entry(entry)
+
+
+async def async_unload_entry(hass, entry):
+ """Unload a config entry."""
+ return await hass.data[DOMAIN].async_unload_entry(entry)
+
+
class FanEntity(ToggleEntity):
"""Representation of a fan."""
diff --git a/homeassistant/components/fan/dyson.py b/homeassistant/components/fan/dyson.py
index 9f505c87b3d..ef517021178 100644
--- a/homeassistant/components/fan/dyson.py
+++ b/homeassistant/components/fan/dyson.py
@@ -3,7 +3,6 @@
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.dyson/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -77,8 +76,7 @@ class DysonPureCoolLinkDevice(FanEntity):
self.hass = hass
self._device = device
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
diff --git a/homeassistant/components/fan/insteon.py b/homeassistant/components/fan/insteon.py
index f938ae7aec1..604063a9aa3 100644
--- a/homeassistant/components/fan/insteon.py
+++ b/homeassistant/components/fan/insteon.py
@@ -4,7 +4,6 @@ Support for INSTEON fans via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/fan.insteon/
"""
-import asyncio
import logging
from homeassistant.components.fan import (SPEED_OFF,
@@ -28,9 +27,8 @@ FAN_SPEEDS = [STATE_OFF, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data['insteon'].get('modem')
@@ -64,20 +62,17 @@ class InsteonFan(InsteonEntity, FanEntity):
"""Flag supported features."""
return SUPPORT_SET_SPEED
- @asyncio.coroutine
- def async_turn_on(self, speed: str = None, **kwargs) -> None:
+ async def async_turn_on(self, speed: str = None, **kwargs) -> None:
"""Turn on the entity."""
if speed is None:
speed = SPEED_MEDIUM
- yield from self.async_set_speed(speed)
+ await self.async_set_speed(speed)
- @asyncio.coroutine
- def async_turn_off(self, **kwargs) -> None:
+ async def async_turn_off(self, **kwargs) -> None:
"""Turn off the entity."""
- yield from self.async_set_speed(SPEED_OFF)
+ await self.async_set_speed(SPEED_OFF)
- @asyncio.coroutine
- def async_set_speed(self, speed: str) -> None:
+ async def async_set_speed(self, speed: str) -> None:
"""Set the speed of the fan."""
fan_speed = SPEED_TO_HEX[speed]
if fan_speed == 0x00:
diff --git a/homeassistant/components/fan/mqtt.py b/homeassistant/components/fan/mqtt.py
index db3cfab3608..3e1ad2704e7 100644
--- a/homeassistant/components/fan/mqtt.py
+++ b/homeassistant/components/fan/mqtt.py
@@ -5,6 +5,7 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/fan.mqtt/
"""
import logging
+from typing import Optional
import voluptuous as vol
@@ -14,9 +15,9 @@ from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_STATE, STATE_ON, STATE_OFF,
CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON)
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
- CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
- MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
+ CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
+ CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM,
@@ -41,6 +42,7 @@ CONF_PAYLOAD_LOW_SPEED = 'payload_low_speed'
CONF_PAYLOAD_MEDIUM_SPEED = 'payload_medium_speed'
CONF_PAYLOAD_HIGH_SPEED = 'payload_high_speed'
CONF_SPEED_LIST = 'speeds'
+CONF_UNIQUE_ID = 'unique_id'
DEFAULT_NAME = 'MQTT Fan'
DEFAULT_PAYLOAD_ON = 'ON'
@@ -74,6 +76,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
default=[SPEED_OFF, SPEED_LOW,
SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
+ vol.Optional(CONF_UNIQUE_ID): cv.string,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@@ -83,6 +86,10 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
+ discovery_hash = None
+ if discovery_info is not None and ATTR_DISCOVERY_HASH in discovery_info:
+ discovery_hash = discovery_info[ATTR_DISCOVERY_HASH]
+
async_add_entities([MqttFan(
config.get(CONF_NAME),
{
@@ -116,18 +123,22 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
+ config.get(CONF_UNIQUE_ID),
+ discovery_hash,
)])
-class MqttFan(MqttAvailability, FanEntity):
+class MqttFan(MqttAvailability, MqttDiscoveryUpdate, FanEntity):
"""A MQTT fan component."""
def __init__(self, name, topic, templates, qos, retain, payload,
speed_list, optimistic, availability_topic, payload_available,
- payload_not_available):
+ payload_not_available, unique_id: Optional[str],
+ discovery_hash):
"""Initialize the MQTT fan."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._name = name
self._topic = topic
self._qos = qos
@@ -148,10 +159,13 @@ class MqttFan(MqttAvailability, FanEntity):
is not None and SUPPORT_OSCILLATE)
self._supported_features |= (topic[CONF_SPEED_STATE_TOPIC]
is not None and SUPPORT_SET_SPEED)
+ self._unique_id = unique_id
+ self._discovery_hash = discovery_hash
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
- await super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
templates = {}
for key, tpl in list(self._templates.items()):
@@ -315,3 +329,8 @@ class MqttFan(MqttAvailability, FanEntity):
if self._optimistic_oscillation:
self._oscillation = oscillating
self.async_schedule_update_ha_state()
+
+ @property
+ def unique_id(self):
+ """Return a unique ID."""
+ return self._unique_id
diff --git a/homeassistant/components/fan/wink.py b/homeassistant/components/fan/wink.py
index 480801c48c0..d0dc386d74d 100644
--- a/homeassistant/components/fan/wink.py
+++ b/homeassistant/components/fan/wink.py
@@ -4,7 +4,6 @@ Support for Wink fans.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/fan.wink/
"""
-import asyncio
import logging
from homeassistant.components.fan import (
@@ -33,8 +32,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class WinkFanDevice(WinkDevice, FanEntity):
"""Representation of a Wink fan."""
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['fan'].append(self)
diff --git a/homeassistant/components/ffmpeg.py b/homeassistant/components/ffmpeg.py
index 9aaae16ee21..f28dbd52336 100644
--- a/homeassistant/components/ffmpeg.py
+++ b/homeassistant/components/ffmpeg.py
@@ -4,7 +4,6 @@ Component that will help set the FFmpeg component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/ffmpeg/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -55,29 +54,7 @@ SERVICE_FFMPEG_SCHEMA = vol.Schema({
})
-@callback
-def async_start(hass, entity_id=None):
- """Start a FFmpeg process on entity."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_START, data))
-
-
-@callback
-def async_stop(hass, entity_id=None):
- """Stop a FFmpeg process on entity."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_STOP, data))
-
-
-@callback
-def async_restart(hass, entity_id=None):
- """Restart a FFmpeg process on entity."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RESTART, data))
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the FFmpeg component."""
conf = config.get(DOMAIN, {})
@@ -88,8 +65,7 @@ def async_setup(hass, config):
)
# Register service
- @asyncio.coroutine
- def async_service_handle(service):
+ async def async_service_handle(service):
"""Handle service ffmpeg process."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
@@ -131,8 +107,7 @@ class FFmpegManager:
"""Return ffmpeg binary from config."""
return self._bin
- @asyncio.coroutine
- def async_run_test(self, input_source):
+ async def async_run_test(self, input_source):
"""Run test on this input. TRUE is deactivate or run correct.
This method must be run in the event loop.
@@ -146,7 +121,7 @@ class FFmpegManager:
# run test
ffmpeg_test = Test(self.binary, loop=self.hass.loop)
- success = yield from ffmpeg_test.run_test(input_source)
+ success = await ffmpeg_test.run_test(input_source)
if not success:
_LOGGER.error("FFmpeg '%s' test fails!", input_source)
self._cache[input_source] = False
@@ -163,8 +138,7 @@ class FFmpegBase(Entity):
self.ffmpeg = None
self.initial_state = initial_state
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register dispatcher & events.
This method is a coroutine.
@@ -189,40 +163,36 @@ class FFmpegBase(Entity):
"""Return True if entity has to be polled for state."""
return False
- @asyncio.coroutine
- def _async_start_ffmpeg(self, entity_ids):
+ async def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg process.
This method is a coroutine.
"""
raise NotImplementedError()
- @asyncio.coroutine
- def _async_stop_ffmpeg(self, entity_ids):
+ async def _async_stop_ffmpeg(self, entity_ids):
"""Stop a FFmpeg process.
This method is a coroutine.
"""
if entity_ids is None or self.entity_id in entity_ids:
- yield from self.ffmpeg.close()
+ await self.ffmpeg.close()
- @asyncio.coroutine
- def _async_restart_ffmpeg(self, entity_ids):
+ async def _async_restart_ffmpeg(self, entity_ids):
"""Stop a FFmpeg process.
This method is a coroutine.
"""
if entity_ids is None or self.entity_id in entity_ids:
- yield from self._async_stop_ffmpeg(None)
- yield from self._async_start_ffmpeg(None)
+ await self._async_stop_ffmpeg(None)
+ await self._async_start_ffmpeg(None)
@callback
def _async_register_events(self):
"""Register a FFmpeg process/device."""
- @asyncio.coroutine
- def async_shutdown_handle(event):
+ async def async_shutdown_handle(event):
"""Stop FFmpeg process."""
- yield from self._async_stop_ffmpeg(None)
+ await self._async_stop_ffmpeg(None)
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, async_shutdown_handle)
@@ -231,10 +201,9 @@ class FFmpegBase(Entity):
if not self.initial_state:
return
- @asyncio.coroutine
- def async_start_handle(event):
+ async def async_start_handle(event):
"""Start FFmpeg process."""
- yield from self._async_start_ffmpeg(None)
+ await self._async_start_ffmpeg(None)
self.async_schedule_update_ha_state()
self.hass.bus.async_listen_once(
diff --git a/homeassistant/components/foursquare.py b/homeassistant/components/foursquare.py
index 2c10df327f4..a4a7395adc4 100644
--- a/homeassistant/components/foursquare.py
+++ b/homeassistant/components/foursquare.py
@@ -4,7 +4,6 @@ Support for the Foursquare (Swarm) API.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/foursquare/
"""
-import asyncio
import logging
import requests
@@ -85,11 +84,10 @@ class FoursquarePushReceiver(HomeAssistantView):
"""Initialize the OAuth callback view."""
self.push_secret = push_secret
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Accept the POST from Foursquare."""
try:
- data = yield from request.json()
+ data = await request.json()
except ValueError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
diff --git a/homeassistant/components/freedns.py b/homeassistant/components/freedns.py
index 0512030bdcb..0b5cbeda01a 100644
--- a/homeassistant/components/freedns.py
+++ b/homeassistant/components/freedns.py
@@ -37,8 +37,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize the FreeDNS component."""
url = config[DOMAIN].get(CONF_URL)
auth_token = config[DOMAIN].get(CONF_ACCESS_TOKEN)
@@ -46,16 +45,15 @@ def async_setup(hass, config):
session = hass.helpers.aiohttp_client.async_get_clientsession()
- result = yield from _update_freedns(
+ result = await _update_freedns(
hass, session, url, auth_token)
if result is False:
return False
- @asyncio.coroutine
- def update_domain_callback(now):
+ async def update_domain_callback(now):
"""Update the FreeDNS entry."""
- yield from _update_freedns(hass, session, url, auth_token)
+ await _update_freedns(hass, session, url, auth_token)
hass.helpers.event.async_track_time_interval(
update_domain_callback, update_interval)
@@ -63,8 +61,7 @@ def async_setup(hass, config):
return True
-@asyncio.coroutine
-def _update_freedns(hass, session, url, auth_token):
+async def _update_freedns(hass, session, url, auth_token):
"""Update FreeDNS."""
params = None
@@ -77,8 +74,8 @@ def _update_freedns(hass, session, url, auth_token):
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
- resp = yield from session.get(url, params=params)
- body = yield from resp.text()
+ resp = await session.get(url, params=params)
+ body = await resp.text()
if "has not changed" in body:
# IP has not changed.
diff --git a/homeassistant/components/fritzbox.py b/homeassistant/components/fritzbox.py
index a3c35aaa597..e6f121799df 100644
--- a/homeassistant/components/fritzbox.py
+++ b/homeassistant/components/fritzbox.py
@@ -16,15 +16,18 @@ from homeassistant.helpers import discovery
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['pyfritzhome==0.3.7']
+REQUIREMENTS = ['pyfritzhome==0.4.0']
-SUPPORTED_DOMAINS = ['climate', 'switch']
+SUPPORTED_DOMAINS = ['binary_sensor', 'climate', 'switch']
DOMAIN = 'fritzbox'
-ATTR_STATE_DEVICE_LOCKED = 'device_locked'
-ATTR_STATE_LOCKED = 'locked'
ATTR_STATE_BATTERY_LOW = 'battery_low'
+ATTR_STATE_DEVICE_LOCKED = 'device_locked'
+ATTR_STATE_HOLIDAY_MODE = 'holiday_mode'
+ATTR_STATE_LOCKED = 'locked'
+ATTR_STATE_SUMMER_MODE = 'summer_mode'
+ATTR_STATE_WINDOW_OPEN = 'window_open'
CONFIG_SCHEMA = vol.Schema({
diff --git a/homeassistant/components/frontend/__init__.py b/homeassistant/components/frontend/__init__.py
index 9bd13f316b6..c06f659573e 100644
--- a/homeassistant/components/frontend/__init__.py
+++ b/homeassistant/components/frontend/__init__.py
@@ -21,16 +21,14 @@ from homeassistant.components import websocket_api
from homeassistant.config import find_config_file, load_yaml_config_file
from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
-from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.translation import async_get_translations
from homeassistant.loader import bind_hass
-from homeassistant.util.yaml import load_yaml
-REQUIREMENTS = ['home-assistant-frontend==20180927.0']
+REQUIREMENTS = ['home-assistant-frontend==20181012.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log',
- 'auth', 'onboarding']
+ 'auth', 'onboarding', 'lovelace']
CONF_THEMES = 'themes'
CONF_EXTRA_HTML_URL = 'extra_html_url'
@@ -108,10 +106,6 @@ SCHEMA_GET_TRANSLATIONS = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
vol.Required('type'): WS_TYPE_GET_TRANSLATIONS,
vol.Required('language'): str,
})
-WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config'
-SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): WS_TYPE_GET_LOVELACE_UI,
-})
class Panel:
@@ -151,7 +145,7 @@ class Panel:
index_view.get)
@callback
- def to_response(self, hass, request):
+ def to_response(self):
"""Panel as dictionary."""
return {
'component_name': self.component_name,
@@ -208,9 +202,6 @@ async def async_setup(hass, config):
hass.components.websocket_api.async_register_command(
WS_TYPE_GET_TRANSLATIONS, websocket_get_translations,
SCHEMA_GET_TRANSLATIONS)
- hass.components.websocket_api.async_register_command(
- WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config,
- SCHEMA_GET_LOVELACE_UI)
hass.http.register_view(ManifestJSONView)
conf = config.get(DOMAIN, {})
@@ -353,11 +344,12 @@ class AuthorizeView(HomeAssistantView):
_is_latest(self.js_option, request)
if latest:
- location = '/frontend_latest/authorize.html'
+ base = 'frontend_latest'
else:
- location = '/frontend_es5/authorize.html'
+ base = 'frontend_es5'
- location += '?{}'.format(request.query_string)
+ location = "/{}/authorize.html{}".format(
+ base, str(request.url.relative())[15:])
return web.Response(status=302, headers={
'location': location
@@ -493,12 +485,10 @@ def websocket_get_panels(hass, connection, msg):
Async friendly.
"""
panels = {
- panel:
- connection.hass.data[DATA_PANELS][panel].to_response(
- connection.hass, connection.request)
+ panel: connection.hass.data[DATA_PANELS][panel].to_response()
for panel in connection.hass.data[DATA_PANELS]}
- connection.to_write.put_nowait(websocket_api.result_message(
+ connection.send_message(websocket_api.result_message(
msg['id'], panels))
@@ -508,50 +498,21 @@ def websocket_get_themes(hass, connection, msg):
Async friendly.
"""
- connection.to_write.put_nowait(websocket_api.result_message(msg['id'], {
+ connection.send_message(websocket_api.result_message(msg['id'], {
'themes': hass.data[DATA_THEMES],
'default_theme': hass.data[DATA_DEFAULT_THEME],
}))
-@callback
-def websocket_get_translations(hass, connection, msg):
+@websocket_api.async_response
+async def websocket_get_translations(hass, connection, msg):
"""Handle get translations command.
Async friendly.
"""
- async def send_translations():
- """Send a translation."""
- resources = await async_get_translations(hass, msg['language'])
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], {
- 'resources': resources,
- }
- ))
-
- hass.async_add_job(send_translations())
-
-
-def websocket_lovelace_config(hass, connection, msg):
- """Send lovelace UI config over websocket config."""
- async def send_exp_config():
- """Send lovelace frontend config."""
- error = None
- try:
- config = await hass.async_add_job(
- load_yaml, hass.config.path('ui-lovelace.yaml'))
- message = websocket_api.result_message(
- msg['id'], config
- )
- except FileNotFoundError:
- error = ('file_not_found',
- 'Could not find ui-lovelace.yaml in your config dir.')
- except HomeAssistantError as err:
- error = 'load_error', str(err)
-
- if error is not None:
- message = websocket_api.error_message(msg['id'], *error)
-
- connection.send_message_outside(message)
-
- hass.async_add_job(send_exp_config())
+ resources = await async_get_translations(hass, msg['language'])
+ connection.send_message(websocket_api.result_message(
+ msg['id'], {
+ 'resources': resources,
+ }
+ ))
diff --git a/homeassistant/components/google_assistant/__init__.py b/homeassistant/components/google_assistant/__init__.py
index 22569af1f86..8d4ac9f01c9 100644
--- a/homeassistant/components/google_assistant/__init__.py
+++ b/homeassistant/components/google_assistant/__init__.py
@@ -14,21 +14,18 @@ import async_timeout
import voluptuous as vol
# Typing imports
-from homeassistant.core import HomeAssistant
+from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.const import CONF_NAME
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
-from homeassistant.loader import bind_hass
from .const import (
- DOMAIN, CONF_PROJECT_ID, CONF_CLIENT_ID, CONF_ACCESS_TOKEN,
- CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT, CONF_EXPOSED_DOMAINS,
- DEFAULT_EXPOSED_DOMAINS, CONF_AGENT_USER_ID, CONF_API_KEY,
+ DOMAIN, CONF_PROJECT_ID, CONF_EXPOSE_BY_DEFAULT, DEFAULT_EXPOSE_BY_DEFAULT,
+ CONF_EXPOSED_DOMAINS, DEFAULT_EXPOSED_DOMAINS, CONF_API_KEY,
SERVICE_REQUEST_SYNC, REQUEST_SYNC_BASE_URL, CONF_ENTITY_CONFIG,
CONF_EXPOSE, CONF_ALIASES, CONF_ROOM_HINT
)
-from .auth import GoogleAssistantAuthView
from .http import async_register_http
_LOGGER = logging.getLogger(__name__)
@@ -44,40 +41,28 @@ ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_ROOM_HINT): cv.string
})
-CONFIG_SCHEMA = vol.Schema(
- {
- DOMAIN: {
- vol.Required(CONF_PROJECT_ID): cv.string,
- vol.Required(CONF_CLIENT_ID): cv.string,
- vol.Required(CONF_ACCESS_TOKEN): cv.string,
- vol.Optional(CONF_EXPOSE_BY_DEFAULT,
- default=DEFAULT_EXPOSE_BY_DEFAULT): cv.boolean,
- vol.Optional(CONF_EXPOSED_DOMAINS,
- default=DEFAULT_EXPOSED_DOMAINS): cv.ensure_list,
- vol.Optional(CONF_AGENT_USER_ID,
- default=DEFAULT_AGENT_USER_ID): cv.string,
- vol.Optional(CONF_API_KEY): cv.string,
- vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA}
- }
- },
- extra=vol.ALLOW_EXTRA)
+GOOGLE_ASSISTANT_SCHEMA = vol.Schema({
+ vol.Required(CONF_PROJECT_ID): cv.string,
+ vol.Optional(CONF_EXPOSE_BY_DEFAULT,
+ default=DEFAULT_EXPOSE_BY_DEFAULT): cv.boolean,
+ vol.Optional(CONF_EXPOSED_DOMAINS,
+ default=DEFAULT_EXPOSED_DOMAINS): cv.ensure_list,
+ vol.Optional(CONF_API_KEY): cv.string,
+ vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ENTITY_SCHEMA}
+}, extra=vol.PREVENT_EXTRA)
-
-@bind_hass
-def request_sync(hass):
- """Request sync."""
- hass.services.call(DOMAIN, SERVICE_REQUEST_SYNC)
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: GOOGLE_ASSISTANT_SCHEMA
+}, extra=vol.ALLOW_EXTRA)
async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]):
"""Activate Google Actions component."""
config = yaml_config.get(DOMAIN, {})
- agent_user_id = config.get(CONF_AGENT_USER_ID)
api_key = config.get(CONF_API_KEY)
- hass.http.register_view(GoogleAssistantAuthView(hass, config))
async_register_http(hass, config)
- async def request_sync_service_handler(call):
+ async def request_sync_service_handler(call: ServiceCall):
"""Handle request sync service calls."""
websession = async_get_clientsession(hass)
try:
@@ -85,7 +70,7 @@ async def async_setup(hass: HomeAssistant, yaml_config: Dict[str, Any]):
res = await websession.post(
REQUEST_SYNC_BASE_URL,
params={'key': api_key},
- json={'agent_user_id': agent_user_id})
+ json={'agent_user_id': call.context.user_id})
_LOGGER.info("Submitted request_sync request to Google")
res.raise_for_status()
except aiohttp.ClientResponseError:
diff --git a/homeassistant/components/google_assistant/auth.py b/homeassistant/components/google_assistant/auth.py
deleted file mode 100644
index 5b98e25014d..00000000000
--- a/homeassistant/components/google_assistant/auth.py
+++ /dev/null
@@ -1,83 +0,0 @@
-"""Google Assistant OAuth View."""
-
-import logging
-from typing import Dict, Any
-
-# Typing imports
-# if False:
-from aiohttp.web import Request, Response
-
-from homeassistant.core import HomeAssistant
-from homeassistant.components.http import HomeAssistantView
-from homeassistant.const import (
- HTTP_BAD_REQUEST,
- HTTP_UNAUTHORIZED,
- HTTP_MOVED_PERMANENTLY,
-)
-
-from .const import (
- GOOGLE_ASSISTANT_API_ENDPOINT,
- CONF_PROJECT_ID, CONF_CLIENT_ID, CONF_ACCESS_TOKEN
-)
-
-BASE_OAUTH_URL = 'https://oauth-redirect.googleusercontent.com'
-REDIRECT_TEMPLATE_URL = \
- '{}/r/{}#access_token={}&token_type=bearer&state={}'
-
-_LOGGER = logging.getLogger(__name__)
-
-
-class GoogleAssistantAuthView(HomeAssistantView):
- """Handle Google Actions auth requests."""
-
- url = GOOGLE_ASSISTANT_API_ENDPOINT + '/auth'
- name = 'api:google_assistant:auth'
- requires_auth = False
-
- def __init__(self, hass: HomeAssistant, cfg: Dict[str, Any]) -> None:
- """Initialize instance of the view."""
- super().__init__()
-
- self.project_id = cfg.get(CONF_PROJECT_ID)
- self.client_id = cfg.get(CONF_CLIENT_ID)
- self.access_token = cfg.get(CONF_ACCESS_TOKEN)
-
- async def get(self, request: Request) -> Response:
- """Handle oauth token request."""
- query = request.query
- redirect_uri = query.get('redirect_uri')
- if not redirect_uri:
- msg = 'missing redirect_uri field'
- _LOGGER.warning(msg)
- return self.json_message(msg, status_code=HTTP_BAD_REQUEST)
-
- if self.project_id not in redirect_uri:
- msg = 'missing project_id in redirect_uri'
- _LOGGER.warning(msg)
- return self.json_message(msg, status_code=HTTP_BAD_REQUEST)
-
- state = query.get('state')
- if not state:
- msg = 'oauth request missing state'
- _LOGGER.warning(msg)
- return self.json_message(msg, status_code=HTTP_BAD_REQUEST)
-
- client_id = query.get('client_id')
- if self.client_id != client_id:
- msg = 'invalid client id'
- _LOGGER.warning(msg)
- return self.json_message(msg, status_code=HTTP_UNAUTHORIZED)
-
- generated_url = redirect_url(self.project_id, self.access_token, state)
-
- _LOGGER.info('user login in from Google Assistant')
- return self.json_message(
- 'redirect success',
- status_code=HTTP_MOVED_PERMANENTLY,
- headers={'Location': generated_url})
-
-
-def redirect_url(project_id: str, access_token: str, state: str) -> str:
- """Generate the redirect format for the oauth request."""
- return REDIRECT_TEMPLATE_URL.format(BASE_OAUTH_URL, project_id,
- access_token, state)
diff --git a/homeassistant/components/google_assistant/const.py b/homeassistant/components/google_assistant/const.py
index 12888ea2cf6..485b98e8e22 100644
--- a/homeassistant/components/google_assistant/const.py
+++ b/homeassistant/components/google_assistant/const.py
@@ -8,10 +8,7 @@ CONF_ENTITY_CONFIG = 'entity_config'
CONF_EXPOSE_BY_DEFAULT = 'expose_by_default'
CONF_EXPOSED_DOMAINS = 'exposed_domains'
CONF_PROJECT_ID = 'project_id'
-CONF_ACCESS_TOKEN = 'access_token'
-CONF_CLIENT_ID = 'client_id'
CONF_ALIASES = 'aliases'
-CONF_AGENT_USER_ID = 'agent_user_id'
CONF_API_KEY = 'api_key'
CONF_ROOM_HINT = 'room'
diff --git a/homeassistant/components/google_assistant/http.py b/homeassistant/components/google_assistant/http.py
index 05bc3cbd01c..65af7b932b0 100644
--- a/homeassistant/components/google_assistant/http.py
+++ b/homeassistant/components/google_assistant/http.py
@@ -6,7 +6,6 @@ https://home-assistant.io/components/google_assistant/
"""
import logging
-from aiohttp.hdrs import AUTHORIZATION
from aiohttp.web import Request, Response
# Typing imports
@@ -15,10 +14,8 @@ from homeassistant.core import callback
from .const import (
GOOGLE_ASSISTANT_API_ENDPOINT,
- CONF_ACCESS_TOKEN,
CONF_EXPOSE_BY_DEFAULT,
CONF_EXPOSED_DOMAINS,
- CONF_AGENT_USER_ID,
CONF_ENTITY_CONFIG,
CONF_EXPOSE,
)
@@ -31,10 +28,8 @@ _LOGGER = logging.getLogger(__name__)
@callback
def async_register_http(hass, cfg):
"""Register HTTP views for Google Assistant."""
- access_token = cfg.get(CONF_ACCESS_TOKEN)
expose_by_default = cfg.get(CONF_EXPOSE_BY_DEFAULT)
exposed_domains = cfg.get(CONF_EXPOSED_DOMAINS)
- agent_user_id = cfg.get(CONF_AGENT_USER_ID)
entity_config = cfg.get(CONF_ENTITY_CONFIG) or {}
def is_exposed(entity) -> bool:
@@ -57,9 +52,8 @@ def async_register_http(hass, cfg):
return is_default_exposed or explicit_expose
- gass_config = Config(is_exposed, agent_user_id, entity_config)
hass.http.register_view(
- GoogleAssistantView(access_token, gass_config))
+ GoogleAssistantView(is_exposed, entity_config))
class GoogleAssistantView(HomeAssistantView):
@@ -67,20 +61,19 @@ class GoogleAssistantView(HomeAssistantView):
url = GOOGLE_ASSISTANT_API_ENDPOINT
name = 'api:google_assistant'
- requires_auth = False # Uses access token from oauth flow
+ requires_auth = True
- def __init__(self, access_token, gass_config):
+ def __init__(self, is_exposed, entity_config):
"""Initialize the Google Assistant request handler."""
- self.access_token = access_token
- self.gass_config = gass_config
+ self.is_exposed = is_exposed
+ self.entity_config = entity_config
async def post(self, request: Request) -> Response:
"""Handle Google Assistant requests."""
- auth = request.headers.get(AUTHORIZATION, None)
- if 'Bearer {}'.format(self.access_token) != auth:
- return self.json_message("missing authorization", status_code=401)
-
message = await request.json() # type: dict
+ config = Config(self.is_exposed,
+ request['hass_user'].id,
+ self.entity_config)
result = await async_handle_message(
- request.app['hass'], self.gass_config, message)
+ request.app['hass'], config, message)
return self.json(result)
diff --git a/homeassistant/components/google_domains.py b/homeassistant/components/google_domains.py
index 3b414306be5..32bdb79557a 100644
--- a/homeassistant/components/google_domains.py
+++ b/homeassistant/components/google_domains.py
@@ -36,8 +36,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize the Google Domains component."""
domain = config[DOMAIN].get(CONF_DOMAIN)
user = config[DOMAIN].get(CONF_USERNAME)
@@ -46,16 +45,15 @@ def async_setup(hass, config):
session = hass.helpers.aiohttp_client.async_get_clientsession()
- result = yield from _update_google_domains(
+ result = await _update_google_domains(
hass, session, domain, user, password, timeout)
if not result:
return False
- @asyncio.coroutine
- def update_domain_interval(now):
+ async def update_domain_interval(now):
"""Update the Google Domains entry."""
- yield from _update_google_domains(
+ await _update_google_domains(
hass, session, domain, user, password, timeout)
hass.helpers.event.async_track_time_interval(
@@ -64,8 +62,8 @@ def async_setup(hass, config):
return True
-@asyncio.coroutine
-def _update_google_domains(hass, session, domain, user, password, timeout):
+async def _update_google_domains(hass, session, domain, user, password,
+ timeout):
"""Update Google Domains."""
url = UPDATE_URL.format(user, password)
@@ -75,8 +73,8 @@ def _update_google_domains(hass, session, domain, user, password, timeout):
try:
with async_timeout.timeout(timeout, loop=hass.loop):
- resp = yield from session.get(url, params=params)
- body = yield from resp.text()
+ resp = await session.get(url, params=params)
+ body = await resp.text()
if body.startswith('good') or body.startswith('nochg'):
return True
diff --git a/homeassistant/components/group/__init__.py b/homeassistant/components/group/__init__.py
index eda65d1895d..39fd7567c98 100644
--- a/homeassistant/components/group/__init__.py
+++ b/homeassistant/components/group/__init__.py
@@ -120,70 +120,6 @@ def is_on(hass, entity_id):
return False
-@bind_hass
-def reload(hass):
- """Reload the automation from config."""
- hass.add_job(async_reload, hass)
-
-
-@callback
-@bind_hass
-def async_reload(hass):
- """Reload the automation from config."""
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RELOAD))
-
-
-@bind_hass
-def set_visibility(hass, entity_id=None, visible=True):
- """Hide or shows a group."""
- data = {ATTR_ENTITY_ID: entity_id, ATTR_VISIBLE: visible}
- hass.services.call(DOMAIN, SERVICE_SET_VISIBILITY, data)
-
-
-@bind_hass
-def set_group(hass, object_id, name=None, entity_ids=None, visible=None,
- icon=None, view=None, control=None, add=None):
- """Create/Update a group."""
- hass.add_job(
- async_set_group, hass, object_id, name, entity_ids, visible, icon,
- view, control, add)
-
-
-@callback
-@bind_hass
-def async_set_group(hass, object_id, name=None, entity_ids=None, visible=None,
- icon=None, view=None, control=None, add=None):
- """Create/Update a group."""
- data = {
- key: value for key, value in [
- (ATTR_OBJECT_ID, object_id),
- (ATTR_NAME, name),
- (ATTR_ENTITIES, entity_ids),
- (ATTR_VISIBLE, visible),
- (ATTR_ICON, icon),
- (ATTR_VIEW, view),
- (ATTR_CONTROL, control),
- (ATTR_ADD_ENTITIES, add),
- ] if value is not None
- }
-
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SET, data))
-
-
-@bind_hass
-def remove(hass, name):
- """Remove a user group."""
- hass.add_job(async_remove, hass, name)
-
-
-@callback
-@bind_hass
-def async_remove(hass, object_id):
- """Remove a user group."""
- data = {ATTR_OBJECT_ID: object_id}
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_REMOVE, data))
-
-
@bind_hass
def expand_entity_ids(hass, entity_ids):
"""Return entity_ids with group entity ids replaced by their members.
diff --git a/homeassistant/components/hangouts/.translations/de.json b/homeassistant/components/hangouts/.translations/de.json
index a2ed8d21230..e0f18b6cccf 100644
--- a/homeassistant/components/hangouts/.translations/de.json
+++ b/homeassistant/components/hangouts/.translations/de.json
@@ -14,6 +14,7 @@
"data": {
"2fa": "2FA PIN"
},
+ "description": "Leer",
"title": "2-Faktor-Authentifizierung"
},
"user": {
@@ -21,6 +22,7 @@
"email": "E-Mail-Adresse",
"password": "Passwort"
},
+ "description": "Leer",
"title": "Google Hangouts Login"
}
},
diff --git a/homeassistant/components/hangouts/.translations/ko.json b/homeassistant/components/hangouts/.translations/ko.json
index aabf977a8cc..af0e76829e5 100644
--- a/homeassistant/components/hangouts/.translations/ko.json
+++ b/homeassistant/components/hangouts/.translations/ko.json
@@ -2,7 +2,7 @@
"config": {
"abort": {
"already_configured": "Google Hangouts \uc740 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4",
- "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
+ "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
},
"error": {
"invalid_2fa": "2\ub2e8\uacc4 \uc778\uc99d\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574 \uc8fc\uc138\uc694.",
diff --git a/homeassistant/components/hangouts/.translations/nl.json b/homeassistant/components/hangouts/.translations/nl.json
index cf73210aa3b..da9bc9edd7b 100644
--- a/homeassistant/components/hangouts/.translations/nl.json
+++ b/homeassistant/components/hangouts/.translations/nl.json
@@ -14,6 +14,7 @@
"data": {
"2fa": "2FA pin"
},
+ "description": "Leeg",
"title": "Twee-factor-authenticatie"
},
"user": {
@@ -21,6 +22,7 @@
"email": "E-mailadres",
"password": "Wachtwoord"
},
+ "description": "Leeg",
"title": "Google Hangouts inlog"
}
},
diff --git a/homeassistant/components/hangouts/.translations/no.json b/homeassistant/components/hangouts/.translations/no.json
index c2cdb93c005..d75092da759 100644
--- a/homeassistant/components/hangouts/.translations/no.json
+++ b/homeassistant/components/hangouts/.translations/no.json
@@ -14,6 +14,7 @@
"data": {
"2fa": "2FA Pin"
},
+ "description": "Tom",
"title": "Tofaktorautentisering"
},
"user": {
@@ -21,6 +22,7 @@
"email": "E-postadresse",
"password": "Passord"
},
+ "description": "Tom",
"title": "Google Hangouts p\u00e5logging"
}
},
diff --git a/homeassistant/components/hangouts/.translations/ru.json b/homeassistant/components/hangouts/.translations/ru.json
index c3363215201..6d93ec0d18f 100644
--- a/homeassistant/components/hangouts/.translations/ru.json
+++ b/homeassistant/components/hangouts/.translations/ru.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "already_configured": "Google Hangouts \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d",
+ "already_configured": "\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.",
"unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430"
},
"error": {
diff --git a/homeassistant/components/hangouts/.translations/sv.json b/homeassistant/components/hangouts/.translations/sv.json
index 90bf4e97712..ae03fdbf722 100644
--- a/homeassistant/components/hangouts/.translations/sv.json
+++ b/homeassistant/components/hangouts/.translations/sv.json
@@ -14,6 +14,7 @@
"data": {
"2fa": "2FA Pinkod"
},
+ "description": "Missing english translation",
"title": "Tv\u00e5faktorsautentisering"
},
"user": {
@@ -21,6 +22,7 @@
"email": "E-postadress",
"password": "L\u00f6senord"
},
+ "description": "Missing english translation",
"title": "Google Hangouts-inloggning"
}
},
diff --git a/homeassistant/components/hangouts/const.py b/homeassistant/components/hangouts/const.py
index caae0de169b..5a527fae260 100644
--- a/homeassistant/components/hangouts/const.py
+++ b/homeassistant/components/hangouts/const.py
@@ -3,7 +3,8 @@ import logging
import voluptuous as vol
-from homeassistant.components.notify import ATTR_MESSAGE, ATTR_TARGET
+from homeassistant.components.notify \
+ import ATTR_MESSAGE, ATTR_TARGET, ATTR_DATA
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger('homeassistant.components.hangouts')
@@ -56,10 +57,15 @@ MESSAGE_SEGMENT_SCHEMA = vol.Schema({
vol.Optional('parse_str'): cv.boolean,
vol.Optional('link_target'): cv.string
})
+MESSAGE_DATA_SCHEMA = vol.Schema({
+ vol.Optional('image_file'): cv.string,
+ vol.Optional('image_url'): cv.string
+})
MESSAGE_SCHEMA = vol.Schema({
vol.Required(ATTR_TARGET): [TARGETS_SCHEMA],
- vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA]
+ vol.Required(ATTR_MESSAGE): [MESSAGE_SEGMENT_SCHEMA],
+ vol.Optional(ATTR_DATA): MESSAGE_DATA_SCHEMA
})
INTENT_SCHEMA = vol.All(
diff --git a/homeassistant/components/hangouts/hangouts_bot.py b/homeassistant/components/hangouts/hangouts_bot.py
index 7edc8898c8c..8747bff9ba7 100644
--- a/homeassistant/components/hangouts/hangouts_bot.py
+++ b/homeassistant/components/hangouts/hangouts_bot.py
@@ -1,10 +1,13 @@
"""The Hangouts Bot."""
+import io
import logging
-
+import asyncio
+import aiohttp
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers import dispatcher, intent
from .const import (
- ATTR_MESSAGE, ATTR_TARGET, CONF_CONVERSATIONS, DOMAIN,
+ ATTR_MESSAGE, ATTR_TARGET, ATTR_DATA, CONF_CONVERSATIONS, DOMAIN,
EVENT_HANGOUTS_CONNECTED, EVENT_HANGOUTS_CONVERSATIONS_CHANGED,
EVENT_HANGOUTS_DISCONNECTED, EVENT_HANGOUTS_MESSAGE_RECEIVED,
CONF_MATCHERS, CONF_CONVERSATION_ID,
@@ -146,7 +149,8 @@ class HangoutsBot:
is_error and conv_id in self._error_suppressed_conv_ids):
await self._async_send_message(
[{'text': message, 'parse_str': True}],
- [{CONF_CONVERSATION_ID: conv_id}])
+ [{CONF_CONVERSATION_ID: conv_id}],
+ None)
async def _async_process(self, intents, text, conv_id):
"""Detect a matching intent."""
@@ -203,7 +207,7 @@ class HangoutsBot:
"""Run once when Home Assistant stops."""
await self.async_disconnect()
- async def _async_send_message(self, message, targets):
+ async def _async_send_message(self, message, targets, data):
conversations = []
for target in targets:
conversation = None
@@ -233,10 +237,48 @@ class HangoutsBot:
del segment['parse_str']
messages.append(ChatMessageSegment(**segment))
+ image_file = None
+ if data:
+ if data.get('image_url'):
+ uri = data.get('image_url')
+ try:
+ websession = async_get_clientsession(self.hass)
+ async with websession.get(uri, timeout=5) as response:
+ if response.status != 200:
+ _LOGGER.error(
+ 'Fetch image failed, %s, %s',
+ response.status,
+ response
+ )
+ image_file = None
+ else:
+ image_data = await response.read()
+ image_file = io.BytesIO(image_data)
+ image_file.name = "image.png"
+ except (asyncio.TimeoutError, aiohttp.ClientError) as error:
+ _LOGGER.error(
+ 'Failed to fetch image, %s',
+ type(error)
+ )
+ image_file = None
+ elif data.get('image_file'):
+ uri = data.get('image_file')
+ if self.hass.config.is_allowed_path(uri):
+ try:
+ image_file = open(uri, 'rb')
+ except IOError as error:
+ _LOGGER.error(
+ 'Image file I/O error(%s): %s',
+ error.errno,
+ error.strerror
+ )
+ else:
+ _LOGGER.error('Path "%s" not allowed', uri)
+
if not messages:
return False
for conv in conversations:
- await conv.send_message(messages)
+ await conv.send_message(messages, image_file)
async def _async_list_conversations(self):
import hangups
@@ -261,7 +303,8 @@ class HangoutsBot:
async def async_handle_send_message(self, service):
"""Handle the send_message service."""
await self._async_send_message(service.data[ATTR_MESSAGE],
- service.data[ATTR_TARGET])
+ service.data[ATTR_TARGET],
+ service.data[ATTR_DATA])
async def async_handle_update_users_and_conversations(self, _=None):
"""Handle the update_users_and_conversations service."""
diff --git a/homeassistant/components/hangouts/services.yaml b/homeassistant/components/hangouts/services.yaml
index 5d314bc2479..d07f1d65688 100644
--- a/homeassistant/components/hangouts/services.yaml
+++ b/homeassistant/components/hangouts/services.yaml
@@ -9,4 +9,7 @@ send_message:
example: '[{"id": "UgxrXzVrARmjx_C6AZx4AaABAagBo-6UCw"}, {"name": "Test Conversation"}]'
message:
description: List of message segments, only the "text" field is required in every segment. [Required]
- example: '[{"text":"test", "is_bold": false, "is_italic": false, "is_strikethrough": false, "is_underline": false, "parse_str": false, "link_target": "http://google.com"}, ...]'
\ No newline at end of file
+ example: '[{"text":"test", "is_bold": false, "is_italic": false, "is_strikethrough": false, "is_underline": false, "parse_str": false, "link_target": "http://google.com"}, ...]'
+ data:
+ description: Other options ['image_file' / 'image_url']
+ example: '{ "image_file": "file" } or { "image_url": "url" }'
diff --git a/homeassistant/components/hassio/__init__.py b/homeassistant/components/hassio/__init__.py
index e0356017e3e..9516675480a 100644
--- a/homeassistant/components/hassio/__init__.py
+++ b/homeassistant/components/hassio/__init__.py
@@ -4,7 +4,6 @@ Exposes regular REST commands as services.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/hassio/
"""
-import asyncio
from datetime import timedelta
import logging
import os
@@ -20,7 +19,9 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
from homeassistant.util.dt import utcnow
-from .handler import HassIO
+from .auth import async_setup_auth
+from .handler import HassIO, HassioAPIError
+from .discovery import async_setup_discovery
from .http import HassIOView
_LOGGER = logging.getLogger(__name__)
@@ -134,58 +135,54 @@ def is_hassio(hass):
@bind_hass
-@asyncio.coroutine
-def async_check_config(hass):
+async def async_check_config(hass):
"""Check configuration over Hass.io API."""
hassio = hass.data[DOMAIN]
- result = yield from hassio.check_homeassistant_config()
- if not result:
- return "Hass.io config check API error"
+ try:
+ result = await hassio.check_homeassistant_config()
+ except HassioAPIError as err:
+ _LOGGER.error("Error on Hass.io API: %s", err)
+
if result['result'] == "error":
return result['message']
return None
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Hass.io component."""
- try:
- host = os.environ['HASSIO']
- except KeyError:
- _LOGGER.error("Missing HASSIO environment variable.")
- return False
-
- try:
- os.environ['HASSIO_TOKEN']
- except KeyError:
- _LOGGER.error("Missing HASSIO_TOKEN environment variable.")
+ # Check local setup
+ for env in ('HASSIO', 'HASSIO_TOKEN'):
+ if os.environ.get(env):
+ continue
+ _LOGGER.error("Missing %s environment variable.", env)
return False
+ host = os.environ['HASSIO']
websession = hass.helpers.aiohttp_client.async_get_clientsession()
hass.data[DOMAIN] = hassio = HassIO(hass.loop, websession, host)
- if not (yield from hassio.is_connected()):
+ if not await hassio.is_connected():
_LOGGER.error("Not connected with Hass.io")
return False
store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
- data = yield from store.async_load()
+ data = await store.async_load()
if data is None:
data = {}
refresh_token = None
if 'hassio_user' in data:
- user = yield from hass.auth.async_get_user(data['hassio_user'])
+ user = await hass.auth.async_get_user(data['hassio_user'])
if user and user.refresh_tokens:
refresh_token = list(user.refresh_tokens.values())[0]
if refresh_token is None:
- user = yield from hass.auth.async_create_system_user('Hass.io')
- refresh_token = yield from hass.auth.async_create_refresh_token(user)
+ user = await hass.auth.async_create_system_user('Hass.io')
+ refresh_token = await hass.auth.async_create_refresh_token(user)
data['hassio_user'] = user.id
- yield from store.async_save(data)
+ await store.async_save(data)
# This overrides the normal API call that would be forwarded
development_repo = config.get(DOMAIN, {}).get(CONF_FRONTEND_REPO)
@@ -197,7 +194,7 @@ def async_setup(hass, config):
hass.http.register_view(HassIOView(host, websession))
if 'frontend' in hass.config.components:
- yield from hass.components.panel_custom.async_register_panel(
+ await hass.components.panel_custom.async_register_panel(
frontend_url_path='hassio',
webcomponent_name='hassio-main',
sidebar_title='Hass.io',
@@ -212,13 +209,12 @@ def async_setup(hass, config):
else:
token = None
- yield from hassio.update_hass_api(config.get('http', {}), token)
+ await hassio.update_hass_api(config.get('http', {}), token)
if 'homeassistant' in config:
- yield from hassio.update_hass_timezone(config['homeassistant'])
+ await hassio.update_hass_timezone(config['homeassistant'])
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Handle service calls for Hass.io."""
api_command = MAP_SERVICE_API[service.service][0]
data = service.data.copy()
@@ -233,39 +229,39 @@ def async_setup(hass, config):
payload = data
# Call API
- ret = yield from hassio.send_command(
- api_command.format(addon=addon, snapshot=snapshot),
- payload=payload, timeout=MAP_SERVICE_API[service.service][2]
- )
-
- if not ret or ret['result'] != "ok":
- _LOGGER.error("Error on Hass.io API: %s", ret['message'])
+ try:
+ await hassio.send_command(
+ api_command.format(addon=addon, snapshot=snapshot),
+ payload=payload, timeout=MAP_SERVICE_API[service.service][2]
+ )
+ except HassioAPIError as err:
+ _LOGGER.error("Error on Hass.io API: %s", err)
for service, settings in MAP_SERVICE_API.items():
hass.services.async_register(
DOMAIN, service, async_service_handler, schema=settings[1])
- @asyncio.coroutine
- def update_homeassistant_version(now):
+ async def update_homeassistant_version(now):
"""Update last available Home Assistant version."""
- data = yield from hassio.get_homeassistant_info()
- if data:
+ try:
+ data = await hassio.get_homeassistant_info()
hass.data[DATA_HOMEASSISTANT_VERSION] = data['last_version']
+ except HassioAPIError as err:
+ _LOGGER.warning("Can't read last version: %s", err)
hass.helpers.event.async_track_point_in_utc_time(
update_homeassistant_version, utcnow() + HASSIO_UPDATE_INTERVAL)
# Fetch last version
- yield from update_homeassistant_version(None)
+ await update_homeassistant_version(None)
- @asyncio.coroutine
- def async_handle_core_service(call):
+ async def async_handle_core_service(call):
"""Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP:
- yield from hassio.stop_homeassistant()
+ await hassio.stop_homeassistant()
return
- error = yield from async_check_config(hass)
+ error = await async_check_config(hass)
if error:
_LOGGER.error(error)
hass.components.persistent_notification.async_create(
@@ -274,7 +270,7 @@ def async_setup(hass, config):
return
if call.service == SERVICE_HOMEASSISTANT_RESTART:
- yield from hassio.restart_homeassistant()
+ await hassio.restart_homeassistant()
# Mock core services
for service in (SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
@@ -282,4 +278,10 @@ def async_setup(hass, config):
hass.services.async_register(
HASS_DOMAIN, service, async_handle_core_service)
+ # Init discovery Hass.io feature
+ async_setup_discovery(hass, hassio, config)
+
+ # Init auth Hass.io feature
+ async_setup_auth(hass)
+
return True
diff --git a/homeassistant/components/hassio/auth.py b/homeassistant/components/hassio/auth.py
new file mode 100644
index 00000000000..4be3ba9956c
--- /dev/null
+++ b/homeassistant/components/hassio/auth.py
@@ -0,0 +1,74 @@
+"""Implement the auth feature from Hass.io for Add-ons."""
+import logging
+from ipaddress import ip_address
+import os
+
+from aiohttp import web
+from aiohttp.web_exceptions import HTTPForbidden, HTTPNotFound
+import voluptuous as vol
+
+from homeassistant.core import callback
+import homeassistant.helpers.config_validation as cv
+from homeassistant.exceptions import HomeAssistantError
+from homeassistant.components.http import HomeAssistantView
+from homeassistant.components.http.const import KEY_REAL_IP
+from homeassistant.components.http.data_validator import RequestDataValidator
+
+from .const import ATTR_USERNAME, ATTR_PASSWORD, ATTR_ADDON
+
+_LOGGER = logging.getLogger(__name__)
+
+
+SCHEMA_API_AUTH = vol.Schema({
+ vol.Required(ATTR_USERNAME): cv.string,
+ vol.Required(ATTR_PASSWORD): cv.string,
+ vol.Required(ATTR_ADDON): cv.string,
+}, extra=vol.ALLOW_EXTRA)
+
+
+@callback
+def async_setup_auth(hass):
+ """Auth setup."""
+ hassio_auth = HassIOAuth(hass)
+ hass.http.register_view(hassio_auth)
+
+
+class HassIOAuth(HomeAssistantView):
+ """Hass.io view to handle base part."""
+
+ name = "api:hassio_auth"
+ url = "/api/hassio_auth"
+
+ def __init__(self, hass):
+ """Initialize WebView."""
+ self.hass = hass
+
+ @RequestDataValidator(SCHEMA_API_AUTH)
+ async def post(self, request, data):
+ """Handle new discovery requests."""
+ hassio_ip = os.environ['HASSIO'].split(':')[0]
+ if request[KEY_REAL_IP] != ip_address(hassio_ip):
+ _LOGGER.error(
+ "Invalid auth request from %s", request[KEY_REAL_IP])
+ raise HTTPForbidden()
+
+ await self._check_login(data[ATTR_USERNAME], data[ATTR_PASSWORD])
+ return web.Response(status=200)
+
+ def _get_provider(self):
+ """Return Homeassistant auth provider."""
+ for prv in self.hass.auth.auth_providers:
+ if prv.type == 'homeassistant':
+ return prv
+
+ _LOGGER.error("Can't find Home Assistant auth.")
+ raise HTTPNotFound()
+
+ async def _check_login(self, username, password):
+ """Check User credentials."""
+ provider = self._get_provider()
+
+ try:
+ await provider.async_validate_login(username, password)
+ except HomeAssistantError:
+ raise HTTPForbidden() from None
diff --git a/homeassistant/components/hassio/const.py b/homeassistant/components/hassio/const.py
new file mode 100644
index 00000000000..c539169ebe3
--- /dev/null
+++ b/homeassistant/components/hassio/const.py
@@ -0,0 +1,12 @@
+"""Hass.io const variables."""
+
+ATTR_DISCOVERY = 'discovery'
+ATTR_ADDON = 'addon'
+ATTR_NAME = 'name'
+ATTR_SERVICE = 'service'
+ATTR_CONFIG = 'config'
+ATTR_UUID = 'uuid'
+ATTR_USERNAME = 'username'
+ATTR_PASSWORD = 'password'
+
+X_HASSIO = 'X-HASSIO-KEY'
diff --git a/homeassistant/components/hassio/discovery.py b/homeassistant/components/hassio/discovery.py
new file mode 100644
index 00000000000..4c7c5a6597f
--- /dev/null
+++ b/homeassistant/components/hassio/discovery.py
@@ -0,0 +1,114 @@
+"""Implement the serivces discovery feature from Hass.io for Add-ons."""
+import asyncio
+import logging
+
+from aiohttp import web
+from aiohttp.web_exceptions import HTTPServiceUnavailable
+
+from homeassistant.core import callback, CoreState
+from homeassistant.const import EVENT_HOMEASSISTANT_START
+from homeassistant.components.http import HomeAssistantView
+
+from .handler import HassioAPIError
+from .const import (
+ ATTR_DISCOVERY, ATTR_ADDON, ATTR_NAME, ATTR_SERVICE, ATTR_CONFIG,
+ ATTR_UUID)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+@callback
+def async_setup_discovery(hass, hassio, config):
+ """Discovery setup."""
+ hassio_discovery = HassIODiscovery(hass, hassio, config)
+
+ # Handle exists discovery messages
+ async def async_discovery_start_handler(event):
+ """Process all exists discovery on startup."""
+ try:
+ data = await hassio.retrieve_discovery_messages()
+ except HassioAPIError as err:
+ _LOGGER.error("Can't read discover info: %s", err)
+ return
+
+ jobs = [hassio_discovery.async_process_new(discovery)
+ for discovery in data[ATTR_DISCOVERY]]
+ if jobs:
+ await asyncio.wait(jobs)
+
+ if hass.state == CoreState.running:
+ hass.async_create_task(async_discovery_start_handler(None))
+ else:
+ hass.bus.async_listen_once(
+ EVENT_HOMEASSISTANT_START, async_discovery_start_handler)
+
+ hass.http.register_view(hassio_discovery)
+
+
+class HassIODiscovery(HomeAssistantView):
+ """Hass.io view to handle base part."""
+
+ name = "api:hassio_push:discovery"
+ url = "/api/hassio_push/discovery/{uuid}"
+
+ def __init__(self, hass, hassio, config):
+ """Initialize WebView."""
+ self.hass = hass
+ self.hassio = hassio
+ self.config = config
+
+ async def post(self, request, uuid):
+ """Handle new discovery requests."""
+ # Fetch discovery data and prevent injections
+ try:
+ data = await self.hassio.get_discovery_message(uuid)
+ except HassioAPIError as err:
+ _LOGGER.error("Can't read discovey data: %s", err)
+ raise HTTPServiceUnavailable() from None
+
+ await self.async_process_new(data)
+ return web.Response()
+
+ async def delete(self, request, uuid):
+ """Handle remove discovery requests."""
+ data = request.json()
+
+ await self.async_process_del(data)
+ return web.Response()
+
+ async def async_process_new(self, data):
+ """Process add discovery entry."""
+ service = data[ATTR_SERVICE]
+ config_data = data[ATTR_CONFIG]
+
+ # Read addinional Add-on info
+ try:
+ addon_info = await self.hassio.get_addon_info(data[ATTR_ADDON])
+ except HassioAPIError as err:
+ _LOGGER.error("Can't read add-on info: %s", err)
+ return
+ config_data[ATTR_ADDON] = addon_info[ATTR_NAME]
+
+ # Use config flow
+ await self.hass.config_entries.flow.async_init(
+ service, context={'source': 'hassio'}, data=config_data)
+
+ async def async_process_del(self, data):
+ """Process remove discovery entry."""
+ service = data[ATTR_SERVICE]
+ uuid = data[ATTR_UUID]
+
+ # Check if realy deletet / prevent injections
+ try:
+ data = await self.hassio.get_discovery_message(uuid)
+ except HassioAPIError:
+ pass
+ else:
+ _LOGGER.warning("Retrieve wrong unload for %s", service)
+ return
+
+ # Use config flow
+ for entry in self.hass.config_entries.async_entries(service):
+ if entry.source != 'hassio':
+ continue
+ await self.hass.config_entries.async_remove(entry)
diff --git a/homeassistant/components/hassio/handler.py b/homeassistant/components/hassio/handler.py
index d75529a99b0..91019776eeb 100644
--- a/homeassistant/components/hassio/handler.py
+++ b/homeassistant/components/hassio/handler.py
@@ -16,17 +16,24 @@ from homeassistant.components.http import (
CONF_SSL_CERTIFICATE)
from homeassistant.const import CONF_TIME_ZONE, SERVER_PORT
+from .const import X_HASSIO
+
_LOGGER = logging.getLogger(__name__)
-X_HASSIO = 'X-HASSIO-KEY'
+
+class HassioAPIError(RuntimeError):
+ """Return if a API trow a error."""
def _api_bool(funct):
"""Return a boolean."""
async def _wrapper(*argv, **kwargs):
"""Wrap function."""
- data = await funct(*argv, **kwargs)
- return data and data['result'] == "ok"
+ try:
+ data = await funct(*argv, **kwargs)
+ return data['result'] == "ok"
+ except HassioAPIError:
+ return False
return _wrapper
@@ -36,9 +43,9 @@ def _api_data(funct):
async def _wrapper(*argv, **kwargs):
"""Wrap function."""
data = await funct(*argv, **kwargs)
- if data and data['result'] == "ok":
+ if data['result'] == "ok":
return data['data']
- return None
+ raise HassioAPIError(data['message'])
return _wrapper
@@ -68,6 +75,15 @@ class HassIO:
"""
return self.send_command("/homeassistant/info", method="get")
+ @_api_data
+ def get_addon_info(self, addon):
+ """Return data for a Add-on.
+
+ This method return a coroutine.
+ """
+ return self.send_command(
+ "/addons/{}/info".format(addon), method="get")
+
@_api_bool
def restart_homeassistant(self):
"""Restart Home-Assistant container.
@@ -91,6 +107,22 @@ class HassIO:
"""
return self.send_command("/homeassistant/check", timeout=300)
+ @_api_data
+ def retrieve_discovery_messages(self):
+ """Return all discovery data from Hass.io API.
+
+ This method return a coroutine.
+ """
+ return self.send_command("/discovery", method="get")
+
+ @_api_data
+ def get_discovery_message(self, uuid):
+ """Return a single discovery data message.
+
+ This method return a coroutine.
+ """
+ return self.send_command("/discovery/{}".format(uuid), method="get")
+
@_api_bool
async def update_hass_api(self, http_config, refresh_token):
"""Update Home Assistant API data on Hass.io."""
@@ -120,15 +152,15 @@ class HassIO:
'timezone': core_config.get(CONF_TIME_ZONE)
})
- @asyncio.coroutine
- def send_command(self, command, method="post", payload=None, timeout=10):
+ async def send_command(self, command, method="post", payload=None,
+ timeout=10):
"""Send API command to Hass.io.
This method is a coroutine.
"""
try:
with async_timeout.timeout(timeout, loop=self.loop):
- request = yield from self.websession.request(
+ request = await self.websession.request(
method, "http://{}{}".format(self._ip, command),
json=payload, headers={
X_HASSIO: os.environ.get('HASSIO_TOKEN', "")
@@ -137,9 +169,9 @@ class HassIO:
if request.status not in (200, 400):
_LOGGER.error(
"%s return code %d.", command, request.status)
- return None
+ raise HassioAPIError()
- answer = yield from request.json()
+ answer = await request.json()
return answer
except asyncio.TimeoutError:
@@ -148,4 +180,4 @@ class HassIO:
except aiohttp.ClientError as err:
_LOGGER.error("Client error on %s request %s", command, err)
- return None
+ raise HassioAPIError()
diff --git a/homeassistant/components/hassio/http.py b/homeassistant/components/hassio/http.py
index 55cc7f54787..c3bd18fa9bb 100644
--- a/homeassistant/components/hassio/http.py
+++ b/homeassistant/components/hassio/http.py
@@ -18,9 +18,10 @@ from aiohttp.web_exceptions import HTTPBadGateway
from homeassistant.const import CONTENT_TYPE_TEXT_PLAIN
from homeassistant.components.http import KEY_AUTHENTICATED, HomeAssistantView
+from .const import X_HASSIO
+
_LOGGER = logging.getLogger(__name__)
-X_HASSIO = 'X-HASSIO-KEY'
NO_TIMEOUT = re.compile(
r'^(?:'
@@ -54,15 +55,14 @@ class HassIOView(HomeAssistantView):
self._host = host
self._websession = websession
- @asyncio.coroutine
- def _handle(self, request, path):
+ async def _handle(self, request, path):
"""Route data to Hass.io."""
if _need_auth(path) and not request[KEY_AUTHENTICATED]:
return web.Response(status=401)
- client = yield from self._command_proxy(path, request)
+ client = await self._command_proxy(path, request)
- data = yield from client.read()
+ data = await client.read()
if path.endswith('/logs'):
return _create_response_log(client, data)
return _create_response(client, data)
@@ -70,8 +70,7 @@ class HassIOView(HomeAssistantView):
get = _handle
post = _handle
- @asyncio.coroutine
- def _command_proxy(self, path, request):
+ async def _command_proxy(self, path, request):
"""Return a client request with proxy origin for Hass.io supervisor.
This method is a coroutine.
@@ -83,14 +82,14 @@ class HassIOView(HomeAssistantView):
data = None
headers = {X_HASSIO: os.environ.get('HASSIO_TOKEN', "")}
with async_timeout.timeout(10, loop=hass.loop):
- data = yield from request.read()
+ data = await request.read()
if data:
headers[CONTENT_TYPE] = request.content_type
else:
data = None
method = getattr(self._websession, request.method.lower())
- client = yield from method(
+ client = await method(
"http://{}/{}".format(self._host, path), data=data,
headers=headers, timeout=read_timeout
)
diff --git a/homeassistant/components/homekit/__init__.py b/homeassistant/components/homekit/__init__.py
index 8c12243ee8f..5d7733584be 100644
--- a/homeassistant/components/homekit/__init__.py
+++ b/homeassistant/components/homekit/__init__.py
@@ -24,14 +24,17 @@ from .const import (
BRIDGE_NAME, CONF_AUTO_START, CONF_ENTITY_CONFIG, CONF_FEATURE_LIST,
CONF_FILTER, DEFAULT_AUTO_START, DEFAULT_PORT, DEVICE_CLASS_CO,
DEVICE_CLASS_CO2, DEVICE_CLASS_PM25, DOMAIN, HOMEKIT_FILE,
- SERVICE_HOMEKIT_START, TYPE_OUTLET, TYPE_SWITCH)
+ SERVICE_HOMEKIT_START, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER,
+ TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
from .util import (
show_setup_message, validate_entity_config, validate_media_player_features)
-TYPES = Registry()
+REQUIREMENTS = ['HAP-python==2.2.2']
+
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['HAP-python==2.2.2']
+MAX_DEVICES = 100
+TYPES = Registry()
# #### Driver Status ####
STATUS_READY = 0
@@ -39,8 +42,13 @@ STATUS_RUNNING = 1
STATUS_STOPPED = 2
STATUS_WAIT = 3
-SWITCH_TYPES = {TYPE_OUTLET: 'Outlet',
- TYPE_SWITCH: 'Switch'}
+SWITCH_TYPES = {
+ TYPE_FAUCET: 'Valve',
+ TYPE_OUTLET: 'Outlet',
+ TYPE_SHOWER: 'Valve',
+ TYPE_SPRINKLER: 'Valve',
+ TYPE_SWITCH: 'Switch',
+ TYPE_VALVE: 'Valve'}
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All({
@@ -239,6 +247,10 @@ class HomeKit():
if not self.driver.state.paired:
show_setup_message(self.hass, self.driver.state.pincode)
+ if len(self.bridge.accessories) > MAX_DEVICES:
+ _LOGGER.warning('You have exceeded the device limit, which might '
+ 'cause issues. Consider using the filter option.')
+
_LOGGER.debug('Driver start')
self.hass.add_job(self.driver.start)
self.status = STATUS_RUNNING
diff --git a/homeassistant/components/homekit/const.py b/homeassistant/components/homekit/const.py
index df488d4a73a..617dd3f4f22 100644
--- a/homeassistant/components/homekit/const.py
+++ b/homeassistant/components/homekit/const.py
@@ -32,8 +32,12 @@ BRIDGE_SERIAL_NUMBER = 'homekit.bridge'
MANUFACTURER = 'Home Assistant'
# #### Switch Types ####
+TYPE_FAUCET = 'faucet'
TYPE_OUTLET = 'outlet'
+TYPE_SHOWER = 'shower'
+TYPE_SPRINKLER = 'sprinkler'
TYPE_SWITCH = 'switch'
+TYPE_VALVE = 'valve'
# #### Services ####
SERV_ACCESSORY_INFO = 'AccessoryInformation'
@@ -57,6 +61,7 @@ SERV_SMOKE_SENSOR = 'SmokeSensor'
SERV_SWITCH = 'Switch'
SERV_TEMPERATURE_SENSOR = 'TemperatureSensor'
SERV_THERMOSTAT = 'Thermostat'
+SERV_VALVE = 'Valve'
SERV_WINDOW_COVERING = 'WindowCovering'
# #### Characteristics ####
@@ -85,6 +90,7 @@ CHAR_CURRENT_TEMPERATURE = 'CurrentTemperature'
CHAR_FIRMWARE_REVISION = 'FirmwareRevision'
CHAR_HEATING_THRESHOLD_TEMPERATURE = 'HeatingThresholdTemperature'
CHAR_HUE = 'Hue'
+CHAR_IN_USE = 'InUse'
CHAR_LEAK_DETECTED = 'LeakDetected'
CHAR_LOCK_CURRENT_STATE = 'LockCurrentState'
CHAR_LOCK_TARGET_STATE = 'LockTargetState'
@@ -109,6 +115,7 @@ CHAR_TARGET_POSITION = 'TargetPosition'
CHAR_TARGET_SECURITY_STATE = 'SecuritySystemTargetState'
CHAR_TARGET_TEMPERATURE = 'TargetTemperature'
CHAR_TEMP_DISPLAY_UNITS = 'TemperatureDisplayUnits'
+CHAR_VALVE_TYPE = 'ValveType'
# #### Properties ####
PROP_MAX_VALUE = 'maxValue'
diff --git a/homeassistant/components/homekit/type_switches.py b/homeassistant/components/homekit/type_switches.py
index a5724057eee..82a5d68d644 100644
--- a/homeassistant/components/homekit/type_switches.py
+++ b/homeassistant/components/homekit/type_switches.py
@@ -5,15 +5,29 @@ from pyhap.const import CATEGORY_OUTLET, CATEGORY_SWITCH
from homeassistant.components.switch import DOMAIN
from homeassistant.const import (
- ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
+ ATTR_ENTITY_ID, CONF_TYPE, SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_ON)
from homeassistant.core import split_entity_id
from . import TYPES
from .accessories import HomeAccessory
-from .const import CHAR_ON, CHAR_OUTLET_IN_USE, SERV_OUTLET, SERV_SWITCH
+from .const import (
+ CHAR_ACTIVE, CHAR_IN_USE, CHAR_ON, CHAR_OUTLET_IN_USE, CHAR_VALVE_TYPE,
+ SERV_OUTLET, SERV_SWITCH, SERV_VALVE, TYPE_FAUCET, TYPE_SHOWER,
+ TYPE_SPRINKLER, TYPE_VALVE)
_LOGGER = logging.getLogger(__name__)
+CATEGORY_SPRINKLER = 28
+CATEGORY_FAUCET = 29
+CATEGORY_SHOWER_HEAD = 30
+
+VALVE_TYPE = {
+ TYPE_FAUCET: (CATEGORY_FAUCET, 3),
+ TYPE_SHOWER: (CATEGORY_SHOWER_HEAD, 2),
+ TYPE_SPRINKLER: (CATEGORY_SPRINKLER, 1),
+ TYPE_VALVE: (CATEGORY_FAUCET, 0),
+}
+
@TYPES.register('Outlet')
class Outlet(HomeAccessory):
@@ -80,3 +94,43 @@ class Switch(HomeAccessory):
self.entity_id, current_state)
self.char_on.set_value(current_state)
self.flag_target_state = False
+
+
+@TYPES.register('Valve')
+class Valve(HomeAccessory):
+ """Generate a Valve accessory."""
+
+ def __init__(self, *args):
+ """Initialize a Valve accessory object."""
+ super().__init__(*args)
+ self.flag_target_state = False
+ valve_type = self.config[CONF_TYPE]
+ self.category = VALVE_TYPE[valve_type][0]
+
+ serv_valve = self.add_preload_service(SERV_VALVE)
+ self.char_active = serv_valve.configure_char(
+ CHAR_ACTIVE, value=False, setter_callback=self.set_state)
+ self.char_in_use = serv_valve.configure_char(
+ CHAR_IN_USE, value=False)
+ self.char_valve_type = serv_valve.configure_char(
+ CHAR_VALVE_TYPE, value=VALVE_TYPE[valve_type][1])
+
+ def set_state(self, value):
+ """Move value state to value if call came from HomeKit."""
+ _LOGGER.debug('%s: Set switch state to %s',
+ self.entity_id, value)
+ self.flag_target_state = True
+ self.char_in_use.set_value(value)
+ params = {ATTR_ENTITY_ID: self.entity_id}
+ service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
+ self.hass.services.call(DOMAIN, service, params)
+
+ def update_state(self, new_state):
+ """Update switch state after state changed."""
+ current_state = (new_state.state == STATE_ON)
+ if not self.flag_target_state:
+ _LOGGER.debug('%s: Set current state to %s',
+ self.entity_id, current_state)
+ self.char_active.set_value(current_state)
+ self.char_in_use.set_value(current_state)
+ self.flag_target_state = False
diff --git a/homeassistant/components/homekit/util.py b/homeassistant/components/homekit/util.py
index 9d60530edd7..4dd7396cf8d 100644
--- a/homeassistant/components/homekit/util.py
+++ b/homeassistant/components/homekit/util.py
@@ -11,8 +11,8 @@ import homeassistant.helpers.config_validation as cv
import homeassistant.util.temperature as temp_util
from .const import (
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
- FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, TYPE_OUTLET,
- TYPE_SWITCH)
+ FEATURE_PLAY_PAUSE, FEATURE_PLAY_STOP, FEATURE_TOGGLE_MUTE, TYPE_FAUCET,
+ TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
_LOGGER = logging.getLogger(__name__)
@@ -38,12 +38,17 @@ MEDIA_PLAYER_SCHEMA = vol.Schema({
SWITCH_TYPE_SCHEMA = BASIC_INFO_SCHEMA.extend({
vol.Optional(CONF_TYPE, default=TYPE_SWITCH): vol.All(
- cv.string, vol.In((TYPE_OUTLET, TYPE_SWITCH))),
+ cv.string, vol.In((
+ TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
+ TYPE_SWITCH, TYPE_VALVE))),
})
def validate_entity_config(values):
"""Validate config entry for CONF_ENTITY."""
+ if not isinstance(values, dict):
+ raise vol.Invalid('expected a dictionary')
+
entities = {}
for entity_id, config in values.items():
entity = cv.entity_id(entity_id)
diff --git a/homeassistant/components/homekit_controller/__init__.py b/homeassistant/components/homekit_controller/__init__.py
index 5e24fe82340..5431dd4a61a 100644
--- a/homeassistant/components/homekit_controller/__init__.py
+++ b/homeassistant/components/homekit_controller/__init__.py
@@ -13,6 +13,7 @@ import uuid
from homeassistant.components.discovery import SERVICE_HOMEKIT
from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity
+from homeassistant.helpers.event import call_later
REQUIREMENTS = ['homekit==0.10']
@@ -37,6 +38,13 @@ KNOWN_DEVICES = "{}-devices".format(DOMAIN)
_LOGGER = logging.getLogger(__name__)
+REQUEST_TIMEOUT = 5 # seconds
+RETRY_INTERVAL = 60 # seconds
+
+
+class HomeKitConnectionError(ConnectionError):
+ """Raised when unable to connect to target device."""
+
def homekit_http_send(self, message_body=None, encode_chunked=False):
r"""Send the currently buffered request and clear the buffer.
@@ -89,6 +97,9 @@ class HKDevice():
self.config_num = config_num
self.config = config
self.configurator = hass.components.configurator
+ self.conn = None
+ self.securecon = None
+ self._connection_warning_logged = False
data_dir = os.path.join(hass.config.path(), HOMEKIT_DIR)
if not os.path.isdir(data_dir):
@@ -101,23 +112,35 @@ class HKDevice():
# pylint: disable=protected-access
http.client.HTTPConnection._send_output = homekit_http_send
- self.conn = http.client.HTTPConnection(self.host, port=self.port)
if self.pairing_data is not None:
self.accessory_setup()
else:
self.configure()
+ def connect(self):
+ """Open the connection to the HomeKit device."""
+ # pylint: disable=import-error
+ import homekit
+
+ self.conn = http.client.HTTPConnection(
+ self.host, port=self.port, timeout=REQUEST_TIMEOUT)
+ if self.pairing_data is not None:
+ controllerkey, accessorykey = \
+ homekit.get_session_keys(self.conn, self.pairing_data)
+ self.securecon = homekit.SecureHttp(
+ self.conn.sock, accessorykey, controllerkey)
+
def accessory_setup(self):
"""Handle setup of a HomeKit accessory."""
# pylint: disable=import-error
import homekit
- self.controllerkey, self.accessorykey = \
- homekit.get_session_keys(self.conn, self.pairing_data)
- self.securecon = homekit.SecureHttp(self.conn.sock,
- self.accessorykey,
- self.controllerkey)
- response = self.securecon.get('/accessories')
- data = json.loads(response.read().decode())
+
+ try:
+ data = self.get_json('/accessories')
+ except HomeKitConnectionError:
+ call_later(
+ self.hass, RETRY_INTERVAL, lambda _: self.accessory_setup())
+ return
for accessory in data['accessories']:
serial = get_serial(accessory)
if serial in self.hass.data[KNOWN_ACCESSORIES]:
@@ -135,6 +158,31 @@ class HKDevice():
discovery.load_platform(self.hass, component, DOMAIN,
service_info, self.config)
+ def get_json(self, target):
+ """Get JSON data from the device."""
+ try:
+ if self.conn is None:
+ self.connect()
+ response = self.securecon.get(target)
+ data = json.loads(response.read().decode())
+
+ # After a successful connection, clear the warning logged status
+ self._connection_warning_logged = False
+
+ return data
+ except (ConnectionError, OSError, json.JSONDecodeError) as ex:
+ # Mark connection as failed
+ if not self._connection_warning_logged:
+ _LOGGER.warning("Failed to connect to homekit device",
+ exc_info=ex)
+ self._connection_warning_logged = True
+ else:
+ _LOGGER.debug("Failed to connect to homekit device",
+ exc_info=ex)
+ self.conn = None
+ self.securecon = None
+ raise HomeKitConnectionError() from ex
+
def device_config_callback(self, callback_data):
"""Handle initial pairing."""
# pylint: disable=import-error
@@ -142,6 +190,7 @@ class HKDevice():
pairing_id = str(uuid.uuid4())
code = callback_data.get('code').strip()
try:
+ self.connect()
self.pairing_data = homekit.perform_pair_setup(self.conn, code,
pairing_id)
except homekit.exception.UnavailableError:
@@ -192,7 +241,7 @@ class HomeKitEntity(Entity):
def __init__(self, accessory, devinfo):
"""Initialise a generic HomeKit device."""
self._name = accessory.model
- self._securecon = accessory.securecon
+ self._accessory = accessory
self._aid = devinfo['aid']
self._iid = devinfo['iid']
self._address = "homekit-{}-{}".format(devinfo['serial'], self._iid)
@@ -201,8 +250,10 @@ class HomeKitEntity(Entity):
def update(self):
"""Obtain a HomeKit device's state."""
- response = self._securecon.get('/accessories')
- data = json.loads(response.read().decode())
+ try:
+ data = self._accessory.get_json('/accessories')
+ except HomeKitConnectionError:
+ return
for accessory in data['accessories']:
if accessory['aid'] != self._aid:
continue
@@ -222,6 +273,11 @@ class HomeKitEntity(Entity):
"""Return the name of the device if any."""
return self._name
+ @property
+ def available(self) -> bool:
+ """Return True if entity is available."""
+ return self._accessory.conn is not None
+
def update_characteristics(self, characteristics):
"""Synchronise a HomeKit device state with Home Assistant."""
raise NotImplementedError
@@ -229,7 +285,7 @@ class HomeKitEntity(Entity):
def put_characteristics(self, characteristics):
"""Control a HomeKit device state from Home Assistant."""
body = json.dumps({'characteristics': characteristics})
- self._securecon.put('/characteristics', body)
+ self._accessory.securecon.put('/characteristics', body)
def setup(hass, config):
diff --git a/homeassistant/components/homematic/__init__.py b/homeassistant/components/homematic/__init__.py
index 4e6b3f04ee1..927f86b590d 100644
--- a/homeassistant/components/homematic/__init__.py
+++ b/homeassistant/components/homematic/__init__.py
@@ -4,7 +4,6 @@ Support for HomeMatic devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/homematic/
"""
-import asyncio
from datetime import timedelta
from functools import partial
import logging
@@ -18,9 +17,8 @@ from homeassistant.const import (
from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
-from homeassistant.loader import bind_hass
-REQUIREMENTS = ['pyhomematic==0.1.49']
+REQUIREMENTS = ['pyhomematic==0.1.50']
_LOGGER = logging.getLogger(__name__)
@@ -245,78 +243,6 @@ SCHEMA_SERVICE_PUT_PARAMSET = vol.Schema({
})
-@bind_hass
-def virtualkey(hass, address, channel, param, interface=None):
- """Send virtual keypress to homematic controller."""
- data = {
- ATTR_ADDRESS: address,
- ATTR_CHANNEL: channel,
- ATTR_PARAM: param,
- ATTR_INTERFACE: interface,
- }
-
- hass.services.call(DOMAIN, SERVICE_VIRTUALKEY, data)
-
-
-@bind_hass
-def set_variable_value(hass, entity_id, value):
- """Change value of a Homematic system variable."""
- data = {
- ATTR_ENTITY_ID: entity_id,
- ATTR_VALUE: value,
- }
-
- hass.services.call(DOMAIN, SERVICE_SET_VARIABLE_VALUE, data)
-
-
-@bind_hass
-def set_device_value(hass, address, channel, param, value, interface=None):
- """Call setValue XML-RPC method of supplied interface."""
- data = {
- ATTR_ADDRESS: address,
- ATTR_CHANNEL: channel,
- ATTR_PARAM: param,
- ATTR_VALUE: value,
- ATTR_INTERFACE: interface,
- }
-
- hass.services.call(DOMAIN, SERVICE_SET_DEVICE_VALUE, data)
-
-
-@bind_hass
-def put_paramset(hass, interface, address, paramset_key, paramset):
- """Call putParamset XML-RPC method of supplied interface."""
- data = {
- ATTR_INTERFACE: interface,
- ATTR_ADDRESS: address,
- ATTR_PARAMSET_KEY: paramset_key,
- ATTR_PARAMSET: paramset,
- }
-
- hass.services.call(DOMAIN, SERVICE_PUT_PARAMSET, data)
-
-
-@bind_hass
-def set_install_mode(hass, interface, mode=None, time=None, address=None):
- """Call setInstallMode XML-RPC method of supplied interface."""
- data = {
- key: value for key, value in (
- (ATTR_INTERFACE, interface),
- (ATTR_MODE, mode),
- (ATTR_TIME, time),
- (ATTR_ADDRESS, address)
- ) if value
- }
-
- hass.services.call(DOMAIN, SERVICE_SET_INSTALL_MODE, data)
-
-
-@bind_hass
-def reconnect(hass):
- """Reconnect to CCU/Homegear."""
- hass.services.call(DOMAIN, SERVICE_RECONNECT, {})
-
-
def setup(hass, config):
"""Set up the Homematic component."""
from pyhomematic import HMConnection
@@ -788,10 +714,9 @@ class HMDevice(Entity):
if self._state:
self._state = self._state.upper()
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Load data init callbacks."""
- yield from self.hass.async_add_job(self.link_homematic)
+ await self.hass.async_add_job(self.link_homematic)
@property
def unique_id(self):
diff --git a/homeassistant/components/homematicip_cloud/.translations/hu.json b/homeassistant/components/homematicip_cloud/.translations/hu.json
index f2f22e6a49d..cfb4f5e87fd 100644
--- a/homeassistant/components/homematicip_cloud/.translations/hu.json
+++ b/homeassistant/components/homematicip_cloud/.translations/hu.json
@@ -1,5 +1,21 @@
{
"config": {
+ "abort": {
+ "connection_aborted": "Nem siker\u00fclt csatlakozni a HMIP szerverhez",
+ "unknown": "Unknown error occurred."
+ },
+ "error": {
+ "invalid_pin": "\u00c9rv\u00e9nytelen PIN, pr\u00f3b\u00e1lkozz \u00fajra.",
+ "press_the_button": "Nyomd meg a k\u00e9k gombot.",
+ "register_failed": "Regisztr\u00e1ci\u00f3 nem siker\u00fclt, pr\u00f3b\u00e1ld \u00fajra."
+ },
+ "step": {
+ "init": {
+ "data": {
+ "pin": "Pin k\u00f3d (opcion\u00e1lis)"
+ }
+ }
+ },
"title": "HomematicIP Felh\u0151"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/homematicip_cloud/.translations/ko.json b/homeassistant/components/homematicip_cloud/.translations/ko.json
index 7b8dc8b5087..46ef55c9eca 100644
--- a/homeassistant/components/homematicip_cloud/.translations/ko.json
+++ b/homeassistant/components/homematicip_cloud/.translations/ko.json
@@ -3,7 +3,7 @@
"abort": {
"already_configured": "\uc561\uc138\uc2a4 \ud3ec\uc778\ud2b8\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"connection_aborted": "HMIP \uc11c\ubc84\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
- "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
+ "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
},
"error": {
"invalid_pin": "PIN\uc774 \uc798\ubabb\ub418\uc5c8\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\uc8fc\uc138\uc694.",
diff --git a/homeassistant/components/homematicip_cloud/.translations/ru.json b/homeassistant/components/homematicip_cloud/.translations/ru.json
index ef2b3be4a64..ae67c616f3f 100644
--- a/homeassistant/components/homematicip_cloud/.translations/ru.json
+++ b/homeassistant/components/homematicip_cloud/.translations/ru.json
@@ -3,7 +3,7 @@
"abort": {
"already_configured": "\u0422\u043e\u0447\u043a\u0430 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u0430",
"connection_aborted": "\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 HMIP",
- "unknown": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430"
+ "unknown": "\u0412\u043e\u0437\u043d\u0438\u043a\u043b\u0430 \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430."
},
"error": {
"invalid_pin": "\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 PIN-\u043a\u043e\u0434, \u043f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043f\u043e\u043f\u0440\u043e\u0431\u0443\u0439\u0442\u0435 \u0441\u043d\u043e\u0432\u0430.",
@@ -18,7 +18,7 @@
"name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e, \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0435\u0442\u0441\u044f \u043a\u0430\u043a \u043f\u0440\u0435\u0444\u0438\u043a\u0441 \u0434\u043b\u044f \u043d\u0430\u0437\u0432\u0430\u043d\u0438\u044f \u0432\u0441\u0435\u0445 \u0443\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432)",
"pin": "PIN-\u043a\u043e\u0434 (\u043d\u0435\u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e)"
},
- "title": "\u0412\u044b\u0431\u0438\u0440\u0438\u0442\u0435 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 HomematicIP"
+ "title": "\u0412\u044b\u0431\u0435\u0440\u0438\u0442\u0435 \u0442\u043e\u0447\u043a\u0443 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 HomematicIP"
},
"link": {
"description": "\u041d\u0430\u0436\u043c\u0438\u0442\u0435 \u0441\u0438\u043d\u044e\u044e \u043a\u043d\u043e\u043f\u043a\u0443 \u043d\u0430 \u0442\u043e\u0447\u043a\u0435 \u0434\u043e\u0441\u0442\u0443\u043f\u0430 \u0438 \u043a\u043d\u043e\u043f\u043a\u0443 \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438, \u0447\u0442\u043e\u0431\u044b \u0437\u0430\u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0438\u0440\u043e\u0432\u0430\u0442\u044c HomematicIP \u0432 Home Assistant. \n\n ",
diff --git a/homeassistant/components/homematicip_cloud/device.py b/homeassistant/components/homematicip_cloud/device.py
index 9c335befda4..c43f0e24e2b 100644
--- a/homeassistant/components/homematicip_cloud/device.py
+++ b/homeassistant/components/homematicip_cloud/device.py
@@ -36,7 +36,7 @@ class HomematicipGenericDevice(Entity):
"""Register callbacks."""
self._device.on_update(self._device_changed)
- def _device_changed(self, json, **kwargs):
+ def _device_changed(self, *args, **kwargs):
"""Handle device state changes."""
_LOGGER.debug("Event %s (%s)", self.name, self._device.modelType)
self.async_schedule_update_ha_state()
@@ -61,6 +61,11 @@ class HomematicipGenericDevice(Entity):
"""Device available."""
return not self._device.unreach
+ @property
+ def unique_id(self):
+ """Return a unique ID."""
+ return "{}_{}".format(self.__class__.__name__, self._device.id)
+
@property
def icon(self):
"""Return the icon."""
diff --git a/homeassistant/components/homematicip_cloud/hap.py b/homeassistant/components/homematicip_cloud/hap.py
index 6fddc7c001e..d79e7c1ee14 100644
--- a/homeassistant/components/homematicip_cloud/hap.py
+++ b/homeassistant/components/homematicip_cloud/hap.py
@@ -142,7 +142,7 @@ class HomematicipHAP:
# Explicitly getting an update as device states might have
# changed during access point disconnect."""
- job = self.hass.async_add_job(self.get_state())
+ job = self.hass.async_create_task(self.get_state())
job.add_done_callback(self.get_state_finished)
async def get_state(self):
@@ -161,7 +161,7 @@ class HomematicipHAP:
# so reconnect loop is taking over.
_LOGGER.error(
"Updating state after HMIP access point reconnect failed")
- self.hass.async_add_job(self.home.disable_events())
+ self.hass.async_create_task(self.home.disable_events())
def set_all_to_unavailable(self):
"""Set all devices to unavailable and tell Home Assistant."""
@@ -212,7 +212,7 @@ class HomematicipHAP:
"Retrying in %d seconds",
self.config_entry.data.get(HMIPC_HAPID), retry_delay)
try:
- self._retry_task = self.hass.async_add_job(asyncio.sleep(
+ self._retry_task = self.hass.async_create_task(asyncio.sleep(
retry_delay, loop=self.hass.loop))
await self._retry_task
except asyncio.CancelledError:
diff --git a/homeassistant/components/http/auth.py b/homeassistant/components/http/auth.py
index a18b4de7a10..bcc86b36dbe 100644
--- a/homeassistant/components/http/auth.py
+++ b/homeassistant/components/http/auth.py
@@ -112,6 +112,7 @@ async def async_validate_auth_header(request, api_password=None):
if refresh_token is None:
return False
+ request['hass_refresh_token'] = refresh_token
request['hass_user'] = refresh_token.user
return True
diff --git a/homeassistant/components/huawei_lte.py b/homeassistant/components/huawei_lte.py
index 33da6be56db..ad134d8c60e 100644
--- a/homeassistant/components/huawei_lte.py
+++ b/homeassistant/components/huawei_lte.py
@@ -21,7 +21,7 @@ from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['huawei-lte-api==1.0.12']
+REQUIREMENTS = ['huawei-lte-api==1.0.16']
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=10)
diff --git a/homeassistant/components/hue/.translations/ko.json b/homeassistant/components/hue/.translations/ko.json
index 47306a35414..a4a8051663e 100644
--- a/homeassistant/components/hue/.translations/ko.json
+++ b/homeassistant/components/hue/.translations/ko.json
@@ -1,15 +1,15 @@
{
"config": {
"abort": {
- "all_configured": "\ubaa8\ub4e0 \ud544\ub9bd\uc2a4 Hue \ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4",
- "already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4",
+ "all_configured": "\ubaa8\ub4e0 \ud544\ub9bd\uc2a4 Hue \ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
+ "already_configured": "\ube0c\ub9bf\uc9c0\uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
"cannot_connect": "\ube0c\ub9ac\uc9c0\uc5d0 \uc5f0\uacb0\ud560 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
"discover_timeout": "Hue \ube0c\ub9bf\uc9c0\ub97c \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4",
"no_bridges": "\ubc1c\uacac\ub41c \ud544\ub9bd\uc2a4 Hue \ube0c\ub9bf\uc9c0\uac00 \uc5c6\uc2b5\ub2c8\ub2e4",
- "unknown": "\uc54c \uc218\uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
+ "unknown": "\uc54c \uc218 \uc5c6\ub294 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4"
},
"error": {
- "linking": "\uc54c \uc218\uc5c6\ub294 \uc5f0\uacb0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
+ "linking": "\uc54c \uc218 \uc5c6\ub294 \uc5f0\uacb0 \uc624\ub958\uac00 \ubc1c\uc0dd\ud588\uc2b5\ub2c8\ub2e4.",
"register_failed": "\ub4f1\ub85d\ud558\uc9c0 \ubabb\ud588\uc2b5\ub2c8\ub2e4. \ub2e4\uc2dc \uc2dc\ub3c4\ud574\ubcf4\uc138\uc694"
},
"step": {
diff --git a/homeassistant/components/hue/.translations/no.json b/homeassistant/components/hue/.translations/no.json
index 309e9f6a299..02dd6ef7128 100644
--- a/homeassistant/components/hue/.translations/no.json
+++ b/homeassistant/components/hue/.translations/no.json
@@ -24,6 +24,6 @@
"title": "Link Hub"
}
},
- "title": "Philips Hue Bridge"
+ "title": "Philips Hue"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/hue/.translations/ru.json b/homeassistant/components/hue/.translations/ru.json
index b471dd1a0cd..4b2581dde65 100644
--- a/homeassistant/components/hue/.translations/ru.json
+++ b/homeassistant/components/hue/.translations/ru.json
@@ -2,7 +2,7 @@
"config": {
"abort": {
"all_configured": "\u0412\u0441\u0435 Philips Hue \u0448\u043b\u044e\u0437\u044b \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d\u044b",
- "already_configured": "\u0428\u043b\u044e\u0437 \u0443\u0436\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043d",
+ "already_configured": "\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.",
"cannot_connect": "\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0448\u043b\u044e\u0437\u0443",
"discover_timeout": "\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0438\u0442\u044c \u0448\u043b\u044e\u0437\u044b Philips Hue",
"no_bridges": "\u0428\u043b\u044e\u0437\u044b Philips Hue \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b",
diff --git a/homeassistant/components/hue/__init__.py b/homeassistant/components/hue/__init__.py
index 7a781c99f53..9c28d08054b 100644
--- a/homeassistant/components/hue/__init__.py
+++ b/homeassistant/components/hue/__init__.py
@@ -12,9 +12,9 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import CONF_FILENAME, CONF_HOST
from homeassistant.helpers import (
- aiohttp_client, config_validation as cv, device_registry as dr)
+ config_validation as cv, device_registry as dr)
-from .const import DOMAIN, API_NUPNP
+from .const import DOMAIN
from .bridge import HueBridge
# Loading the config flow file will register the flow
from .config_flow import configured_hosts
@@ -62,37 +62,11 @@ async def async_setup(hass, config):
configured = configured_hosts(hass)
# User has configured bridges
- if CONF_BRIDGES in conf:
- bridges = conf[CONF_BRIDGES]
-
- # Component is part of config but no bridges specified, discover.
- elif DOMAIN in config:
- # discover from nupnp
- websession = aiohttp_client.async_get_clientsession(hass)
-
- async with websession.get(API_NUPNP) as req:
- hosts = await req.json()
-
- bridges = []
- for entry in hosts:
- # Filter out already configured hosts
- if entry['internalipaddress'] in configured:
- continue
-
- # Run through config schema to populate defaults
- bridges.append(BRIDGE_CONFIG_SCHEMA({
- CONF_HOST: entry['internalipaddress'],
- # Careful with using entry['id'] for other reasons. The
- # value is in lowercase but is returned uppercase from hub.
- CONF_FILENAME: '.hue_{}.conf'.format(entry['id']),
- }))
- else:
- # Component not specified in config, we're loaded via discovery
- bridges = []
-
- if not bridges:
+ if CONF_BRIDGES not in conf:
return True
+ bridges = conf[CONF_BRIDGES]
+
for bridge_conf in bridges:
host = bridge_conf[CONF_HOST]
@@ -108,7 +82,7 @@ async def async_setup(hass, config):
# this component we'll have to use hass.async_add_job to avoid a
# deadlock: creating a config entry will set up the component but the
# setup would block till the entry is created!
- hass.async_add_job(hass.config_entries.flow.async_init(
+ hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
data={
'host': bridge_conf[CONF_HOST],
diff --git a/homeassistant/components/hue/bridge.py b/homeassistant/components/hue/bridge.py
index 874c18aaa7e..93241622f0b 100644
--- a/homeassistant/components/hue/bridge.py
+++ b/homeassistant/components/hue/bridge.py
@@ -50,7 +50,7 @@ class HueBridge:
# We are going to fail the config entry setup and initiate a new
# linking procedure. When linking succeeds, it will remove the
# old config entry.
- hass.async_add_job(hass.config_entries.flow.async_init(
+ hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
data={
'host': host,
diff --git a/homeassistant/components/hydrawise.py b/homeassistant/components/hydrawise.py
index 0c4db63034e..5a045a083b3 100644
--- a/homeassistant/components/hydrawise.py
+++ b/homeassistant/components/hydrawise.py
@@ -4,7 +4,6 @@ Support for Hydrawise cloud.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/hydrawise/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -127,8 +126,7 @@ class HydrawiseEntity(Entity):
"""Return the name of the sensor."""
return self._name
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_HYDRAWISE, self._update_callback)
diff --git a/homeassistant/components/ifttt.py b/homeassistant/components/ifttt.py
deleted file mode 100644
index 9497282ab21..00000000000
--- a/homeassistant/components/ifttt.py
+++ /dev/null
@@ -1,74 +0,0 @@
-"""
-Support to trigger Maker IFTTT recipes.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/ifttt/
-"""
-import logging
-
-import requests
-import voluptuous as vol
-
-import homeassistant.helpers.config_validation as cv
-
-REQUIREMENTS = ['pyfttt==0.3']
-
-_LOGGER = logging.getLogger(__name__)
-
-ATTR_EVENT = 'event'
-ATTR_VALUE1 = 'value1'
-ATTR_VALUE2 = 'value2'
-ATTR_VALUE3 = 'value3'
-
-CONF_KEY = 'key'
-
-DOMAIN = 'ifttt'
-
-SERVICE_TRIGGER = 'trigger'
-
-SERVICE_TRIGGER_SCHEMA = vol.Schema({
- vol.Required(ATTR_EVENT): cv.string,
- vol.Optional(ATTR_VALUE1): cv.string,
- vol.Optional(ATTR_VALUE2): cv.string,
- vol.Optional(ATTR_VALUE3): cv.string,
-})
-
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Required(CONF_KEY): cv.string,
- }),
-}, extra=vol.ALLOW_EXTRA)
-
-
-def trigger(hass, event, value1=None, value2=None, value3=None):
- """Trigger a Maker IFTTT recipe."""
- data = {
- ATTR_EVENT: event,
- ATTR_VALUE1: value1,
- ATTR_VALUE2: value2,
- ATTR_VALUE3: value3,
- }
- hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
-
-
-def setup(hass, config):
- """Set up the IFTTT service component."""
- key = config[DOMAIN][CONF_KEY]
-
- def trigger_service(call):
- """Handle IFTTT trigger service calls."""
- event = call.data[ATTR_EVENT]
- value1 = call.data.get(ATTR_VALUE1)
- value2 = call.data.get(ATTR_VALUE2)
- value3 = call.data.get(ATTR_VALUE3)
-
- try:
- import pyfttt
- pyfttt.send_event(key, event, value1, value2, value3)
- except requests.exceptions.RequestException:
- _LOGGER.exception("Error communicating with IFTTT")
-
- hass.services.register(DOMAIN, SERVICE_TRIGGER, trigger_service,
- schema=SERVICE_TRIGGER_SCHEMA)
-
- return True
diff --git a/homeassistant/components/ifttt/.translations/ca.json b/homeassistant/components/ifttt/.translations/ca.json
new file mode 100644
index 00000000000..f93fbe19078
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/ca.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "La vostra inst\u00e0ncia de Home Assistant ha de ser accessible des d'Internet per rebre missatges de IFTTT.",
+ "one_instance_allowed": "Nom\u00e9s cal una sola inst\u00e0ncia."
+ },
+ "create_entry": {
+ "default": "Per enviar esdeveniments a Home Assistant, necessitareu utilitzar l'acci\u00f3 \"Make a web resquest\" de [IFTTT Webhook applet]({applet_url}). \n\n Ompliu la informaci\u00f3 seg\u00fcent: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nConsulteu [la documentaci\u00f3]({docs_url}) sobre com configurar els automatismes per gestionar les dades entrants."
+ },
+ "step": {
+ "user": {
+ "description": "Esteu segur que voleu configurar IFTTT?",
+ "title": "Configureu la miniaplicaci\u00f3 Webhook de IFTTT"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/de.json b/homeassistant/components/ifttt/.translations/de.json
new file mode 100644
index 00000000000..b8fdc819753
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/de.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Auf Ihre Home Assistant-Instanz muss vom Internet aus zugegriffen werden k\u00f6nnen, um IFTTT-Nachrichten zu empfangen.",
+ "one_instance_allowed": "Nur eine einzige Instanz ist notwendig."
+ },
+ "create_entry": {
+ "default": "Um Ereignisse an den Home Assistant zu senden, m\u00fcssen Sie die Aktion \"Eine Webanforderung erstellen\" aus dem [IFTTT Webhook Applet]({applet_url}) ausw\u00e4hlen.\n\nF\u00fcllen Sie folgende Informationen aus: \n- URL: `{webhook_url}`\n- Methode: POST\n- Inhaltstyp: application/json\n\nIn der Dokumentation ({docs_url}) finden Sie Informationen zur Konfiguration der Automation eingehender Daten."
+ },
+ "step": {
+ "user": {
+ "description": "Bist du sicher, dass du IFTTT einrichten m\u00f6chtest?",
+ "title": "Einrichten des IFTTT Webhook Applets"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/en.json b/homeassistant/components/ifttt/.translations/en.json
new file mode 100644
index 00000000000..dae4b24de47
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/en.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive IFTTT messages.",
+ "one_instance_allowed": "Only a single instance is necessary."
+ },
+ "create_entry": {
+ "default": "To send events to Home Assistant, you will need to use the \"Make a web request\" action from the [IFTTT Webhook applet]({applet_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
+ },
+ "step": {
+ "user": {
+ "description": "Are you sure you want to set up IFTTT?",
+ "title": "Set up the IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/fr.json b/homeassistant/components/ifttt/.translations/fr.json
new file mode 100644
index 00000000000..d083a624d70
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/fr.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Votre instance Home Assistant doit \u00eatre accessible \u00e0 partir d'Internet pour recevoir les messages IFTTT.",
+ "one_instance_allowed": "Une seule instance est n\u00e9cessaire."
+ },
+ "create_entry": {
+ "default": "Pour envoyer des \u00e9v\u00e9nements \u00e0 Home Assistant, vous devez utiliser l'action \"Effectuer une demande Web\" \u00e0 partir de [l'applet IFTTT Webhook] ( {applet_url} ). \n\n Remplissez les informations suivantes: \n\n - URL: ` {webhook_url} ` \n - M\u00e9thode: POST \n - Type de contenu: application / json \n\n Voir [la documentation] ( {docs_url} ) pour savoir comment configurer les automatisations pour g\u00e9rer les donn\u00e9es entrantes."
+ },
+ "step": {
+ "user": {
+ "description": "\u00cates-vous s\u00fbr de vouloir configurer IFTTT?",
+ "title": "Configurer l'applet IFTTT Webhook"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/hu.json b/homeassistant/components/ifttt/.translations/hu.json
new file mode 100644
index 00000000000..a131f848d45
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/hu.json
@@ -0,0 +1,11 @@
+{
+ "config": {
+ "step": {
+ "user": {
+ "description": "Biztosan be szeretn\u00e9d \u00e1ll\u00edtani az IFTTT-t?",
+ "title": "IFTTT Webhook Applet be\u00e1ll\u00edt\u00e1sa"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/ko.json b/homeassistant/components/ifttt/.translations/ko.json
new file mode 100644
index 00000000000..832123d5065
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/ko.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "IFTTT \uba54\uc2dc\uc9c0\ub97c \ubc1b\uc73c\ub824\uba74 \uc778\ud130\ub137\uc5d0\uc11c Home Assistant \uc778\uc2a4\ud134\uc2a4\uc5d0 \uc561\uc138\uc2a4 \ud560 \uc218 \uc788\uc5b4\uc57c \ud569\ub2c8\ub2e4.",
+ "one_instance_allowed": "\ud558\ub098\uc758 \uc778\uc2a4\ud134\uc2a4\ub9cc \ud544\uc694\ud569\ub2c8\ub2e4."
+ },
+ "create_entry": {
+ "default": "Home Assistant \ub85c \uc774\ubca4\ud2b8\ub97c \ubcf4\ub0b4\uae30 \uc704\ud574\uc11c\ub294 [IFTTT Webhook \uc560\ud50c\ub9bf]({applet_url}) \uc5d0\uc11c \"Make a web request\" \ub97c \uc0ac\uc6a9\ud574\uc57c \ud569\ub2c8\ub2e4. \n\n \ub2e4\uc74c\uc758 \uc815\ubcf4\ub97c \uc785\ub825\ud574 \uc8fc\uc138\uc694.\n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\n Home Assistant \ub85c \ub4e4\uc5b4\uc624\ub294 \ub370\uc774\ud130\ub97c \ucc98\ub9ac\ud558\uae30 \uc704\ud55c \uc790\ub3d9\ud654\ub97c \uad6c\uc131\ud558\ub294 \ubc29\ubc95\uc740 [\ubcf8 \ubb38\uc11c]({docs_url}) \ub97c \ucc38\uc870\ud574 \uc8fc\uc138\uc694."
+ },
+ "step": {
+ "user": {
+ "description": "IFTTT \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "title": "IFTTT Webhook \uc560\ud50c\ub9bf \uc124\uc815"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/lb.json b/homeassistant/components/ifttt/.translations/lb.json
new file mode 100644
index 00000000000..74e6b4926ef
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/lb.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "\u00c4r Home Assistant Instanz muss iwwert Internet accessibel si fir IFTTT Noriichten z'empf\u00e4nken.",
+ "one_instance_allowed": "N\u00ebmmen eng eenzeg Instanz ass n\u00e9ideg."
+ },
+ "create_entry": {
+ "default": "Fir Evenementer un Home Assistant ze sch\u00e9ckemusst dir d'Aktioun \"Make a web request\" vum [IFTTT Webhook applet] ({applet_url}) benotzen.\n\nGitt folgend Informatiounen un:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nKuckt iech [Dokumentatioun]({docs_url}) w\u00e9i een Automatisatioune mat empfaangene Donn\u00e9e konfigur\u00e9iert."
+ },
+ "step": {
+ "user": {
+ "description": "S\u00e9cher fir IFTTT anzeriichten?",
+ "title": "IFTTT Webhook Applet ariichten"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/nl.json b/homeassistant/components/ifttt/.translations/nl.json
new file mode 100644
index 00000000000..9188b1f6b08
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/nl.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Uw Home Assistant-instantie moet via internet toegankelijk zijn om IFTTT-berichten te ontvangen.",
+ "one_instance_allowed": "Slechts \u00e9\u00e9n instantie is nodig."
+ },
+ "create_entry": {
+ "default": "Om evenementen naar de Home Assistant te verzenden, moet u de actie \"Een webverzoek doen\" gebruiken vanuit de [IFTTT Webhook-applet]({applet_url}). \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [the documentation]({docs_url}) voor informatie over het configureren van automatiseringen om inkomende gegevens te verwerken."
+ },
+ "step": {
+ "user": {
+ "description": "Weet je zeker dat u IFTTT wilt instellen?",
+ "title": "Stel de IFTTT Webhook-applet in"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/no.json b/homeassistant/components/ifttt/.translations/no.json
new file mode 100644
index 00000000000..481ab372e91
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/no.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Din Home Assistant enhet m\u00e5 v\u00e6re tilgjengelig fra internett for \u00e5 kunne motta IFTTT-meldinger.",
+ "one_instance_allowed": "Kun en enkelt forekomst er n\u00f8dvendig."
+ },
+ "create_entry": {
+ "default": "For \u00e5 sende hendelser til Home Assistant, m\u00e5 du bruke \"Make a web request\" handlingen fra [IFTTT Webhook applet]({applet_url}).\n\nFyll ut f\u00f8lgende informasjon:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSe [dokumentasjonen]({docs_url}) om hvordan du konfigurerer automatiseringer for \u00e5 h\u00e5ndtere innkommende data."
+ },
+ "step": {
+ "user": {
+ "description": "Er du sikker p\u00e5 at du vil sette opp IFTTT?",
+ "title": "Sett opp IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/pl.json b/homeassistant/components/ifttt/.translations/pl.json
new file mode 100644
index 00000000000..3c3c2182503
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/pl.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Tw\u00f3j Home Assistant musi by\u0107 dost\u0119pny z Internetu, aby odbiera\u0107 komunikaty IFTTT.",
+ "one_instance_allowed": "Wymagana jest tylko jedna instancja."
+ },
+ "create_entry": {
+ "default": "Aby wys\u0142a\u0107 zdarzenia do Home Assistant'a, b\u0119dziesz musia\u0142 u\u017cy\u0107 akcji \"Make a web request\" z [IFTTT Webhook apletu]({applet_url}). \n\n Podaj nast\u0119puj\u0105ce informacje:\n\n - URL: `{webhook_url}`\n - Metoda: POST\n - Typ zawarto\u015bci: application/json\n\nZapoznaj si\u0119 z [dokumentacj\u0105]({docs_url}) na temat konfiguracji automatyzacji by obs\u0142u\u017cy\u0107 przychodz\u0105ce dane."
+ },
+ "step": {
+ "user": {
+ "description": "Jeste\u015b pewny, \u017ce chcesz skonfigurowa\u0107 IFTTT?",
+ "title": "Konfigurowanie apletu Webhook IFTTT"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/ru.json b/homeassistant/components/ifttt/.translations/ru.json
new file mode 100644
index 00000000000..3c1d7b580e4
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/ru.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "\u0412\u0430\u0448 Home Assistant \u0434\u043e\u043b\u0436\u0435\u043d \u0431\u044b\u0442\u044c \u0434\u043e\u0441\u0442\u0443\u043f\u0435\u043d \u0438\u0437 \u0438\u043d\u0442\u0435\u0440\u043d\u0435\u0442\u0430 \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0439 IFTTT.",
+ "one_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."
+ },
+ "create_entry": {
+ "default": "\u0414\u043b\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u043a\u0438 \u0441\u043e\u0431\u044b\u0442\u0438\u0439 \u0432 Home Assistant \u0432\u044b \u0434\u043e\u043b\u0436\u043d\u044b \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c \u0434\u0435\u0439\u0441\u0442\u0432\u0438\u0435 \"Make a web request\" \u0438\u0437 [IFTTT Webhook applet]({applet_url}).\n\n\u0417\u0430\u043f\u043e\u043b\u043d\u0438\u0442\u0435 \u0441\u043b\u0435\u0434\u0443\u044e\u0449\u0443\u044e \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439]({docs_url}) \u0434\u043b\u044f \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0437\u0430\u0446\u0438\u0439 \u043f\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0430\u044e\u0449\u0438\u0445 \u0434\u0430\u043d\u043d\u044b\u0445."
+ },
+ "step": {
+ "user": {
+ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c IFTTT?",
+ "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/sl.json b/homeassistant/components/ifttt/.translations/sl.json
new file mode 100644
index 00000000000..f5cc1dc572e
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/sl.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Va\u0161 Home Assistent mora biti dostopek prek interneta, da boste lahko prejemali IFTTT sporo\u010dila.",
+ "one_instance_allowed": "Potrebna je samo ena instanca."
+ },
+ "create_entry": {
+ "default": "\u010ce \u017eelite poslati dogodke Home Assistent-u, boste morali uporabiti akcijo \u00bbNaredi spletno zahtevo\u00ab iz orodja [IFTTT Webhook applet] ( {applet_url} ). \n\n Izpolnite naslednje podatke: \n\n - URL: ` {webhook_url} ` \n - Metoda: POST \n - Vrsta vsebine: application/json \n\n Poglejte si [dokumentacijo] ( {docs_url} ) o tem, kako konfigurirati avtomatizacijo za obdelavo dohodnih podatkov."
+ },
+ "step": {
+ "user": {
+ "description": "Ali ste prepri\u010dani, da \u017eelite nastaviti IFTTT?",
+ "title": "Nastavite IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/sv.json b/homeassistant/components/ifttt/.translations/sv.json
new file mode 100644
index 00000000000..883bb042822
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/sv.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Din Home Assistant instans m\u00e5ste vara tillg\u00e4nglig fr\u00e5n internet f\u00f6r att ta emot IFTTT meddelanden.",
+ "one_instance_allowed": "Endast en enda instans \u00e4r n\u00f6dv\u00e4ndig."
+ },
+ "create_entry": {
+ "default": "F\u00f6r att skicka h\u00e4ndelser till Home Assistant m\u00e5ste du anv\u00e4nda \u00e5tg\u00e4rden \"G\u00f6r en webbf\u00f6rfr\u00e5gan\" fr\u00e5n [IFTTT Webhook applet] ( {applet_url} ).\n\n Fyll i f\u00f6ljande information:\n \n - URL: ` {webhook_url} `\n - Metod: POST\n - Inneh\u00e5llstyp: application / json\n\n Se [dokumentationen] ( {docs_url} ) om hur du konfigurerar automatiseringar f\u00f6r att hantera inkommande data."
+ },
+ "step": {
+ "user": {
+ "description": "\u00c4r du s\u00e4ker p\u00e5 att du vill st\u00e4lla in IFTTT?",
+ "title": "St\u00e4lla in IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/zh-Hans.json b/homeassistant/components/ifttt/.translations/zh-Hans.json
new file mode 100644
index 00000000000..e9f7aeb36d4
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/zh-Hans.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "\u60a8\u7684 Home Assistant \u5b9e\u4f8b\u9700\u8981\u53ef\u4ece\u4e92\u8054\u7f51\u8bbf\u95ee\u4ee5\u63a5\u6536 IFTTT \u6d88\u606f\u3002",
+ "one_instance_allowed": "\u53ea\u6709\u4e00\u4e2a\u5b9e\u4f8b\u662f\u5fc5\u9700\u7684\u3002"
+ },
+ "create_entry": {
+ "default": "\u8981\u5411 Home Assistant \u53d1\u9001\u4e8b\u4ef6\uff0c\u60a8\u9700\u8981\u4f7f\u7528 [IFTTT Webhook applet]({applet_url}) \u4e2d\u7684 \"Make a web request\" \u52a8\u4f5c\u3002\n\n\u586b\u5199\u4ee5\u4e0b\u4fe1\u606f\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u6709\u5173\u5982\u4f55\u914d\u7f6e\u81ea\u52a8\u5316\u4ee5\u5904\u7406\u4f20\u5165\u7684\u6570\u636e\uff0c\u8bf7\u53c2\u9605[\u6587\u6863]({docs_url})\u3002"
+ },
+ "step": {
+ "user": {
+ "description": "\u60a8\u786e\u5b9a\u8981\u8bbe\u7f6e IFTTT \u5417\uff1f",
+ "title": "\u8bbe\u7f6e IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/.translations/zh-Hant.json b/homeassistant/components/ifttt/.translations/zh-Hant.json
new file mode 100644
index 00000000000..8610351f43b
--- /dev/null
+++ b/homeassistant/components/ifttt/.translations/zh-Hant.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "abort": {
+ "not_internet_accessible": "Home Assistant \u7269\u4ef6\u5fc5\u9808\u80fd\u5920\u7531\u7db2\u969b\u7db2\u8def\u5b58\u53d6\uff0c\u65b9\u80fd\u63a5\u53d7 IFTTT \u8a0a\u606f\u3002",
+ "one_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u7d44\u7269\u4ef6\u5373\u53ef\u3002"
+ },
+ "create_entry": {
+ "default": "\u6b32\u50b3\u9001\u4e8b\u4ef6\u81f3 Home Assistant\uff0c\u5c07\u9700\u8981\u7531 [IFTTT Webhook applet]({applet_url}) \u547c\u53eb\u300c\u9032\u884c Web \u8acb\u6c42\u300d\u52d5\u4f5c\u3002\n\n\u8acb\u586b\u5beb\u4e0b\u5217\u8cc7\u8a0a\uff1a\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\n\u95dc\u65bc\u5982\u4f55\u50b3\u5165\u8cc7\u6599\u81ea\u52d5\u5316\u8a2d\u5b9a\uff0c\u8acb\u53c3\u95b1[\u6587\u4ef6]({docs_url})\u4ee5\u9032\u884c\u4e86\u89e3\u3002"
+ },
+ "step": {
+ "user": {
+ "description": "\u662f\u5426\u8981\u8a2d\u5b9a IFTTT\uff1f",
+ "title": "\u8a2d\u5b9a IFTTT Webhook Applet"
+ }
+ },
+ "title": "IFTTT"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/ifttt/__init__.py b/homeassistant/components/ifttt/__init__.py
new file mode 100644
index 00000000000..60748d6ff13
--- /dev/null
+++ b/homeassistant/components/ifttt/__init__.py
@@ -0,0 +1,142 @@
+"""
+Support to trigger Maker IFTTT recipes.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/ifttt/
+"""
+from ipaddress import ip_address
+import json
+import logging
+from urllib.parse import urlparse
+
+import requests
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant import config_entries
+from homeassistant.util.network import is_local
+
+REQUIREMENTS = ['pyfttt==0.3']
+DEPENDENCIES = ['webhook']
+
+_LOGGER = logging.getLogger(__name__)
+
+EVENT_RECEIVED = 'ifttt_webhook_received'
+
+ATTR_EVENT = 'event'
+ATTR_VALUE1 = 'value1'
+ATTR_VALUE2 = 'value2'
+ATTR_VALUE3 = 'value3'
+
+CONF_KEY = 'key'
+CONF_WEBHOOK_ID = 'webhook_id'
+
+DOMAIN = 'ifttt'
+
+SERVICE_TRIGGER = 'trigger'
+
+SERVICE_TRIGGER_SCHEMA = vol.Schema({
+ vol.Required(ATTR_EVENT): cv.string,
+ vol.Optional(ATTR_VALUE1): cv.string,
+ vol.Optional(ATTR_VALUE2): cv.string,
+ vol.Optional(ATTR_VALUE3): cv.string,
+})
+
+CONFIG_SCHEMA = vol.Schema({
+ vol.Optional(DOMAIN): vol.Schema({
+ vol.Required(CONF_KEY): cv.string,
+ }),
+}, extra=vol.ALLOW_EXTRA)
+
+
+async def async_setup(hass, config):
+ """Set up the IFTTT service component."""
+ if DOMAIN not in config:
+ return True
+
+ key = config[DOMAIN][CONF_KEY]
+
+ def trigger_service(call):
+ """Handle IFTTT trigger service calls."""
+ event = call.data[ATTR_EVENT]
+ value1 = call.data.get(ATTR_VALUE1)
+ value2 = call.data.get(ATTR_VALUE2)
+ value3 = call.data.get(ATTR_VALUE3)
+
+ try:
+ import pyfttt
+ pyfttt.send_event(key, event, value1, value2, value3)
+ except requests.exceptions.RequestException:
+ _LOGGER.exception("Error communicating with IFTTT")
+
+ hass.services.async_register(DOMAIN, SERVICE_TRIGGER, trigger_service,
+ schema=SERVICE_TRIGGER_SCHEMA)
+
+ return True
+
+
+async def handle_webhook(hass, webhook_id, request):
+ """Handle webhook callback."""
+ body = await request.text()
+ try:
+ data = json.loads(body) if body else {}
+ except ValueError:
+ return None
+
+ if isinstance(data, dict):
+ data['webhook_id'] = webhook_id
+ hass.bus.async_fire(EVENT_RECEIVED, data)
+
+
+async def async_setup_entry(hass, entry):
+ """Configure based on config entry."""
+ hass.components.webhook.async_register(
+ entry.data['webhook_id'], handle_webhook)
+ return True
+
+
+async def async_unload_entry(hass, entry):
+ """Unload a config entry."""
+ hass.components.webhook.async_unregister(entry.data['webhook_id'])
+ return True
+
+
+@config_entries.HANDLERS.register(DOMAIN)
+class ConfigFlow(config_entries.ConfigFlow):
+ """Handle an IFTTT config flow."""
+
+ async def async_step_user(self, user_input=None):
+ """Handle a user initiated set up flow."""
+ if self._async_current_entries():
+ return self.async_abort(reason='one_instance_allowed')
+
+ try:
+ url_parts = urlparse(self.hass.config.api.base_url)
+
+ if is_local(ip_address(url_parts.hostname)):
+ return self.async_abort(reason='not_internet_accessible')
+ except ValueError:
+ # If it's not an IP address, it's very likely publicly accessible
+ pass
+
+ if user_input is None:
+ return self.async_show_form(
+ step_id='user',
+ )
+
+ webhook_id = self.hass.components.webhook.async_generate_id()
+ webhook_url = \
+ self.hass.components.webhook.async_generate_url(webhook_id)
+
+ return self.async_create_entry(
+ title='IFTTT Webhook',
+ data={
+ CONF_WEBHOOK_ID: webhook_id
+ },
+ description_placeholders={
+ 'applet_url': 'https://ifttt.com/maker_webhooks',
+ 'webhook_url': webhook_url,
+ 'docs_url':
+ 'https://www.home-assistant.io/components/ifttt/'
+ }
+ )
diff --git a/homeassistant/components/ifttt/strings.json b/homeassistant/components/ifttt/strings.json
new file mode 100644
index 00000000000..9fc47504b9b
--- /dev/null
+++ b/homeassistant/components/ifttt/strings.json
@@ -0,0 +1,18 @@
+{
+ "config": {
+ "title": "IFTTT",
+ "step": {
+ "user": {
+ "title": "Set up the IFTTT Webhook Applet",
+ "description": "Are you sure you want to set up IFTTT?"
+ }
+ },
+ "abort": {
+ "one_instance_allowed": "Only a single instance is necessary.",
+ "not_internet_accessible": "Your Home Assistant instance needs to be accessible from the internet to receive IFTTT messages."
+ },
+ "create_entry": {
+ "default": "To send events to Home Assistant, you will need to use the \"Make a web request\" action from the [IFTTT Webhook applet]({applet_url}).\n\nFill in the following info:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nSee [the documentation]({docs_url}) on how to configure automations to handle incoming data."
+ }
+ }
+}
diff --git a/homeassistant/components/ihc/ihcdevice.py b/homeassistant/components/ihc/ihcdevice.py
index 93ab81850c9..26ee2fb14fc 100644
--- a/homeassistant/components/ihc/ihcdevice.py
+++ b/homeassistant/components/ihc/ihcdevice.py
@@ -1,5 +1,4 @@
"""Implementation of a base class for all IHC devices."""
-import asyncio
from homeassistant.helpers.entity import Entity
@@ -28,8 +27,7 @@ class IHCDevice(Entity):
self.ihc_note = ''
self.ihc_position = ''
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Add callback for IHC changes."""
self.ihc_controller.add_notify_event(
self.ihc_id, self.on_ihc_change, True)
diff --git a/homeassistant/components/image_processing/__init__.py b/homeassistant/components/image_processing/__init__.py
index 480ec31da7d..84d92361541 100644
--- a/homeassistant/components/image_processing/__init__.py
+++ b/homeassistant/components/image_processing/__init__.py
@@ -17,7 +17,6 @@ from homeassistant.exceptions import HomeAssistantError
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
-from homeassistant.loader import bind_hass
from homeassistant.util.async_ import run_callback_threadsafe
_LOGGER = logging.getLogger(__name__)
@@ -67,20 +66,6 @@ SERVICE_SCAN_SCHEMA = vol.Schema({
})
-@bind_hass
-def scan(hass, entity_id=None):
- """Force process of all cameras or given entity."""
- hass.add_job(async_scan, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_scan(hass, entity_id=None):
- """Force process of all cameras or given entity."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SCAN, data))
-
-
async def async_setup(hass, config):
"""Set up the image processing."""
component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL)
diff --git a/homeassistant/components/image_processing/microsoft_face_detect.py b/homeassistant/components/image_processing/microsoft_face_detect.py
index 7e10d05c5b6..69bd8a8f931 100644
--- a/homeassistant/components/image_processing/microsoft_face_detect.py
+++ b/homeassistant/components/image_processing/microsoft_face_detect.py
@@ -4,7 +4,6 @@ Component that will help set the Microsoft face detect processing.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/image_processing.microsoft_face_detect/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -45,9 +44,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Microsoft Face detection platform."""
api = hass.data[DATA_MICROSOFT_FACE]
attributes = config[CONF_ATTRIBUTES]
@@ -88,15 +86,14 @@ class MicrosoftFaceDetectEntity(ImageProcessingFaceEntity):
"""Return the name of the entity."""
return self._name
- @asyncio.coroutine
- def async_process_image(self, image):
+ async def async_process_image(self, image):
"""Process image.
This method is a coroutine.
"""
face_data = None
try:
- face_data = yield from self._api.call_api(
+ face_data = await self._api.call_api(
'post', 'detect', image, binary=True,
params={'returnFaceAttributes': ",".join(self._attributes)})
diff --git a/homeassistant/components/image_processing/microsoft_face_identify.py b/homeassistant/components/image_processing/microsoft_face_identify.py
index fae11a3dfa9..0a5b7725260 100644
--- a/homeassistant/components/image_processing/microsoft_face_identify.py
+++ b/homeassistant/components/image_processing/microsoft_face_identify.py
@@ -4,7 +4,6 @@ Component that will help set the Microsoft face for verify processing.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/image_processing.microsoft_face_identify/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -29,9 +28,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Microsoft Face identify platform."""
api = hass.data[DATA_MICROSOFT_FACE]
face_group = config[CONF_GROUP]
@@ -80,22 +78,21 @@ class MicrosoftFaceIdentifyEntity(ImageProcessingFaceEntity):
"""Return the name of the entity."""
return self._name
- @asyncio.coroutine
- def async_process_image(self, image):
+ async def async_process_image(self, image):
"""Process image.
This method is a coroutine.
"""
detect = None
try:
- face_data = yield from self._api.call_api(
+ face_data = await self._api.call_api(
'post', 'detect', image, binary=True)
if not face_data:
return
face_ids = [data['faceId'] for data in face_data]
- detect = yield from self._api.call_api(
+ detect = await self._api.call_api(
'post', 'identify',
{'faceIds': face_ids, 'personGroupId': self._face_group})
diff --git a/homeassistant/components/image_processing/openalpr_cloud.py b/homeassistant/components/image_processing/openalpr_cloud.py
index 3daaeb6fa01..dea55b76025 100644
--- a/homeassistant/components/image_processing/openalpr_cloud.py
+++ b/homeassistant/components/image_processing/openalpr_cloud.py
@@ -48,9 +48,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the OpenALPR cloud API platform."""
confidence = config[CONF_CONFIDENCE]
params = {
@@ -101,8 +100,7 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity):
"""Return the name of the entity."""
return self._name
- @asyncio.coroutine
- def async_process_image(self, image):
+ async def async_process_image(self, image):
"""Process image.
This method is a coroutine.
@@ -116,11 +114,11 @@ class OpenAlprCloudEntity(ImageProcessingAlprEntity):
try:
with async_timeout.timeout(self.timeout, loop=self.hass.loop):
- request = yield from websession.post(
+ request = await websession.post(
OPENALPR_API_URL, params=params, data=body
)
- data = yield from request.json()
+ data = await request.json()
if request.status != 200:
_LOGGER.error("Error %d -> %s.",
diff --git a/homeassistant/components/image_processing/openalpr_local.py b/homeassistant/components/image_processing/openalpr_local.py
index 901533d1da4..9d5ebf2e2b9 100644
--- a/homeassistant/components/image_processing/openalpr_local.py
+++ b/homeassistant/components/image_processing/openalpr_local.py
@@ -55,9 +55,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the OpenALPR local platform."""
command = [config[CONF_ALPR_BIN], '-c', config[CONF_REGION], '-']
confidence = config[CONF_CONFIDENCE]
@@ -173,8 +172,7 @@ class OpenAlprLocalEntity(ImageProcessingAlprEntity):
"""Return the name of the entity."""
return self._name
- @asyncio.coroutine
- def async_process_image(self, image):
+ async def async_process_image(self, image):
"""Process image.
This method is a coroutine.
@@ -182,7 +180,7 @@ class OpenAlprLocalEntity(ImageProcessingAlprEntity):
result = {}
vehicles = 0
- alpr = yield from asyncio.create_subprocess_exec(
+ alpr = await asyncio.create_subprocess_exec(
*self._cmd,
loop=self.hass.loop,
stdin=asyncio.subprocess.PIPE,
@@ -191,7 +189,7 @@ class OpenAlprLocalEntity(ImageProcessingAlprEntity):
)
# Send image
- stdout, _ = yield from alpr.communicate(input=image)
+ stdout, _ = await alpr.communicate(input=image)
stdout = io.StringIO(str(stdout, 'utf-8'))
while True:
diff --git a/homeassistant/components/image_processing/seven_segments.py b/homeassistant/components/image_processing/seven_segments.py
index fb6f41b4a49..1f56ba6b572 100644
--- a/homeassistant/components/image_processing/seven_segments.py
+++ b/homeassistant/components/image_processing/seven_segments.py
@@ -4,7 +4,6 @@ Local optical character recognition processing of seven segments displays.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/image_processing.seven_segments/
"""
-import asyncio
import logging
import io
import os
@@ -44,9 +43,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Seven segments OCR platform."""
entities = []
for camera in config[CONF_SOURCE]:
diff --git a/homeassistant/components/input_boolean.py b/homeassistant/components/input_boolean.py
index b9c4dcc685e..18c9808c6d2 100644
--- a/homeassistant/components/input_boolean.py
+++ b/homeassistant/components/input_boolean.py
@@ -46,24 +46,6 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, entity_id):
- """Set input_boolean to True."""
- hass.services.call(DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id})
-
-
-@bind_hass
-def turn_off(hass, entity_id):
- """Set input_boolean to False."""
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
-
-
-@bind_hass
-def toggle(hass, entity_id):
- """Set input_boolean to False."""
- hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
-
-
async def async_setup(hass, config):
"""Set up an input boolean."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
diff --git a/homeassistant/components/input_number.py b/homeassistant/components/input_number.py
index 2f25ca143b8..9630e943bf4 100644
--- a/homeassistant/components/input_number.py
+++ b/homeassistant/components/input_number.py
@@ -4,7 +4,6 @@ Component to offer a way to set a numeric value from a slider or text box.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_number/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -15,7 +14,6 @@ from homeassistant.const import (
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
-from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__)
@@ -82,33 +80,7 @@ CONFIG_SCHEMA = vol.Schema({
}, required=True, extra=vol.ALLOW_EXTRA)
-@bind_hass
-def set_value(hass, entity_id, value):
- """Set input_number to value."""
- hass.services.call(DOMAIN, SERVICE_SET_VALUE, {
- ATTR_ENTITY_ID: entity_id,
- ATTR_VALUE: value,
- })
-
-
-@bind_hass
-def increment(hass, entity_id):
- """Increment value of entity."""
- hass.services.call(DOMAIN, SERVICE_INCREMENT, {
- ATTR_ENTITY_ID: entity_id
- })
-
-
-@bind_hass
-def decrement(hass, entity_id):
- """Decrement value of entity."""
- hass.services.call(DOMAIN, SERVICE_DECREMENT, {
- ATTR_ENTITY_ID: entity_id
- })
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up an input slider."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -146,7 +118,7 @@ def async_setup(hass, config):
'async_decrement'
)
- yield from component.async_add_entities(entities)
+ await component.async_add_entities(entities)
return True
@@ -201,13 +173,12 @@ class InputNumber(Entity):
ATTR_MODE: self._mode,
}
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
if self._current_value is not None:
return
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
value = state and float(state.state)
# Check against None because value can be 0
@@ -216,8 +187,7 @@ class InputNumber(Entity):
else:
self._current_value = self._minimum
- @asyncio.coroutine
- def async_set_value(self, value):
+ async def async_set_value(self, value):
"""Set new value."""
num_value = float(value)
if num_value < self._minimum or num_value > self._maximum:
@@ -225,10 +195,9 @@ class InputNumber(Entity):
num_value, self._minimum, self._maximum)
return
self._current_value = num_value
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_increment(self):
+ async def async_increment(self):
"""Increment value."""
new_value = self._current_value + self._step
if new_value > self._maximum:
@@ -236,10 +205,9 @@ class InputNumber(Entity):
new_value, self._minimum, self._maximum)
return
self._current_value = new_value
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_decrement(self):
+ async def async_decrement(self):
"""Decrement value."""
new_value = self._current_value - self._step
if new_value < self._minimum:
@@ -247,4 +215,4 @@ class InputNumber(Entity):
new_value, self._minimum, self._maximum)
return
self._current_value = new_value
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
diff --git a/homeassistant/components/input_select.py b/homeassistant/components/input_select.py
index 04e9b04787c..b8398e1be3d 100644
--- a/homeassistant/components/input_select.py
+++ b/homeassistant/components/input_select.py
@@ -4,13 +4,11 @@ Component to offer a way to select an option from a list.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_select/
"""
-import asyncio
import logging
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID, CONF_ICON, CONF_NAME
-from homeassistant.loader import bind_hass
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -78,42 +76,7 @@ CONFIG_SCHEMA = vol.Schema({
}, required=True, extra=vol.ALLOW_EXTRA)
-@bind_hass
-def select_option(hass, entity_id, option):
- """Set value of input_select."""
- hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
- ATTR_ENTITY_ID: entity_id,
- ATTR_OPTION: option,
- })
-
-
-@bind_hass
-def select_next(hass, entity_id):
- """Set next value of input_select."""
- hass.services.call(DOMAIN, SERVICE_SELECT_NEXT, {
- ATTR_ENTITY_ID: entity_id,
- })
-
-
-@bind_hass
-def select_previous(hass, entity_id):
- """Set previous value of input_select."""
- hass.services.call(DOMAIN, SERVICE_SELECT_PREVIOUS, {
- ATTR_ENTITY_ID: entity_id,
- })
-
-
-@bind_hass
-def set_options(hass, entity_id, options):
- """Set options of input_select."""
- hass.services.call(DOMAIN, SERVICE_SET_OPTIONS, {
- ATTR_ENTITY_ID: entity_id,
- ATTR_OPTIONS: options,
- })
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up an input select."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -149,7 +112,7 @@ def async_setup(hass, config):
'async_set_options'
)
- yield from component.async_add_entities(entities)
+ await component.async_add_entities(entities)
return True
@@ -164,13 +127,12 @@ class InputSelect(Entity):
self._options = options
self._icon = icon
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Run when entity about to be added."""
if self._current_option is not None:
return
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
if not state or state.state not in self._options:
self._current_option = self._options[0]
else:
@@ -203,27 +165,24 @@ class InputSelect(Entity):
ATTR_OPTIONS: self._options,
}
- @asyncio.coroutine
- def async_select_option(self, option):
+ async def async_select_option(self, option):
"""Select new option."""
if option not in self._options:
_LOGGER.warning('Invalid option: %s (possible options: %s)',
option, ', '.join(self._options))
return
self._current_option = option
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_offset_index(self, offset):
+ async def async_offset_index(self, offset):
"""Offset current index."""
current_index = self._options.index(self._current_option)
new_index = (current_index + offset) % len(self._options)
self._current_option = self._options[new_index]
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
- @asyncio.coroutine
- def async_set_options(self, options):
+ async def async_set_options(self, options):
"""Set options."""
self._current_option = options[0]
self._options = options
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
diff --git a/homeassistant/components/input_text.py b/homeassistant/components/input_text.py
index 2cb4f58a130..956d9a6466d 100644
--- a/homeassistant/components/input_text.py
+++ b/homeassistant/components/input_text.py
@@ -4,7 +4,6 @@ Component to offer a way to enter a value into a text box.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/input_text/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -12,7 +11,6 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_UNIT_OF_MEASUREMENT, CONF_ICON, CONF_NAME, CONF_MODE)
-from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.restore_state import async_get_last_state
@@ -74,17 +72,7 @@ CONFIG_SCHEMA = vol.Schema({
}, required=True, extra=vol.ALLOW_EXTRA)
-@bind_hass
-def set_value(hass, entity_id, value):
- """Set input_text to value."""
- hass.services.call(DOMAIN, SERVICE_SET_VALUE, {
- ATTR_ENTITY_ID: entity_id,
- ATTR_VALUE: value,
- })
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up an input text box."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
@@ -112,7 +100,7 @@ def async_setup(hass, config):
'async_set_value'
)
- yield from component.async_add_entities(entities)
+ await component.async_add_entities(entities)
return True
@@ -167,25 +155,23 @@ class InputText(Entity):
ATTR_MODE: self._mode,
}
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Run when entity about to be added to hass."""
if self._current_value is not None:
return
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
value = state and state.state
# Check against None because value can be 0
if value is not None and self._minimum <= len(value) <= self._maximum:
self._current_value = value
- @asyncio.coroutine
- def async_set_value(self, value):
+ async def async_set_value(self, value):
"""Select new value."""
if len(value) < self._minimum or len(value) > self._maximum:
_LOGGER.warning("Invalid value: %s (length range %s - %s)",
value, self._minimum, self._maximum)
return
self._current_value = value
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
diff --git a/homeassistant/components/insteon/__init__.py b/homeassistant/components/insteon/__init__.py
index 749d167e6de..924baeaa560 100644
--- a/homeassistant/components/insteon/__init__.py
+++ b/homeassistant/components/insteon/__init__.py
@@ -4,7 +4,6 @@ Support for INSTEON Modems (PLM and Hub).
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon/
"""
-import asyncio
import collections
import logging
from typing import Dict
@@ -149,8 +148,7 @@ X10_HOUSECODE_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the connection to the modem."""
import insteonplm
@@ -292,7 +290,7 @@ def async_setup(hass, config):
if host:
_LOGGER.info('Connecting to Insteon Hub on %s', host)
- conn = yield from insteonplm.Connection.create(
+ conn = await insteonplm.Connection.create(
host=host,
port=ip_port,
username=username,
@@ -302,7 +300,7 @@ def async_setup(hass, config):
workdir=hass.config.config_dir)
else:
_LOGGER.info("Looking for Insteon PLM on %s", port)
- conn = yield from insteonplm.Connection.create(
+ conn = await insteonplm.Connection.create(
device=port,
loop=hass.loop,
workdir=hass.config.config_dir)
@@ -494,8 +492,7 @@ class InsteonEntity(Entity):
deviceid.human, group, val)
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register INSTEON update events."""
_LOGGER.debug('Tracking updates for device %s group %d statename %s',
self.address, self.group,
diff --git a/homeassistant/components/insteon_plm.py b/homeassistant/components/insteon_plm.py
index b89e5679a63..b3011e9d7bd 100644
--- a/homeassistant/components/insteon_plm.py
+++ b/homeassistant/components/insteon_plm.py
@@ -4,14 +4,12 @@ Support for INSTEON PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/insteon_plm/
"""
-import asyncio
import logging
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the insteon_plm component.
This component is deprecated as of release 0.77 and should be removed in
diff --git a/homeassistant/components/intent_script.py b/homeassistant/components/intent_script.py
index 91489e188c5..9c63141e496 100644
--- a/homeassistant/components/intent_script.py
+++ b/homeassistant/components/intent_script.py
@@ -1,5 +1,4 @@
"""Handle intents with scripts."""
-import asyncio
import copy
import logging
@@ -45,8 +44,7 @@ CONFIG_SCHEMA = vol.Schema({
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Activate Alexa component."""
intents = copy.deepcopy(config[DOMAIN])
template.attach(hass, intents)
@@ -69,8 +67,7 @@ class ScriptIntentHandler(intent.IntentHandler):
self.intent_type = intent_type
self.config = config
- @asyncio.coroutine
- def async_handle(self, intent_obj):
+ async def async_handle(self, intent_obj):
"""Handle the intent."""
speech = self.config.get(CONF_SPEECH)
card = self.config.get(CONF_CARD)
@@ -81,9 +78,9 @@ class ScriptIntentHandler(intent.IntentHandler):
if action is not None:
if is_async_action:
- intent_obj.hass.async_add_job(action.async_run(slots))
+ intent_obj.hass.async_create_task(action.async_run(slots))
else:
- yield from action.async_run(slots)
+ await action.async_run(slots)
response = intent_obj.create_response()
diff --git a/homeassistant/components/introduction.py b/homeassistant/components/introduction.py
index cc3e00c4475..17de7fcd6ca 100644
--- a/homeassistant/components/introduction.py
+++ b/homeassistant/components/introduction.py
@@ -4,7 +4,6 @@ Component that will help guide the user taking its first steps.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/introduction/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -16,8 +15,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config=None):
+async def async_setup(hass, config=None):
"""Set up the introduction component."""
log = logging.getLogger(__name__)
log.info("""
diff --git a/homeassistant/components/ios/.translations/de.json b/homeassistant/components/ios/.translations/de.json
index e9e592d18c2..18ffda135ee 100644
--- a/homeassistant/components/ios/.translations/de.json
+++ b/homeassistant/components/ios/.translations/de.json
@@ -5,8 +5,10 @@
},
"step": {
"confirm": {
- "description": "M\u00f6chtest du die Home Assistant iOS-Komponente einrichten?"
+ "description": "M\u00f6chtest du die Home Assistant iOS-Komponente einrichten?",
+ "title": "Home Assistant iOS"
}
- }
+ },
+ "title": "Home Assistant iOS"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/ios/.translations/ru.json b/homeassistant/components/ios/.translations/ru.json
index 7030f18b729..fdcc964a0e6 100644
--- a/homeassistant/components/ios/.translations/ru.json
+++ b/homeassistant/components/ios/.translations/ru.json
@@ -1,11 +1,11 @@
{
"config": {
"abort": {
- "single_instance_allowed": "\u0414\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Home Assistant iOS."
+ "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."
},
"step": {
"confirm": {
- "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Home Assistant iOS?",
+ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 Home Assistant iOS?",
"title": "Home Assistant iOS"
}
},
diff --git a/homeassistant/components/ios/.translations/sv.json b/homeassistant/components/ios/.translations/sv.json
index 6806f9bab90..5a605ed8987 100644
--- a/homeassistant/components/ios/.translations/sv.json
+++ b/homeassistant/components/ios/.translations/sv.json
@@ -1,5 +1,8 @@
{
"config": {
+ "abort": {
+ "single_instance_allowed": "Endast en enda konfiguration av Home Assistant iOS \u00e4r n\u00f6dv\u00e4ndig."
+ },
"step": {
"confirm": {
"description": "Vill du konfigurera Home Assistants iOS komponent?",
diff --git a/homeassistant/components/ios/__init__.py b/homeassistant/components/ios/__init__.py
index a67be0a63de..0b1282b605a 100644
--- a/homeassistant/components/ios/__init__.py
+++ b/homeassistant/components/ios/__init__.py
@@ -4,7 +4,6 @@ Native Home Assistant iOS app component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/ecosystem/ios/
"""
-import asyncio
import logging
import datetime
@@ -259,11 +258,10 @@ class iOSIdentifyDeviceView(HomeAssistantView):
"""Initiliaze the view."""
self._config_path = config_path
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Handle the POST request for device identification."""
try:
- data = yield from request.json()
+ data = await request.json()
except ValueError:
return self.json_message("Invalid JSON", HTTP_BAD_REQUEST)
diff --git a/homeassistant/components/isy994.py b/homeassistant/components/isy994.py
index d8afb7be5da..9b539b0690a 100644
--- a/homeassistant/components/isy994.py
+++ b/homeassistant/components/isy994.py
@@ -4,7 +4,6 @@ Support the ISY-994 controllers.
For configuration details please visit the documentation for this component at
https://home-assistant.io/components/isy994/
"""
-import asyncio
from collections import namedtuple
import logging
from urllib.parse import urlparse
@@ -414,8 +413,7 @@ class ISYDevice(Entity):
self._change_handler = None
self._control_handler = None
- @asyncio.coroutine
- def async_added_to_hass(self) -> None:
+ async def async_added_to_hass(self) -> None:
"""Subscribe to the node change events."""
self._change_handler = self._node.status.subscribe(
'changed', self.on_update)
diff --git a/homeassistant/components/keyboard.py b/homeassistant/components/keyboard.py
index 697b5b6873c..58bb1fa5f42 100644
--- a/homeassistant/components/keyboard.py
+++ b/homeassistant/components/keyboard.py
@@ -18,36 +18,6 @@ DOMAIN = 'keyboard'
TAP_KEY_SCHEMA = vol.Schema({})
-def volume_up(hass):
- """Press the keyboard button for volume up."""
- hass.services.call(DOMAIN, SERVICE_VOLUME_UP)
-
-
-def volume_down(hass):
- """Press the keyboard button for volume down."""
- hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN)
-
-
-def volume_mute(hass):
- """Press the keyboard button for muting volume."""
- hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE)
-
-
-def media_play_pause(hass):
- """Press the keyboard button for play/pause."""
- hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE)
-
-
-def media_next_track(hass):
- """Press the keyboard button for next track."""
- hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK)
-
-
-def media_prev_track(hass):
- """Press the keyboard button for prev track."""
- hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK)
-
-
def setup(hass, config):
"""Listen for keyboard events."""
# pylint: disable=import-error
diff --git a/homeassistant/components/lifx/.translations/ca.json b/homeassistant/components/lifx/.translations/ca.json
new file mode 100644
index 00000000000..b3896d49e1d
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/ca.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "No s'han trobat dispositius LIFX a la xarxa.",
+ "single_instance_allowed": "Nom\u00e9s \u00e9s possible una \u00fanica configuraci\u00f3 de LIFX."
+ },
+ "step": {
+ "confirm": {
+ "description": "Voleu configurar LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/en.json b/homeassistant/components/lifx/.translations/en.json
new file mode 100644
index 00000000000..64fdc7516ea
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/en.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "No LIFX devices found on the network.",
+ "single_instance_allowed": "Only a single configuration of LIFX is possible."
+ },
+ "step": {
+ "confirm": {
+ "description": "Do you want to set up LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/fr.json b/homeassistant/components/lifx/.translations/fr.json
new file mode 100644
index 00000000000..96a264fa6b2
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/fr.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "Aucun p\u00e9riph\u00e9rique LIFX trouv\u00e9 sur le r\u00e9seau.",
+ "single_instance_allowed": "Une seule configuration de LIFX est possible."
+ },
+ "step": {
+ "confirm": {
+ "description": "Voulez-vous configurer LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/ko.json b/homeassistant/components/lifx/.translations/ko.json
new file mode 100644
index 00000000000..c795c54badb
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/ko.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "LIFX \uc7a5\uce58\uac00 \ub124\ud2b8\uc6cc\ud06c\uc5d0\uc11c \ubc1c\uacac\ub418\uc9c0 \uc54a\uc558\uc2b5\ub2c8\ub2e4.",
+ "single_instance_allowed": "\ud558\ub098\uc758 LIFX \ub9cc \uad6c\uc131 \ud560 \uc218 \uc788\uc2b5\ub2c8\ub2e4."
+ },
+ "step": {
+ "confirm": {
+ "description": "LIFX \ub97c \uc124\uc815\ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/lb.json b/homeassistant/components/lifx/.translations/lb.json
new file mode 100644
index 00000000000..2e033280e46
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/lb.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "Keng LIFX Apparater am Netzwierk fonnt.",
+ "single_instance_allowed": "N\u00ebmmen eng eenzeg Konfiguratioun vun LIFX ass erlaabt."
+ },
+ "step": {
+ "confirm": {
+ "description": "Soll LIFX konfigur\u00e9iert ginn?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/nl.json b/homeassistant/components/lifx/.translations/nl.json
new file mode 100644
index 00000000000..a23502729d6
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/nl.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "Geen LIFX-apparaten gevonden op het netwerk.",
+ "single_instance_allowed": "Slechts een enkele configuratie van LIFX is mogelijk."
+ },
+ "step": {
+ "confirm": {
+ "description": "Wilt u LIFX instellen?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/pl.json b/homeassistant/components/lifx/.translations/pl.json
new file mode 100644
index 00000000000..f13c0b54bbd
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/pl.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "Nie znaleziono w sieci urz\u0105dze\u0144 LIFX.",
+ "single_instance_allowed": "Wymagana jest tylko jedna konfiguracja LIFX."
+ },
+ "step": {
+ "confirm": {
+ "description": "Czy chcesz skonfigurowa\u0107 LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/ru.json b/homeassistant/components/lifx/.translations/ru.json
new file mode 100644
index 00000000000..5ad351b7a90
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/ru.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 LIFX \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b \u0432 \u0441\u0435\u0442\u0438.",
+ "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"
+ },
+ "step": {
+ "confirm": {
+ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/sl.json b/homeassistant/components/lifx/.translations/sl.json
new file mode 100644
index 00000000000..492bf9010dd
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/sl.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "V omre\u017eju ni najdenih naprav LIFX.",
+ "single_instance_allowed": "Mo\u017ena je samo ena konfiguracija LIFX-a."
+ },
+ "step": {
+ "confirm": {
+ "description": "Ali \u017eelite nastaviti LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/sv.json b/homeassistant/components/lifx/.translations/sv.json
new file mode 100644
index 00000000000..a935e209bb4
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/sv.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "Inga LIFX enheter hittas i n\u00e4tverket.",
+ "single_instance_allowed": "Endast en enda konfiguration av LIFX \u00e4r m\u00f6jlig."
+ },
+ "step": {
+ "confirm": {
+ "description": "Vill du st\u00e4lla in LIFX?",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/zh-Hans.json b/homeassistant/components/lifx/.translations/zh-Hans.json
new file mode 100644
index 00000000000..bc9375d807d
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/zh-Hans.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "\u6ca1\u6709\u5728\u7f51\u7edc\u4e0a\u627e\u5230 LIFX \u8bbe\u5907\u3002",
+ "single_instance_allowed": "LIFX \u53ea\u80fd\u914d\u7f6e\u4e00\u6b21\u3002"
+ },
+ "step": {
+ "confirm": {
+ "description": "\u60a8\u60f3\u8981\u914d\u7f6e LIFX \u5417\uff1f",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/lifx/.translations/zh-Hant.json b/homeassistant/components/lifx/.translations/zh-Hant.json
new file mode 100644
index 00000000000..4c66f0d0133
--- /dev/null
+++ b/homeassistant/components/lifx/.translations/zh-Hant.json
@@ -0,0 +1,15 @@
+{
+ "config": {
+ "abort": {
+ "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 LIFX \u88dd\u7f6e\u3002",
+ "single_instance_allowed": "\u50c5\u80fd\u8a2d\u5b9a\u4e00\u7d44 LIFX\u3002"
+ },
+ "step": {
+ "confirm": {
+ "description": "\u662f\u5426\u8981\u8a2d\u5b9a LIFX\uff1f",
+ "title": "LIFX"
+ }
+ },
+ "title": "LIFX"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/light/__init__.py b/homeassistant/components/light/__init__.py
index bc7f136322b..41dbbcd6d0c 100644
--- a/homeassistant/components/light/__init__.py
+++ b/homeassistant/components/light/__init__.py
@@ -17,7 +17,6 @@ from homeassistant.components.group import \
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
STATE_ON)
-from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.helpers.entity import ToggleEntity
@@ -142,90 +141,6 @@ def is_on(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, entity_id=None, transition=None, brightness=None,
- brightness_pct=None, rgb_color=None, xy_color=None, hs_color=None,
- color_temp=None, kelvin=None, white_value=None,
- profile=None, flash=None, effect=None, color_name=None):
- """Turn all or specified light on."""
- hass.add_job(
- async_turn_on, hass, entity_id, transition, brightness, brightness_pct,
- rgb_color, xy_color, hs_color, color_temp, kelvin, white_value,
- profile, flash, effect, color_name)
-
-
-@callback
-@bind_hass
-def async_turn_on(hass, entity_id=None, transition=None, brightness=None,
- brightness_pct=None, rgb_color=None, xy_color=None,
- hs_color=None, color_temp=None, kelvin=None,
- white_value=None, profile=None, flash=None, effect=None,
- color_name=None):
- """Turn all or specified light on."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_PROFILE, profile),
- (ATTR_TRANSITION, transition),
- (ATTR_BRIGHTNESS, brightness),
- (ATTR_BRIGHTNESS_PCT, brightness_pct),
- (ATTR_RGB_COLOR, rgb_color),
- (ATTR_XY_COLOR, xy_color),
- (ATTR_HS_COLOR, hs_color),
- (ATTR_COLOR_TEMP, color_temp),
- (ATTR_KELVIN, kelvin),
- (ATTR_WHITE_VALUE, white_value),
- (ATTR_FLASH, flash),
- (ATTR_EFFECT, effect),
- (ATTR_COLOR_NAME, color_name),
- ] if value is not None
- }
-
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
-
-
-@bind_hass
-def turn_off(hass, entity_id=None, transition=None):
- """Turn all or specified light off."""
- hass.add_job(async_turn_off, hass, entity_id, transition)
-
-
-@callback
-@bind_hass
-def async_turn_off(hass, entity_id=None, transition=None):
- """Turn all or specified light off."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_TRANSITION, transition),
- ] if value is not None
- }
-
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_TURN_OFF, data))
-
-
-@callback
-@bind_hass
-def async_toggle(hass, entity_id=None, transition=None):
- """Toggle all or specified light."""
- data = {
- key: value for key, value in [
- (ATTR_ENTITY_ID, entity_id),
- (ATTR_TRANSITION, transition),
- ] if value is not None
- }
-
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_TOGGLE, data))
-
-
-@bind_hass
-def toggle(hass, entity_id=None, transition=None):
- """Toggle all or specified light."""
- hass.add_job(async_toggle, hass, entity_id, transition)
-
-
def preprocess_turn_on_alternatives(params):
"""Process extra data for turn light on request."""
profile = Profiles.get(params.pop(ATTR_PROFILE, None))
diff --git a/homeassistant/components/light/ads.py b/homeassistant/components/light/ads.py
index 65569f6b2d5..0d97efa16a2 100644
--- a/homeassistant/components/light/ads.py
+++ b/homeassistant/components/light/ads.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/light.ads/
"""
-import asyncio
import logging
import voluptuous as vol
from homeassistant.components.light import Light, ATTR_BRIGHTNESS, \
@@ -50,8 +49,7 @@ class AdsLight(Light):
self.ads_var_enable = ads_var_enable
self.ads_var_brightness = ads_var_brightness
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register device notification."""
def update_on_state(name, value):
"""Handle device notifications for state."""
diff --git a/homeassistant/components/light/hue.py b/homeassistant/components/light/hue.py
index 958abaca033..686fc01caf9 100644
--- a/homeassistant/components/light/hue.py
+++ b/homeassistant/components/light/hue.py
@@ -7,6 +7,7 @@ https://home-assistant.io/components/light.hue/
import asyncio
from datetime import timedelta
import logging
+from time import monotonic
import random
import async_timeout
@@ -159,18 +160,23 @@ async def async_update_items(hass, bridge, async_add_entities,
import aiohue
if is_group:
+ api_type = 'group'
api = bridge.api.groups
else:
+ api_type = 'light'
api = bridge.api.lights
try:
+ start = monotonic()
with async_timeout.timeout(4):
await api.update()
- except (asyncio.TimeoutError, aiohue.AiohueException):
+ except (asyncio.TimeoutError, aiohue.AiohueException) as err:
+ _LOGGER.debug('Failed to fetch %s: %s', api_type, err)
+
if not bridge.available:
return
- _LOGGER.error('Unable to reach bridge %s', bridge.host)
+ _LOGGER.error('Unable to reach bridge %s (%s)', bridge.host, err)
bridge.available = False
for light_id, light in current.items():
@@ -179,6 +185,10 @@ async def async_update_items(hass, bridge, async_add_entities,
return
+ finally:
+ _LOGGER.debug('Finished %s request in %.3f seconds',
+ api_type, monotonic() - start)
+
if not bridge.available:
_LOGGER.info('Reconnected to bridge %s', bridge.host)
bridge.available = True
diff --git a/homeassistant/components/light/insteon.py b/homeassistant/components/light/insteon.py
index 82f455c821e..4829ce631a6 100644
--- a/homeassistant/components/light/insteon.py
+++ b/homeassistant/components/light/insteon.py
@@ -4,7 +4,6 @@ Support for Insteon lights via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.insteon/
"""
-import asyncio
import logging
from homeassistant.components.insteon import InsteonEntity
@@ -18,9 +17,8 @@ DEPENDENCIES = ['insteon']
MAX_BRIGHTNESS = 255
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Insteon component."""
insteon_modem = hass.data['insteon'].get('modem')
@@ -55,8 +53,7 @@ class InsteonDimmerDevice(InsteonEntity, Light):
"""Flag supported features."""
return SUPPORT_BRIGHTNESS
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn device on."""
if ATTR_BRIGHTNESS in kwargs:
brightness = int(kwargs[ATTR_BRIGHTNESS])
@@ -64,7 +61,6 @@ class InsteonDimmerDevice(InsteonEntity, Light):
else:
self._insteon_device_state.on()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn device off."""
self._insteon_device_state.off()
diff --git a/homeassistant/components/light/lifx.py b/homeassistant/components/light/lifx.py
index bea39354e1b..9dcd2ae4cc2 100644
--- a/homeassistant/components/light/lifx.py
+++ b/homeassistant/components/light/lifx.py
@@ -30,7 +30,7 @@ import homeassistant.util.color as color_util
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['aiolifx==0.6.3', 'aiolifx_effects==0.1.2']
+REQUIREMENTS = ['aiolifx==0.6.3', 'aiolifx_effects==0.2.1']
UDP_BROADCAST_PORT = 56700
@@ -300,7 +300,7 @@ class LIFXManager:
@callback
def register(self, bulb):
"""Handle aiolifx detected bulb."""
- self.hass.async_add_job(self.register_new_bulb(bulb))
+ self.hass.async_create_task(self.register_new_bulb(bulb))
async def register_new_bulb(self, bulb):
"""Handle newly detected bulb."""
@@ -344,7 +344,7 @@ class LIFXManager:
entity = self.entities[bulb.mac_addr]
_LOGGER.debug("%s unregister", entity.who)
entity.registered = False
- self.hass.async_add_job(entity.async_update_ha_state())
+ self.hass.async_create_task(entity.async_update_ha_state())
class AwaitAioLIFX:
@@ -484,12 +484,12 @@ class LIFXLight(Light):
async def async_turn_on(self, **kwargs):
"""Turn the light on."""
kwargs[ATTR_POWER] = True
- self.hass.async_add_job(self.set_state(**kwargs))
+ self.hass.async_create_task(self.set_state(**kwargs))
async def async_turn_off(self, **kwargs):
"""Turn the light off."""
kwargs[ATTR_POWER] = False
- self.hass.async_add_job(self.set_state(**kwargs))
+ self.hass.async_create_task(self.set_state(**kwargs))
async def set_state(self, **kwargs):
"""Set a color on the light and turn it on/off."""
diff --git a/homeassistant/components/light/limitlessled.py b/homeassistant/components/light/limitlessled.py
index 9400932802a..a5aeabba84d 100644
--- a/homeassistant/components/light/limitlessled.py
+++ b/homeassistant/components/light/limitlessled.py
@@ -4,7 +4,6 @@ Support for LimitlessLED bulbs.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.limitlessled/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -188,10 +187,9 @@ class LimitlessLEDGroup(Light):
self._color = None
self._effect = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle entity about to be added to hass event."""
- last_state = yield from async_get_last_state(self.hass, self.entity_id)
+ last_state = await async_get_last_state(self.hass, self.entity_id)
if last_state:
self._is_on = (last_state.state == STATE_ON)
self._brightness = last_state.attributes.get('brightness')
diff --git a/homeassistant/components/light/lutron_caseta.py b/homeassistant/components/light/lutron_caseta.py
index f345748683b..21360e71c42 100644
--- a/homeassistant/components/light/lutron_caseta.py
+++ b/homeassistant/components/light/lutron_caseta.py
@@ -4,7 +4,6 @@ Support for Lutron Caseta lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.lutron_caseta/
"""
-import asyncio
import logging
from homeassistant.components.light import (
@@ -19,9 +18,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron_caseta']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Lutron Caseta lights."""
devs = []
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
@@ -46,15 +44,13 @@ class LutronCasetaLight(LutronCasetaDevice, Light):
"""Return the brightness of the light."""
return to_hass_level(self._state["current_state"])
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the light on."""
brightness = kwargs.get(ATTR_BRIGHTNESS, 255)
self._smartbridge.set_value(self._device_id,
to_lutron_level(brightness))
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the light off."""
self._smartbridge.set_value(self._device_id, 0)
@@ -63,8 +59,7 @@ class LutronCasetaLight(LutronCasetaDevice, Light):
"""Return true if device is on."""
return self._state["current_state"] > 0
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Call when forcing a refresh of the device."""
self._state = self._smartbridge.get_device_by_id(self._device_id)
_LOGGER.debug(self._state)
diff --git a/homeassistant/components/light/mqtt.py b/homeassistant/components/light/mqtt.py
index 64331411f7f..3b095aa4bfd 100644
--- a/homeassistant/components/light/mqtt.py
+++ b/homeassistant/components/light/mqtt.py
@@ -9,7 +9,7 @@ import logging
import voluptuous as vol
from homeassistant.core import callback
-from homeassistant.components import mqtt
+from homeassistant.components import mqtt, light
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_HS_COLOR,
ATTR_WHITE_VALUE, Light, SUPPORT_BRIGHTNESS, SUPPORT_COLOR_TEMP,
@@ -19,10 +19,13 @@ from homeassistant.const import (
CONF_OPTIMISTIC, CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, STATE_ON,
CONF_RGB, CONF_STATE, CONF_VALUE_TEMPLATE, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE,
- CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, CONF_STATE_TOPIC,
- MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC,
+ CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
+ CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
from homeassistant.helpers.restore_state import async_get_last_state
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
import homeassistant.helpers.config_validation as cv
import homeassistant.util.color as color_util
@@ -102,12 +105,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-async def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up a MQTT Light."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT light through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT light dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add a MQTT light."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(light.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities,
+ discovery_hash=None):
+ """Set up a MQTT Light."""
config.setdefault(
CONF_STATE_VALUE_TEMPLATE, config.get(CONF_VALUE_TEMPLATE))
@@ -156,19 +175,21 @@ async def async_setup_platform(hass, config, async_add_entities,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
+ discovery_hash,
)])
-class MqttLight(MqttAvailability, Light):
+class MqttLight(MqttAvailability, MqttDiscoveryUpdate, Light):
"""Representation of a MQTT light."""
def __init__(self, name, unique_id, effect_list, topic, templates,
qos, retain, payload, optimistic, brightness_scale,
white_value_scale, on_command_type, availability_topic,
- payload_available, payload_not_available):
+ payload_available, payload_not_available, discovery_hash):
"""Initialize MQTT light."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._name = name
self._unique_id = unique_id
self._effect_list = effect_list
@@ -216,10 +237,12 @@ class MqttLight(MqttAvailability, Light):
SUPPORT_WHITE_VALUE)
self._supported_features |= (
topic[CONF_XY_COMMAND_TOPIC] is not None and SUPPORT_COLOR)
+ self._discovery_hash = discovery_hash
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
- await super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
templates = {}
for key, tpl in list(self._templates.items()):
@@ -235,6 +258,10 @@ class MqttLight(MqttAvailability, Light):
def state_received(topic, payload, qos):
"""Handle new MQTT messages."""
payload = templates[CONF_STATE](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty state message from '%s'", topic)
+ return
+
if payload == self._payload['on']:
self._state = True
elif payload == self._payload['off']:
@@ -251,7 +278,13 @@ class MqttLight(MqttAvailability, Light):
@callback
def brightness_received(topic, payload, qos):
"""Handle new MQTT messages for the brightness."""
- device_value = float(templates[CONF_BRIGHTNESS](payload))
+ payload = templates[CONF_BRIGHTNESS](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty brightness message from '%s'",
+ topic)
+ return
+
+ device_value = float(payload)
percent_bright = device_value / self._brightness_scale
self._brightness = int(percent_bright * 255)
self.async_schedule_update_ha_state()
@@ -272,8 +305,12 @@ class MqttLight(MqttAvailability, Light):
@callback
def rgb_received(topic, payload, qos):
"""Handle new MQTT messages for RGB."""
- rgb = [int(val) for val in
- templates[CONF_RGB](payload).split(',')]
+ payload = templates[CONF_RGB](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty rgb message from '%s'", topic)
+ return
+
+ rgb = [int(val) for val in payload.split(',')]
self._hs = color_util.color_RGB_to_hs(*rgb)
self.async_schedule_update_ha_state()
@@ -291,7 +328,13 @@ class MqttLight(MqttAvailability, Light):
@callback
def color_temp_received(topic, payload, qos):
"""Handle new MQTT messages for color temperature."""
- self._color_temp = int(templates[CONF_COLOR_TEMP](payload))
+ payload = templates[CONF_COLOR_TEMP](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty color temp message from '%s'",
+ topic)
+ return
+
+ self._color_temp = int(payload)
self.async_schedule_update_ha_state()
if self._topic[CONF_COLOR_TEMP_STATE_TOPIC] is not None:
@@ -310,7 +353,12 @@ class MqttLight(MqttAvailability, Light):
@callback
def effect_received(topic, payload, qos):
"""Handle new MQTT messages for effect."""
- self._effect = templates[CONF_EFFECT](payload)
+ payload = templates[CONF_EFFECT](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty effect message from '%s'", topic)
+ return
+
+ self._effect = payload
self.async_schedule_update_ha_state()
if self._topic[CONF_EFFECT_STATE_TOPIC] is not None:
@@ -329,7 +377,13 @@ class MqttLight(MqttAvailability, Light):
@callback
def white_value_received(topic, payload, qos):
"""Handle new MQTT messages for white value."""
- device_value = float(templates[CONF_WHITE_VALUE](payload))
+ payload = templates[CONF_WHITE_VALUE](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty white value message from '%s'",
+ topic)
+ return
+
+ device_value = float(payload)
percent_white = device_value / self._white_value_scale
self._white_value = int(percent_white * 255)
self.async_schedule_update_ha_state()
@@ -350,8 +404,13 @@ class MqttLight(MqttAvailability, Light):
@callback
def xy_received(topic, payload, qos):
"""Handle new MQTT messages for color."""
- xy_color = [float(val) for val in
- templates[CONF_XY](payload).split(',')]
+ payload = templates[CONF_XY](payload)
+ if not payload:
+ _LOGGER.debug("Ignoring empty xy-color message from '%s'",
+ topic)
+ return
+
+ xy_color = [float(val) for val in payload.split(',')]
self._hs = color_util.color_xy_to_hs(*xy_color)
self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/light/mqtt_json.py b/homeassistant/components/light/mqtt_json.py
index ed4d350d96d..1ed43a6385a 100644
--- a/homeassistant/components/light/mqtt_json.py
+++ b/homeassistant/components/light/mqtt_json.py
@@ -4,29 +4,31 @@ Support for MQTT JSON lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.mqtt_json/
"""
-import logging
import json
+import logging
+from typing import Optional
+
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.components import mqtt
from homeassistant.components.light import (
- ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH,
- ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_HS_COLOR,
- FLASH_LONG, FLASH_SHORT, Light, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
- SUPPORT_COLOR_TEMP, SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_COLOR,
- SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE)
+ ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
+ ATTR_TRANSITION, ATTR_WHITE_VALUE, FLASH_LONG, FLASH_SHORT,
+ PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP,
+ SUPPORT_EFFECT, SUPPORT_FLASH, SUPPORT_TRANSITION, SUPPORT_WHITE_VALUE,
+ Light)
from homeassistant.components.light.mqtt import CONF_BRIGHTNESS_SCALE
-from homeassistant.const import (
- CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, STATE_ON,
- CONF_NAME, CONF_OPTIMISTIC, CONF_RGB, CONF_WHITE_VALUE, CONF_XY)
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_COMMAND_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
- MqttAvailability)
+ CONF_STATE_TOPIC, MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.const import (
+ CONF_BRIGHTNESS, CONF_COLOR_TEMP, CONF_EFFECT, CONF_NAME, CONF_OPTIMISTIC,
+ CONF_RGB, CONF_WHITE_VALUE, CONF_XY, STATE_ON)
+from homeassistant.core import callback
import homeassistant.helpers.config_validation as cv
-from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.helpers.restore_state import async_get_last_state
+from homeassistant.helpers.typing import ConfigType, HomeAssistantType
import homeassistant.util.color as color_util
_LOGGER = logging.getLogger(__name__)
@@ -87,6 +89,11 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
"""Set up a MQTT JSON Light."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
+
+ discovery_hash = None
+ if discovery_info is not None and ATTR_DISCOVERY_HASH in discovery_info:
+ discovery_hash = discovery_info[ATTR_DISCOVERY_HASH]
+
async_add_entities([MqttJson(
config.get(CONF_NAME),
config.get(CONF_UNIQUE_ID),
@@ -116,20 +123,23 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
- config.get(CONF_BRIGHTNESS_SCALE)
+ config.get(CONF_BRIGHTNESS_SCALE),
+ discovery_hash,
)])
-class MqttJson(MqttAvailability, Light):
+class MqttJson(MqttAvailability, MqttDiscoveryUpdate, Light):
"""Representation of a MQTT JSON light."""
def __init__(self, name, unique_id, effect_list, topic, qos, retain,
optimistic, brightness, color_temp, effect, rgb, white_value,
xy, hs, flash_times, availability_topic, payload_available,
- payload_not_available, brightness_scale):
+ payload_not_available, brightness_scale,
+ discovery_hash: Optional[str]):
"""Initialize MQTT JSON light."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._name = name
self._unique_id = unique_id
self._effect_list = effect_list
@@ -180,7 +190,8 @@ class MqttJson(MqttAvailability, Light):
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
- await super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
last_state = await async_get_last_state(self.hass, self.entity_id)
diff --git a/homeassistant/components/light/opple.py b/homeassistant/components/light/opple.py
new file mode 100644
index 00000000000..66850d04406
--- /dev/null
+++ b/homeassistant/components/light/opple.py
@@ -0,0 +1,147 @@
+"""
+Support for the Opple light.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/light.opple/
+"""
+
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.light import (
+ ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, PLATFORM_SCHEMA, SUPPORT_BRIGHTNESS,
+ SUPPORT_COLOR_TEMP, Light)
+from homeassistant.const import CONF_HOST, CONF_NAME
+import homeassistant.helpers.config_validation as cv
+from homeassistant.util.color import \
+ color_temperature_kelvin_to_mired as kelvin_to_mired
+from homeassistant.util.color import \
+ color_temperature_mired_to_kelvin as mired_to_kelvin
+
+REQUIREMENTS = ['pyoppleio==1.0.5']
+
+_LOGGER = logging.getLogger(__name__)
+
+DEFAULT_NAME = "opple light"
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_HOST): cv.string,
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
+})
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Set up Opple light platform."""
+ name = config[CONF_NAME]
+ host = config[CONF_HOST]
+ entity = OppleLight(name, host)
+ add_entities([entity])
+
+ _LOGGER.debug("Init light %s %s", host, entity.unique_id)
+
+
+class OppleLight(Light):
+ """Opple light device."""
+
+ def __init__(self, name, host):
+ """Initialize an Opple light."""
+ from pyoppleio.OppleLightDevice import OppleLightDevice
+ self._device = OppleLightDevice(host)
+
+ self._name = name
+ self._is_on = None
+ self._brightness = None
+ self._color_temp = None
+
+ @property
+ def available(self):
+ """Return True if light is available."""
+ return self._device.is_online
+
+ @property
+ def unique_id(self):
+ """Return unique ID for light."""
+ return self._device.mac
+
+ @property
+ def name(self):
+ """Return the display name of this light."""
+ return self._name
+
+ @property
+ def is_on(self):
+ """Return true if light is on."""
+ return self._is_on
+
+ @property
+ def brightness(self):
+ """Return the brightness of the light."""
+ return self._brightness
+
+ @property
+ def color_temp(self):
+ """Return the color temperature of this light."""
+ return kelvin_to_mired(self._color_temp)
+
+ @property
+ def min_mireds(self):
+ """Return minimum supported color temperature."""
+ return 175
+
+ @property
+ def max_mireds(self):
+ """Return maximum supported color temperature."""
+ return 333
+
+ @property
+ def supported_features(self):
+ """Flag supported features."""
+ return SUPPORT_BRIGHTNESS | SUPPORT_COLOR_TEMP
+
+ def turn_on(self, **kwargs):
+ """Instruct the light to turn on."""
+ _LOGGER.debug("Turn on light %s %s", self._device.ip, kwargs)
+ if not self.is_on:
+ self._device.power_on = True
+
+ if ATTR_BRIGHTNESS in kwargs and \
+ self.brightness != kwargs[ATTR_BRIGHTNESS]:
+ self._device.brightness = kwargs[ATTR_BRIGHTNESS]
+
+ if ATTR_COLOR_TEMP in kwargs and \
+ self.brightness != kwargs[ATTR_COLOR_TEMP]:
+ color_temp = mired_to_kelvin(kwargs[ATTR_COLOR_TEMP])
+ self._device.color_temperature = color_temp
+
+ def turn_off(self, **kwargs):
+ """Instruct the light to turn off."""
+ self._device.power_on = False
+ _LOGGER.debug("Turn off light %s", self._device.ip)
+
+ def update(self):
+ """Synchronize state with light."""
+ prev_available = self.available
+ self._device.update()
+
+ if prev_available == self.available and \
+ self._is_on == self._device.power_on and \
+ self._brightness == self._device.brightness and \
+ self._color_temp == self._device.color_temperature:
+ return
+
+ if not self.available:
+ _LOGGER.debug("Light %s is offline", self._device.ip)
+ return
+
+ self._is_on = self._device.power_on
+ self._brightness = self._device.brightness
+ self._color_temp = self._device.color_temperature
+
+ if not self.is_on:
+ _LOGGER.debug("Update light %s success: power off",
+ self._device.ip)
+ else:
+ _LOGGER.debug("Update light %s success: power on brightness %s "
+ "color temperature %s",
+ self._device.ip, self._brightness, self._color_temp)
diff --git a/homeassistant/components/light/rflink.py b/homeassistant/components/light/rflink.py
index b410fdceff7..d9f9dd589ec 100644
--- a/homeassistant/components/light/rflink.py
+++ b/homeassistant/components/light/rflink.py
@@ -4,7 +4,6 @@ Support for Rflink lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.rflink/
"""
-import asyncio
import logging
from homeassistant.components.light import (
@@ -155,14 +154,12 @@ def devices_from_config(domain_config, hass=None):
return devices
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Rflink light platform."""
async_add_entities(devices_from_config(config, hass))
- @asyncio.coroutine
- def add_new_device(event):
+ async def add_new_device(event):
"""Check if device is known, otherwise add to list of known devices."""
device_id = event[EVENT_KEY_ID]
@@ -195,15 +192,14 @@ class DimmableRflinkLight(SwitchableRflinkDevice, Light):
_brightness = 255
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on."""
if ATTR_BRIGHTNESS in kwargs:
# rflink only support 16 brightness levels
self._brightness = int(kwargs[ATTR_BRIGHTNESS] / 17) * 17
# Turn on light at the requested dim level
- yield from self._async_handle_command('dim', self._brightness)
+ await self._async_handle_command('dim', self._brightness)
@property
def brightness(self):
@@ -233,8 +229,7 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light):
_brightness = 255
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on and set dim level."""
if ATTR_BRIGHTNESS in kwargs:
# rflink only support 16 brightness levels
@@ -242,12 +237,12 @@ class HybridRflinkLight(SwitchableRflinkDevice, Light):
# if receiver supports dimming this will turn on the light
# at the requested dim level
- yield from self._async_handle_command('dim', self._brightness)
+ await self._async_handle_command('dim', self._brightness)
# if the receiving device does not support dimlevel this
# will ensure it is turned on when full brightness is set
if self._brightness == 255:
- yield from self._async_handle_command('turn_on')
+ await self._async_handle_command('turn_on')
@property
def brightness(self):
@@ -284,12 +279,10 @@ class ToggleRflinkLight(SwitchableRflinkDevice, Light):
# if the state is true, it gets set as false
self._state = self._state in [STATE_UNKNOWN, False]
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on."""
- yield from self._async_handle_command('toggle')
+ await self._async_handle_command('toggle')
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the device off."""
- yield from self._async_handle_command('toggle')
+ await self._async_handle_command('toggle')
diff --git a/homeassistant/components/light/template.py b/homeassistant/components/light/template.py
index 9be6eb99acc..8aff85c6001 100644
--- a/homeassistant/components/light/template.py
+++ b/homeassistant/components/light/template.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.template/
"""
import logging
-import asyncio
import voluptuous as vol
@@ -49,9 +48,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Template Lights."""
lights = []
@@ -182,8 +180,7 @@ class LightTemplate(Light):
"""Return the entity picture to use in the frontend, if any."""
return self._entity_picture
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def template_light_state_listener(entity, old_state, new_state):
@@ -203,8 +200,7 @@ class LightTemplate(Light):
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, template_light_startup)
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the light on."""
optimistic_set = False
# set optimistic states
@@ -219,24 +215,22 @@ class LightTemplate(Light):
optimistic_set = True
if ATTR_BRIGHTNESS in kwargs and self._level_script:
- self.hass.async_add_job(self._level_script.async_run(
+ self.hass.async_create_task(self._level_script.async_run(
{"brightness": kwargs[ATTR_BRIGHTNESS]}))
else:
- yield from self._on_script.async_run()
+ await self._on_script.async_run()
if optimistic_set:
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the light off."""
- yield from self._off_script.async_run()
+ await self._off_script.async_run()
if self._template is None:
self._state = False
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update the state from the template."""
if self._template is not None:
try:
diff --git a/homeassistant/components/light/tradfri.py b/homeassistant/components/light/tradfri.py
index b62900b204c..a26a2eb828a 100644
--- a/homeassistant/components/light/tradfri.py
+++ b/homeassistant/components/light/tradfri.py
@@ -127,7 +127,7 @@ class TradfriGroup(Light):
cmd = self._group.observe(callback=self._observe_update,
err_callback=self._async_start_observe,
duration=0)
- self.hass.async_add_job(self._api(cmd))
+ self.hass.async_create_task(self._api(cmd))
except PytradfriError as err:
_LOGGER.warning("Observation failed, trying again", exc_info=err)
self._async_start_observe()
@@ -346,7 +346,7 @@ class TradfriLight(Light):
cmd = self._light.observe(callback=self._observe_update,
err_callback=self._async_start_observe,
duration=0)
- self.hass.async_add_job(self._api(cmd))
+ self.hass.async_create_task(self._api(cmd))
except PytradfriError as err:
_LOGGER.warning("Observation failed, trying again", exc_info=err)
self._async_start_observe()
diff --git a/homeassistant/components/light/wemo.py b/homeassistant/components/light/wemo.py
index 72279fbe1a4..55a4836e148 100644
--- a/homeassistant/components/light/wemo.py
+++ b/homeassistant/components/light/wemo.py
@@ -4,7 +4,6 @@ Support for Belkin WeMo lights.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/light.wemo/
"""
-import asyncio
import logging
from datetime import timedelta
import requests
@@ -160,13 +159,12 @@ class WemoDimmer(Light):
self._brightness = None
self._state = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register update callback."""
wemo = self.hass.components.wemo
# The register method uses a threading condition, so call via executor.
- # and yield from to wait until the task is done.
- yield from self.hass.async_add_job(
+ # and await to wait until the task is done.
+ await self.hass.async_add_job(
wemo.SUBSCRIPTION_REGISTRY.register, self.wemo)
# The on method just appends to a defaultdict list.
wemo.SUBSCRIPTION_REGISTRY.on(self.wemo, None, self._update_callback)
diff --git a/homeassistant/components/light/wink.py b/homeassistant/components/light/wink.py
index ee8c2aca8b5..96c8f20679e 100644
--- a/homeassistant/components/light/wink.py
+++ b/homeassistant/components/light/wink.py
@@ -4,7 +4,6 @@ Support for Wink lights.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.wink/
"""
-import asyncio
from homeassistant.components.light import (
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_HS_COLOR, SUPPORT_BRIGHTNESS,
@@ -34,8 +33,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class WinkLight(WinkDevice, Light):
"""Representation of a Wink light."""
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['light'].append(self)
diff --git a/homeassistant/components/light/xiaomi_miio.py b/homeassistant/components/light/xiaomi_miio.py
index 51c36fc2dd0..8bc2497a3e5 100644
--- a/homeassistant/components/light/xiaomi_miio.py
+++ b/homeassistant/components/light/xiaomi_miio.py
@@ -713,7 +713,7 @@ class XiaomiPhilipsEyecareLampAmbientLight(XiaomiPhilipsAbstractLight):
_LOGGER.debug("Got new state: %s", state)
self._available = True
- self._state = state.eyecare
+ self._state = state.ambient
self._brightness = ceil((255 / 100.0) * state.ambient_brightness)
except DeviceException as ex:
diff --git a/homeassistant/components/lock/__init__.py b/homeassistant/components/lock/__init__.py
index 3c4ff7cdedd..e9904f0163a 100644
--- a/homeassistant/components/lock/__init__.py
+++ b/homeassistant/components/lock/__init__.py
@@ -4,7 +4,6 @@ Component to interface with various locks that can be controlled remotely.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/lock/
"""
-import asyncio
from datetime import timedelta
import functools as ft
import logging
@@ -57,49 +56,12 @@ def is_locked(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_LOCKED)
-@bind_hass
-def lock(hass, entity_id=None, code=None):
- """Lock all or specified locks."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_LOCK, data)
-
-
-@bind_hass
-def unlock(hass, entity_id=None, code=None):
- """Unlock all or specified locks."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
-
-
-@bind_hass
-def open_lock(hass, entity_id=None, code=None):
- """Open all or specified locks."""
- data = {}
- if code:
- data[ATTR_CODE] = code
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_OPEN, data)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Track states and offer events for locks."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_LOCKS)
- yield from component.async_setup(config)
+ await component.async_setup(config)
component.async_register_entity_service(
SERVICE_UNLOCK, LOCK_SERVICE_SCHEMA,
diff --git a/homeassistant/components/lock/bmw_connected_drive.py b/homeassistant/components/lock/bmw_connected_drive.py
index c84df54cfba..b13665610d8 100644
--- a/homeassistant/components/lock/bmw_connected_drive.py
+++ b/homeassistant/components/lock/bmw_connected_drive.py
@@ -4,7 +4,6 @@ Support for BMW cars with BMW ConnectedDrive.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/lock.bmw_connected_drive/
"""
-import asyncio
import logging
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
@@ -111,8 +110,7 @@ class BMWLock(LockDevice):
"""Schedule a state update."""
self.schedule_update_ha_state(True)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Add callback after being added to hass.
Show latest data after startup.
diff --git a/homeassistant/components/lock/mqtt.py b/homeassistant/components/lock/mqtt.py
index 103864a6bfd..ee43eb942c4 100644
--- a/homeassistant/components/lock/mqtt.py
+++ b/homeassistant/components/lock/mqtt.py
@@ -4,7 +4,6 @@ Support for MQTT locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.mqtt/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -12,9 +11,9 @@ import voluptuous as vol
from homeassistant.core import callback
from homeassistant.components.lock import LockDevice
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
- CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN,
- MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
+ CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
+ CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE)
from homeassistant.components import mqtt
@@ -41,9 +40,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the MQTT lock."""
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
@@ -52,6 +50,10 @@ def async_setup_platform(hass, config, async_add_entities,
if value_template is not None:
value_template.hass = hass
+ discovery_hash = None
+ if discovery_info is not None and ATTR_DISCOVERY_HASH in discovery_info:
+ discovery_hash = discovery_info[ATTR_DISCOVERY_HASH]
+
async_add_entities([MqttLock(
config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC),
@@ -64,19 +66,22 @@ def async_setup_platform(hass, config, async_add_entities,
value_template,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
- config.get(CONF_PAYLOAD_NOT_AVAILABLE)
+ config.get(CONF_PAYLOAD_NOT_AVAILABLE),
+ discovery_hash,
)])
-class MqttLock(MqttAvailability, LockDevice):
+class MqttLock(MqttAvailability, MqttDiscoveryUpdate, LockDevice):
"""Representation of a lock that can be toggled using MQTT."""
def __init__(self, name, state_topic, command_topic, qos, retain,
payload_lock, payload_unlock, optimistic, value_template,
- availability_topic, payload_available, payload_not_available):
+ availability_topic, payload_available, payload_not_available,
+ discovery_hash):
"""Initialize the lock."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._state = False
self._name = name
self._state_topic = state_topic
@@ -87,11 +92,12 @@ class MqttLock(MqttAvailability, LockDevice):
self._payload_unlock = payload_unlock
self._optimistic = optimistic
self._template = value_template
+ self._discovery_hash = discovery_hash
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
- yield from super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
@callback
def message_received(topic, payload, qos):
@@ -110,7 +116,7 @@ class MqttLock(MqttAvailability, LockDevice):
# Force into optimistic mode.
self._optimistic = True
else:
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos)
@property
@@ -133,8 +139,7 @@ class MqttLock(MqttAvailability, LockDevice):
"""Return true if we do optimistic updates."""
return self._optimistic
- @asyncio.coroutine
- def async_lock(self, **kwargs):
+ async def async_lock(self, **kwargs):
"""Lock the device.
This method is a coroutine.
@@ -147,8 +152,7 @@ class MqttLock(MqttAvailability, LockDevice):
self._state = True
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_unlock(self, **kwargs):
+ async def async_unlock(self, **kwargs):
"""Unlock the device.
This method is a coroutine.
diff --git a/homeassistant/components/lock/nuki.py b/homeassistant/components/lock/nuki.py
index 6cf58dda04c..689ec31fc7c 100644
--- a/homeassistant/components/lock/nuki.py
+++ b/homeassistant/components/lock/nuki.py
@@ -4,7 +4,6 @@ Nuki.io lock platform.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/lock.nuki/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -92,8 +91,7 @@ class NukiLock(LockDevice):
self._name = nuki_lock.name
self._battery_critical = nuki_lock.battery_critical
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
if NUKI_DATA not in self.hass.data:
self.hass.data[NUKI_DATA] = {}
diff --git a/homeassistant/components/lock/wink.py b/homeassistant/components/lock/wink.py
index 03de8fc5919..68cc7a79ae6 100644
--- a/homeassistant/components/lock/wink.py
+++ b/homeassistant/components/lock/wink.py
@@ -4,7 +4,6 @@ Support for Wink locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.wink/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -131,8 +130,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class WinkLockDevice(WinkDevice, LockDevice):
"""Representation of a Wink lock."""
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['lock'].append(self)
diff --git a/homeassistant/components/lock/zwave.py b/homeassistant/components/lock/zwave.py
index 5ee88f053b5..4a2b71f0b48 100644
--- a/homeassistant/components/lock/zwave.py
+++ b/homeassistant/components/lock/zwave.py
@@ -4,7 +4,6 @@ Z-Wave platform that handles simple door locks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/lock.zwave/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -119,11 +118,10 @@ CLEAR_USERCODE_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Z-Wave Lock platform."""
- yield from zwave.async_setup_platform(
+ await zwave.async_setup_platform(
hass, config, async_add_entities, discovery_info)
network = hass.data[zwave.const.DATA_NETWORK]
diff --git a/homeassistant/components/logbook.py b/homeassistant/components/logbook.py
index c4fcf53a9c1..5cbd2b9432b 100644
--- a/homeassistant/components/logbook.py
+++ b/homeassistant/components/logbook.py
@@ -10,6 +10,7 @@ import logging
import voluptuous as vol
+from homeassistant.loader import bind_hass
from homeassistant.components import sun
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import (
@@ -17,8 +18,9 @@ from homeassistant.const import (
CONF_INCLUDE, EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
EVENT_LOGBOOK_ENTRY, EVENT_STATE_CHANGED, HTTP_BAD_REQUEST, STATE_NOT_HOME,
STATE_OFF, STATE_ON)
-from homeassistant.core import DOMAIN as HA_DOMAIN
-from homeassistant.core import State, callback, split_entity_id
+from homeassistant.core import (
+ DOMAIN as HA_DOMAIN, State, callback, split_entity_id)
+from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
import homeassistant.helpers.config_validation as cv
import homeassistant.util.dt as dt_util
@@ -53,7 +55,8 @@ CONFIG_SCHEMA = vol.Schema({
ALL_EVENT_TYPES = [
EVENT_STATE_CHANGED, EVENT_LOGBOOK_ENTRY,
- EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP
+ EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP,
+ EVENT_ALEXA_SMART_HOME
]
LOG_MESSAGE_SCHEMA = vol.Schema({
@@ -64,11 +67,13 @@ LOG_MESSAGE_SCHEMA = vol.Schema({
})
+@bind_hass
def log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
hass.add_job(async_log_entry, hass, name, message, domain, entity_id)
+@bind_hass
def async_log_entry(hass, name, message, domain=None, entity_id=None):
"""Add an entry to the logbook."""
data = {
@@ -128,42 +133,26 @@ class LogbookView(HomeAssistantView):
else:
datetime = dt_util.start_of_local_day()
- start_day = dt_util.as_utc(datetime)
- end_day = start_day + timedelta(days=1)
+ period = request.query.get('period')
+ if period is None:
+ period = 1
+ else:
+ period = int(period)
+
+ entity_id = request.query.get('entity')
+ start_day = dt_util.as_utc(datetime) - timedelta(days=period - 1)
+ end_day = start_day + timedelta(days=period)
hass = request.app['hass']
def json_events():
"""Fetch events and generate JSON."""
return self.json(list(
- _get_events(hass, self.config, start_day, end_day)))
+ _get_events(hass, self.config, start_day, end_day, entity_id)))
return await hass.async_add_job(json_events)
-class Entry:
- """A human readable version of the log."""
-
- def __init__(self, when=None, name=None, message=None, domain=None,
- entity_id=None):
- """Initialize the entry."""
- self.when = when
- self.name = name
- self.message = message
- self.domain = domain
- self.entity_id = entity_id
-
- def as_dict(self):
- """Convert entry to a dict to be used within JSON."""
- return {
- 'when': self.when,
- 'name': self.name,
- 'message': self.message,
- 'domain': self.domain,
- 'entity_id': self.entity_id,
- }
-
-
-def humanify(events):
+def humanify(hass, events):
"""Generate a converted list of events into Entry objects.
Will try to group events if possible:
@@ -224,20 +213,28 @@ def humanify(events):
to_state.attributes.get('unit_of_measurement'):
continue
- yield Entry(
- event.time_fired,
- name=to_state.name,
- message=_entry_message_from_state(domain, to_state),
- domain=domain,
- entity_id=to_state.entity_id)
+ yield {
+ 'when': event.time_fired,
+ 'name': to_state.name,
+ 'message': _entry_message_from_state(domain, to_state),
+ 'domain': domain,
+ 'entity_id': to_state.entity_id,
+ 'context_id': event.context.id,
+ 'context_user_id': event.context.user_id
+ }
elif event.event_type == EVENT_HOMEASSISTANT_START:
if start_stop_events.get(event.time_fired.minute) == 2:
continue
- yield Entry(
- event.time_fired, "Home Assistant", "started",
- domain=HA_DOMAIN)
+ yield {
+ 'when': event.time_fired,
+ 'name': "Home Assistant",
+ 'message': "started",
+ 'domain': HA_DOMAIN,
+ 'context_id': event.context.id,
+ 'context_user_id': event.context.user_id
+ }
elif event.event_type == EVENT_HOMEASSISTANT_STOP:
if start_stop_events.get(event.time_fired.minute) == 2:
@@ -245,9 +242,14 @@ def humanify(events):
else:
action = "stopped"
- yield Entry(
- event.time_fired, "Home Assistant", action,
- domain=HA_DOMAIN)
+ yield {
+ 'when': event.time_fired,
+ 'name': "Home Assistant",
+ 'message': action,
+ 'domain': HA_DOMAIN,
+ 'context_id': event.context.id,
+ 'context_user_id': event.context.user_id
+ }
elif event.event_type == EVENT_LOGBOOK_ENTRY:
domain = event.data.get(ATTR_DOMAIN)
@@ -258,13 +260,42 @@ def humanify(events):
except IndexError:
pass
- yield Entry(
- event.time_fired, event.data.get(ATTR_NAME),
- event.data.get(ATTR_MESSAGE), domain,
- entity_id)
+ yield {
+ 'when': event.time_fired,
+ 'name': event.data.get(ATTR_NAME),
+ 'message': event.data.get(ATTR_MESSAGE),
+ 'domain': domain,
+ 'entity_id': entity_id,
+ 'context_id': event.context.id,
+ 'context_user_id': event.context.user_id
+ }
+
+ elif event.event_type == EVENT_ALEXA_SMART_HOME:
+ data = event.data
+ entity_id = data['request'].get('entity_id')
+
+ if entity_id:
+ state = hass.states.get(entity_id)
+ name = state.name if state else entity_id
+ message = "send command {}/{} for {}".format(
+ data['request']['namespace'],
+ data['request']['name'], name)
+ else:
+ message = "send command {}/{}".format(
+ data['request']['namespace'], data['request']['name'])
+
+ yield {
+ 'when': event.time_fired,
+ 'name': 'Amazon Alexa',
+ 'message': message,
+ 'domain': 'alexa',
+ 'entity_id': entity_id,
+ 'context_id': event.context.id,
+ 'context_user_id': event.context.user_id
+ }
-def _get_events(hass, config, start_day, end_day):
+def _get_events(hass, config, start_day, end_day, entity_id=None):
"""Get events for a period of time."""
from homeassistant.components.recorder.models import Events, States
from homeassistant.components.recorder.util import (
@@ -278,8 +309,12 @@ def _get_events(hass, config, start_day, end_day):
& (Events.time_fired < end_day)) \
.filter((States.last_updated == States.last_changed)
| (States.state_id.is_(None)))
+
+ if entity_id is not None:
+ query = query.filter(States.entity_id == entity_id.lower())
+
events = execute(query)
- return humanify(_exclude_events(events, config))
+ return humanify(hass, _exclude_events(events, config))
def _exclude_events(events, config):
diff --git a/homeassistant/components/logger.py b/homeassistant/components/logger.py
index 0baca2f341c..21ae7595ab8 100644
--- a/homeassistant/components/logger.py
+++ b/homeassistant/components/logger.py
@@ -47,11 +47,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-def set_level(hass, logs):
- """Set log level for components."""
- hass.services.call(DOMAIN, SERVICE_SET_LEVEL, logs)
-
-
class HomeAssistantLogFilter(logging.Filter):
"""A log filter."""
diff --git a/homeassistant/components/lovelace/__init__.py b/homeassistant/components/lovelace/__init__.py
new file mode 100644
index 00000000000..a24c8eb9e91
--- /dev/null
+++ b/homeassistant/components/lovelace/__init__.py
@@ -0,0 +1,51 @@
+"""Lovelace UI."""
+import voluptuous as vol
+
+from homeassistant.components import websocket_api
+from homeassistant.util.yaml import load_yaml
+from homeassistant.exceptions import HomeAssistantError
+
+DOMAIN = 'lovelace'
+
+OLD_WS_TYPE_GET_LOVELACE_UI = 'frontend/lovelace_config'
+WS_TYPE_GET_LOVELACE_UI = 'lovelace/config'
+SCHEMA_GET_LOVELACE_UI = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): vol.Any(WS_TYPE_GET_LOVELACE_UI,
+ OLD_WS_TYPE_GET_LOVELACE_UI),
+})
+
+
+async def async_setup(hass, config):
+ """Set up the Lovelace commands."""
+ # Backwards compat. Added in 0.80. Remove after 0.85
+ hass.components.websocket_api.async_register_command(
+ OLD_WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config,
+ SCHEMA_GET_LOVELACE_UI)
+
+ hass.components.websocket_api.async_register_command(
+ WS_TYPE_GET_LOVELACE_UI, websocket_lovelace_config,
+ SCHEMA_GET_LOVELACE_UI)
+
+ return True
+
+
+@websocket_api.async_response
+async def websocket_lovelace_config(hass, connection, msg):
+ """Send lovelace UI config over websocket config."""
+ error = None
+ try:
+ config = await hass.async_add_executor_job(
+ load_yaml, hass.config.path('ui-lovelace.yaml'))
+ message = websocket_api.result_message(
+ msg['id'], config
+ )
+ except FileNotFoundError:
+ error = ('file_not_found',
+ 'Could not find ui-lovelace.yaml in your config dir.')
+ except HomeAssistantError as err:
+ error = 'load_error', str(err)
+
+ if error is not None:
+ message = websocket_api.error_message(msg['id'], *error)
+
+ connection.send_message(message)
diff --git a/homeassistant/components/lutron.py b/homeassistant/components/lutron.py
index bef821220b3..2e49d7ce690 100644
--- a/homeassistant/components/lutron.py
+++ b/homeassistant/components/lutron.py
@@ -4,7 +4,6 @@ Component for interacting with a Lutron RadioRA 2 system.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/lutron/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -69,8 +68,7 @@ class LutronDevice(Entity):
self._controller = controller
self._area_name = area_name
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.async_add_job(
self._controller.subscribe, self._lutron_device,
diff --git a/homeassistant/components/lutron_caseta.py b/homeassistant/components/lutron_caseta.py
index 2535fb76120..eb4010e43a1 100644
--- a/homeassistant/components/lutron_caseta.py
+++ b/homeassistant/components/lutron_caseta.py
@@ -4,7 +4,6 @@ Component for interacting with a Lutron Caseta system.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/lutron_caseta/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -40,8 +39,7 @@ LUTRON_CASETA_COMPONENTS = [
]
-@asyncio.coroutine
-def async_setup(hass, base_config):
+async def async_setup(hass, base_config):
"""Set up the Lutron component."""
from pylutron_caseta.smartbridge import Smartbridge
@@ -54,7 +52,7 @@ def async_setup(hass, base_config):
certfile=certfile,
ca_certs=ca_certs)
hass.data[LUTRON_CASETA_SMARTBRIDGE] = bridge
- yield from bridge.connect()
+ await bridge.connect()
if not hass.data[LUTRON_CASETA_SMARTBRIDGE].is_connected():
_LOGGER.error("Unable to connect to Lutron smartbridge at %s",
config[CONF_HOST])
@@ -85,8 +83,7 @@ class LutronCasetaDevice(Entity):
self._state = None
self._smartbridge = bridge
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self._smartbridge.add_subscriber(self._device_id,
self.async_schedule_update_ha_state)
diff --git a/homeassistant/components/media_extractor.py b/homeassistant/components/media_extractor.py
index a7093579805..ffcb0f6ab95 100644
--- a/homeassistant/components/media_extractor.py
+++ b/homeassistant/components/media_extractor.py
@@ -14,7 +14,7 @@ from homeassistant.components.media_player import (
SERVICE_PLAY_MEDIA)
from homeassistant.helpers import config_validation as cv
-REQUIREMENTS = ['youtube_dl==2018.09.18']
+REQUIREMENTS = ['youtube_dl==2018.09.26']
_LOGGER = logging.getLogger(__name__)
@@ -148,7 +148,7 @@ class MediaExtractor:
if entity_id:
data[ATTR_ENTITY_ID] = entity_id
- self.hass.async_add_job(
+ self.hass.async_create_task(
self.hass.services.async_call(
MEDIA_PLAYER_DOMAIN, SERVICE_PLAY_MEDIA, data)
)
diff --git a/homeassistant/components/media_player/__init__.py b/homeassistant/components/media_player/__init__.py
index 831009ed8bf..8530a01d3e6 100644
--- a/homeassistant/components/media_player/__init__.py
+++ b/homeassistant/components/media_player/__init__.py
@@ -28,7 +28,6 @@ from homeassistant.const import (
SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_VOLUME_DOWN,
SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET, SERVICE_VOLUME_UP, STATE_IDLE,
STATE_OFF, STATE_PLAYING, STATE_UNKNOWN)
-from homeassistant.core import callback
from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
@@ -192,168 +191,6 @@ def is_on(hass, entity_id=None):
for entity_id in entity_ids)
-@bind_hass
-def turn_on(hass, entity_id=None):
- """Turn on specified media player or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
-
-
-@bind_hass
-def turn_off(hass, entity_id=None):
- """Turn off specified media player or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
-
-
-@bind_hass
-def toggle(hass, entity_id=None):
- """Toggle specified media player or all."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
-
-
-@bind_hass
-def volume_up(hass, entity_id=None):
- """Send the media player the command for volume up."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
-
-
-@bind_hass
-def volume_down(hass, entity_id=None):
- """Send the media player the command for volume down."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
-
-
-@bind_hass
-def mute_volume(hass, mute, entity_id=None):
- """Send the media player the command for muting the volume."""
- data = {ATTR_MEDIA_VOLUME_MUTED: mute}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data)
-
-
-@bind_hass
-def set_volume_level(hass, volume, entity_id=None):
- """Send the media player the command for setting the volume."""
- data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_VOLUME_SET, data)
-
-
-@bind_hass
-def media_play_pause(hass, entity_id=None):
- """Send the media player the command for play/pause."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
-
-
-@bind_hass
-def media_play(hass, entity_id=None):
- """Send the media player the command for play/pause."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
-
-
-@bind_hass
-def media_pause(hass, entity_id=None):
- """Send the media player the command for pause."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
-
-
-@bind_hass
-def media_stop(hass, entity_id=None):
- """Send the media player the stop command."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_MEDIA_STOP, data)
-
-
-@bind_hass
-def media_next_track(hass, entity_id=None):
- """Send the media player the command for next track."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
-
-
-@bind_hass
-def media_previous_track(hass, entity_id=None):
- """Send the media player the command for prev track."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
-
-
-@bind_hass
-def media_seek(hass, position, entity_id=None):
- """Send the media player the command to seek in current playing media."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- data[ATTR_MEDIA_SEEK_POSITION] = position
- hass.services.call(DOMAIN, SERVICE_MEDIA_SEEK, data)
-
-
-@bind_hass
-def play_media(hass, media_type, media_id, entity_id=None, enqueue=None):
- """Send the media player the command for playing media."""
- data = {ATTR_MEDIA_CONTENT_TYPE: media_type,
- ATTR_MEDIA_CONTENT_ID: media_id}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- if enqueue:
- data[ATTR_MEDIA_ENQUEUE] = enqueue
-
- hass.services.call(DOMAIN, SERVICE_PLAY_MEDIA, data)
-
-
-@bind_hass
-def select_source(hass, source, entity_id=None):
- """Send the media player the command to select input source."""
- data = {ATTR_INPUT_SOURCE: source}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SELECT_SOURCE, data)
-
-
-@bind_hass
-def select_sound_mode(hass, sound_mode, entity_id=None):
- """Send the media player the command to select sound mode."""
- data = {ATTR_SOUND_MODE: sound_mode}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SELECT_SOUND_MODE, data)
-
-
-@bind_hass
-def clear_playlist(hass, entity_id=None):
- """Send the media player the command for clear playlist."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data)
-
-
-@bind_hass
-def set_shuffle(hass, shuffle, entity_id=None):
- """Send the media player the command to enable/disable shuffle mode."""
- data = {ATTR_MEDIA_SHUFFLE: shuffle}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_SHUFFLE_SET, data)
-
-
WS_TYPE_MEDIA_PLAYER_THUMBNAIL = 'media_player_thumbnail'
SCHEMA_WEBSOCKET_GET_THUMBNAIL = \
websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({
@@ -1027,8 +864,8 @@ class MediaPlayerImageView(HomeAssistantView):
body=data, content_type=content_type, headers=headers)
-@callback
-def websocket_handle_thumbnail(hass, connection, msg):
+@websocket_api.async_response
+async def websocket_handle_thumbnail(hass, connection, msg):
"""Handle get media player cover command.
Async friendly.
@@ -1037,24 +874,20 @@ def websocket_handle_thumbnail(hass, connection, msg):
player = component.get_entity(msg['entity_id'])
if player is None:
- connection.send_message_outside(websocket_api.error_message(
+ connection.send_message(websocket_api.error_message(
msg['id'], 'entity_not_found', 'Entity not found'))
return
- async def send_image():
- """Send image."""
- data, content_type = await player.async_get_media_image()
+ data, content_type = await player.async_get_media_image()
- if data is None:
- connection.send_message_outside(websocket_api.error_message(
- msg['id'], 'thumbnail_fetch_failed',
- 'Failed to fetch thumbnail'))
- return
+ if data is None:
+ connection.send_message(websocket_api.error_message(
+ msg['id'], 'thumbnail_fetch_failed',
+ 'Failed to fetch thumbnail'))
+ return
- connection.send_message_outside(websocket_api.result_message(
- msg['id'], {
- 'content_type': content_type,
- 'content': base64.b64encode(data).decode('utf-8')
- }))
-
- hass.async_add_job(send_image())
+ connection.send_message(websocket_api.result_message(
+ msg['id'], {
+ 'content_type': content_type,
+ 'content': base64.b64encode(data).decode('utf-8')
+ }))
diff --git a/homeassistant/components/media_player/anthemav.py b/homeassistant/components/media_player/anthemav.py
index 33b6e28a890..f1954a1d37e 100644
--- a/homeassistant/components/media_player/anthemav.py
+++ b/homeassistant/components/media_player/anthemav.py
@@ -4,7 +4,6 @@ Support for Anthem Network Receivers and Processors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.anthemav/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -35,9 +34,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up our socket to the AVR."""
import anthemav
@@ -51,9 +49,9 @@ def async_setup_platform(hass, config, async_add_entities,
def async_anthemav_update_callback(message):
"""Receive notification from transport that new data exists."""
_LOGGER.info("Received update callback from AVR: %s", message)
- hass.async_add_job(device.async_update_ha_state())
+ hass.async_create_task(device.async_update_ha_state())
- avr = yield from anthemav.Connection.create(
+ avr = await anthemav.Connection.create(
host=host, port=port, loop=hass.loop,
update_callback=async_anthemav_update_callback)
@@ -136,28 +134,23 @@ class AnthemAVR(MediaPlayerDevice):
"""Return all active, configured inputs."""
return self._lookup('input_list', ["Unknown"])
- @asyncio.coroutine
- def async_select_source(self, source):
+ async def async_select_source(self, source):
"""Change AVR to the designated source (by name)."""
self._update_avr('input_name', source)
- @asyncio.coroutine
- def async_turn_off(self):
+ async def async_turn_off(self):
"""Turn AVR power off."""
self._update_avr('power', False)
- @asyncio.coroutine
- def async_turn_on(self):
+ async def async_turn_on(self):
"""Turn AVR power on."""
self._update_avr('power', True)
- @asyncio.coroutine
- def async_set_volume_level(self, volume):
+ async def async_set_volume_level(self, volume):
"""Set AVR volume (0 to 1)."""
self._update_avr('volume_as_percentage', volume)
- @asyncio.coroutine
- def async_mute_volume(self, mute):
+ async def async_mute_volume(self, mute):
"""Engage AVR mute."""
self._update_avr('mute', mute)
diff --git a/homeassistant/components/media_player/apple_tv.py b/homeassistant/components/media_player/apple_tv.py
index 399e59ae9f5..bff8834639d 100644
--- a/homeassistant/components/media_player/apple_tv.py
+++ b/homeassistant/components/media_player/apple_tv.py
@@ -4,7 +4,6 @@ Support for Apple TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.apple_tv/
"""
-import asyncio
import logging
from homeassistant.components.apple_tv import (
@@ -29,8 +28,7 @@ SUPPORT_APPLE_TV = SUPPORT_TURN_ON | SUPPORT_TURN_OFF | SUPPORT_PLAY_MEDIA | \
SUPPORT_STOP | SUPPORT_NEXT_TRACK | SUPPORT_PREVIOUS_TRACK
-@asyncio.coroutine
-def async_setup_platform(
+async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the Apple TV platform."""
if not discovery_info:
@@ -71,8 +69,7 @@ class AppleTvDevice(MediaPlayerDevice):
self._power.listeners.append(self)
self.atv.push_updater.listener = self
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle when an entity is about to be added to Home Assistant."""
self._power.init()
@@ -164,10 +161,9 @@ class AppleTvDevice(MediaPlayerDevice):
if state in (STATE_PLAYING, STATE_PAUSED):
return dt_util.utcnow()
- @asyncio.coroutine
- def async_play_media(self, media_type, media_id, **kwargs):
+ async def async_play_media(self, media_type, media_id, **kwargs):
"""Send the play_media command to the media player."""
- yield from self.atv.airplay.play_url(media_id)
+ await self.atv.airplay.play_url(media_id)
@property
def media_image_hash(self):
@@ -176,12 +172,11 @@ class AppleTvDevice(MediaPlayerDevice):
if self._playing and state not in [STATE_OFF, STATE_IDLE]:
return self._playing.hash
- @asyncio.coroutine
- def async_get_media_image(self):
+ async def async_get_media_image(self):
"""Fetch media image of current playing image."""
state = self.state
if self._playing and state not in [STATE_OFF, STATE_IDLE]:
- return (yield from self.atv.metadata.artwork()), 'image/png'
+ return (await self.atv.metadata.artwork()), 'image/png'
return None, None
@@ -201,13 +196,11 @@ class AppleTvDevice(MediaPlayerDevice):
"""Flag media player features that are supported."""
return SUPPORT_APPLE_TV
- @asyncio.coroutine
- def async_turn_on(self):
+ async def async_turn_on(self):
"""Turn the media player on."""
self._power.set_power_on(True)
- @asyncio.coroutine
- def async_turn_off(self):
+ async def async_turn_off(self):
"""Turn the media player off."""
self._playing = None
self._power.set_power_on(False)
diff --git a/homeassistant/components/media_player/bluesound.py b/homeassistant/components/media_player/bluesound.py
index ab012402636..f4ed62b15cd 100644
--- a/homeassistant/components/media_player/bluesound.py
+++ b/homeassistant/components/media_player/bluesound.py
@@ -96,7 +96,7 @@ def _add_player(hass, async_add_entities, host, port=None, name=None):
@callback
def _init_player(event=None):
"""Start polling."""
- hass.async_add_job(player.async_init())
+ hass.async_create_task(player.async_init())
@callback
def _start_polling(event=None):
@@ -272,7 +272,7 @@ class BluesoundPlayer(MediaPlayerDevice):
def start_polling(self):
"""Start the polling task."""
- self._polling_task = self._hass.async_add_job(
+ self._polling_task = self._hass.async_create_task(
self._start_poll_command())
def stop_polling(self):
diff --git a/homeassistant/components/media_player/cast.py b/homeassistant/components/media_player/cast.py
index 67d8ea0b419..d6515b9476d 100644
--- a/homeassistant/components/media_player/cast.py
+++ b/homeassistant/components/media_player/cast.py
@@ -343,7 +343,7 @@ class CastDevice(MediaPlayerDevice):
# Discovered is not our device.
return
_LOGGER.debug("Discovered chromecast with same UUID: %s", discover)
- self.hass.async_add_job(self.async_set_cast_info(discover))
+ self.hass.async_create_task(self.async_set_cast_info(discover))
async def async_stop(event):
"""Disconnect socket on Home Assistant stop."""
@@ -352,7 +352,7 @@ class CastDevice(MediaPlayerDevice):
async_dispatcher_connect(self.hass, SIGNAL_CAST_DISCOVERED,
async_cast_discovered)
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_stop)
- self.hass.async_add_job(self.async_set_cast_info(self._cast_info))
+ self.hass.async_create_task(self.async_set_cast_info(self._cast_info))
async def async_will_remove_from_hass(self) -> None:
"""Disconnect Chromecast object when removed."""
diff --git a/homeassistant/components/media_player/clementine.py b/homeassistant/components/media_player/clementine.py
index e38c44b8d27..2add2bd682a 100644
--- a/homeassistant/components/media_player/clementine.py
+++ b/homeassistant/components/media_player/clementine.py
@@ -4,7 +4,6 @@ Support for Clementine Music Player as media player.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.clementine/
"""
-import asyncio
from datetime import timedelta
import logging
import time
@@ -169,8 +168,7 @@ class ClementineDevice(MediaPlayerDevice):
return None
- @asyncio.coroutine
- def async_get_media_image(self):
+ async def async_get_media_image(self):
"""Fetch media image of current playing image."""
if self._client.current_track:
image = bytes(self._client.current_track['art'])
diff --git a/homeassistant/components/media_player/emby.py b/homeassistant/components/media_player/emby.py
index 2bf3a1b803f..50d2426c315 100644
--- a/homeassistant/components/media_player/emby.py
+++ b/homeassistant/components/media_player/emby.py
@@ -4,7 +4,6 @@ Support to interface with the Emby API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.emby/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -50,9 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Emby platform."""
from pyemby import EmbyServer
@@ -113,10 +111,9 @@ def async_setup_platform(hass, config, async_add_entities,
"""Start Emby connection."""
emby.start()
- @asyncio.coroutine
- def stop_emby(event):
+ async def stop_emby(event):
"""Stop Emby connection."""
- yield from emby.stop()
+ await emby.stop()
emby.add_new_devices_callback(device_update_callback)
emby.add_stale_devices_callback(device_removal_callback)
@@ -141,8 +138,7 @@ class EmbyDevice(MediaPlayerDevice):
self.media_status_last_position = None
self.media_status_received = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callback."""
self.emby.add_update_callback(
self.async_update_callback, self.device_id)
diff --git a/homeassistant/components/media_player/frontier_silicon.py b/homeassistant/components/media_player/frontier_silicon.py
index aebdb676859..67c84bd7b1b 100644
--- a/homeassistant/components/media_player/frontier_silicon.py
+++ b/homeassistant/components/media_player/frontier_silicon.py
@@ -4,7 +4,6 @@ Support for Frontier Silicon Devices (Medion, Hama, Auna,...).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.frontier_silicon/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -41,8 +40,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(
+async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the Frontier Silicon platform."""
import requests
@@ -157,18 +155,17 @@ class AFSAPIDevice(MediaPlayerDevice):
"""Image url of current playing media."""
return self._media_image_url
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest date and update device state."""
fs_device = self.fs_device
if not self._name:
- self._name = yield from fs_device.get_friendly_name()
+ self._name = await fs_device.get_friendly_name()
if not self._source_list:
- self._source_list = yield from fs_device.get_mode_list()
+ self._source_list = await fs_device.get_mode_list()
- status = yield from fs_device.get_play_status()
+ status = await fs_device.get_play_status()
self._state = {
'playing': STATE_PLAYING,
'paused': STATE_PAUSED,
@@ -178,16 +175,16 @@ class AFSAPIDevice(MediaPlayerDevice):
}.get(status, STATE_UNKNOWN)
if self._state != STATE_OFF:
- info_name = yield from fs_device.get_play_name()
- info_text = yield from fs_device.get_play_text()
+ info_name = await fs_device.get_play_name()
+ info_text = await fs_device.get_play_text()
self._title = ' - '.join(filter(None, [info_name, info_text]))
- self._artist = yield from fs_device.get_play_artist()
- self._album_name = yield from fs_device.get_play_album()
+ self._artist = await fs_device.get_play_artist()
+ self._album_name = await fs_device.get_play_album()
- self._source = yield from fs_device.get_mode()
- self._mute = yield from fs_device.get_mute()
- self._media_image_url = yield from fs_device.get_play_graphic()
+ self._source = await fs_device.get_mode()
+ self._mute = await fs_device.get_mute()
+ self._media_image_url = await fs_device.get_play_graphic()
else:
self._title = None
self._artist = None
@@ -199,48 +196,40 @@ class AFSAPIDevice(MediaPlayerDevice):
# Management actions
# power control
- @asyncio.coroutine
- def async_turn_on(self):
+ async def async_turn_on(self):
"""Turn on the device."""
- yield from self.fs_device.set_power(True)
+ await self.fs_device.set_power(True)
- @asyncio.coroutine
- def async_turn_off(self):
+ async def async_turn_off(self):
"""Turn off the device."""
- yield from self.fs_device.set_power(False)
+ await self.fs_device.set_power(False)
- @asyncio.coroutine
- def async_media_play(self):
+ async def async_media_play(self):
"""Send play command."""
- yield from self.fs_device.play()
+ await self.fs_device.play()
- @asyncio.coroutine
- def async_media_pause(self):
+ async def async_media_pause(self):
"""Send pause command."""
- yield from self.fs_device.pause()
+ await self.fs_device.pause()
- @asyncio.coroutine
- def async_media_play_pause(self):
+ async def async_media_play_pause(self):
"""Send play/pause command."""
if 'playing' in self._state:
- yield from self.fs_device.pause()
+ await self.fs_device.pause()
else:
- yield from self.fs_device.play()
+ await self.fs_device.play()
- @asyncio.coroutine
- def async_media_stop(self):
+ async def async_media_stop(self):
"""Send play/pause command."""
- yield from self.fs_device.pause()
+ await self.fs_device.pause()
- @asyncio.coroutine
- def async_media_previous_track(self):
+ async def async_media_previous_track(self):
"""Send previous track command (results in rewind)."""
- yield from self.fs_device.rewind()
+ await self.fs_device.rewind()
- @asyncio.coroutine
- def async_media_next_track(self):
+ async def async_media_next_track(self):
"""Send next track command (results in fast-forward)."""
- yield from self.fs_device.forward()
+ await self.fs_device.forward()
# mute
@property
@@ -248,30 +237,25 @@ class AFSAPIDevice(MediaPlayerDevice):
"""Boolean if volume is currently muted."""
return self._mute
- @asyncio.coroutine
- def async_mute_volume(self, mute):
+ async def async_mute_volume(self, mute):
"""Send mute command."""
- yield from self.fs_device.set_mute(mute)
+ await self.fs_device.set_mute(mute)
# volume
- @asyncio.coroutine
- def async_volume_up(self):
+ async def async_volume_up(self):
"""Send volume up command."""
- volume = yield from self.fs_device.get_volume()
- yield from self.fs_device.set_volume(volume+1)
+ volume = await self.fs_device.get_volume()
+ await self.fs_device.set_volume(volume+1)
- @asyncio.coroutine
- def async_volume_down(self):
+ async def async_volume_down(self):
"""Send volume down command."""
- volume = yield from self.fs_device.get_volume()
- yield from self.fs_device.set_volume(volume-1)
+ volume = await self.fs_device.get_volume()
+ await self.fs_device.set_volume(volume-1)
- @asyncio.coroutine
- def async_set_volume_level(self, volume):
+ async def async_set_volume_level(self, volume):
"""Set volume command."""
- yield from self.fs_device.set_volume(volume)
+ await self.fs_device.set_volume(volume)
- @asyncio.coroutine
- def async_select_source(self, source):
+ async def async_select_source(self, source):
"""Select input source."""
- yield from self.fs_device.set_mode(source)
+ await self.fs_device.set_mode(source)
diff --git a/homeassistant/components/media_player/kodi.py b/homeassistant/components/media_player/kodi.py
index c98dc5c56fe..01d8069dc3b 100644
--- a/homeassistant/components/media_player/kodi.py
+++ b/homeassistant/components/media_player/kodi.py
@@ -156,9 +156,8 @@ def _check_deprecated_turn_off(hass, turn_off_action):
return turn_off_action
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Kodi platform."""
if DATA_KODI not in hass.data:
hass.data[DATA_KODI] = dict()
@@ -211,8 +210,7 @@ def async_setup_platform(hass, config, async_add_entities,
hass.data[DATA_KODI][ip_addr] = entity
async_add_entities([entity], update_before_add=True)
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Map services to methods on MediaPlayerDevice."""
method = SERVICE_TO_METHOD.get(service.service)
if not method:
@@ -230,7 +228,7 @@ def async_setup_platform(hass, config, async_add_entities,
update_tasks = []
for player in target_players:
- yield from getattr(player, method['method'])(**params)
+ await getattr(player, method['method'])(**params)
for player in target_players:
if player.should_poll:
@@ -238,7 +236,7 @@ def async_setup_platform(hass, config, async_add_entities,
update_tasks.append(update_coro)
if update_tasks:
- yield from asyncio.wait(update_tasks, loop=hass.loop)
+ await asyncio.wait(update_tasks, loop=hass.loop)
if hass.services.has_service(DOMAIN, SERVICE_ADD_MEDIA):
return
@@ -253,12 +251,11 @@ def async_setup_platform(hass, config, async_add_entities,
def cmd(func):
"""Catch command exceptions."""
@wraps(func)
- @asyncio.coroutine
- def wrapper(obj, *args, **kwargs):
+ async def wrapper(obj, *args, **kwargs):
"""Wrap all command methods."""
import jsonrpc_base
try:
- yield from func(obj, *args, **kwargs)
+ await func(obj, *args, **kwargs)
except jsonrpc_base.jsonrpc.TransportError as exc:
# If Kodi is off, we expect calls to fail.
if obj.state == STATE_OFF:
@@ -325,7 +322,7 @@ class KodiDevice(MediaPlayerDevice):
def on_hass_stop(event):
"""Close websocket connection when hass stops."""
- self.hass.async_add_job(self._ws_server.close())
+ self.hass.async_create_task(self._ws_server.close())
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, on_hass_stop)
@@ -390,14 +387,13 @@ class KodiDevice(MediaPlayerDevice):
self._properties = {}
self._item = {}
self._app_properties = {}
- self.hass.async_add_job(self._ws_server.close())
+ self.hass.async_create_task(self._ws_server.close())
- @asyncio.coroutine
- def _get_players(self):
+ async def _get_players(self):
"""Return the active player objects or None."""
import jsonrpc_base
try:
- return (yield from self.server.Player.GetActivePlayers())
+ return await self.server.Player.GetActivePlayers()
except jsonrpc_base.jsonrpc.TransportError:
if self._players is not None:
_LOGGER.info("Unable to fetch kodi data")
@@ -423,23 +419,21 @@ class KodiDevice(MediaPlayerDevice):
return STATE_PLAYING
- @asyncio.coroutine
- def async_ws_connect(self):
+ async def async_ws_connect(self):
"""Connect to Kodi via websocket protocol."""
import jsonrpc_base
try:
- ws_loop_future = yield from self._ws_server.ws_connect()
+ ws_loop_future = await self._ws_server.ws_connect()
except jsonrpc_base.jsonrpc.TransportError:
_LOGGER.info("Unable to connect to Kodi via websocket")
_LOGGER.debug(
"Unable to connect to Kodi via websocket", exc_info=True)
return
- @asyncio.coroutine
- def ws_loop_wrapper():
+ async def ws_loop_wrapper():
"""Catch exceptions from the websocket loop task."""
try:
- yield from ws_loop_future
+ await ws_loop_future
except jsonrpc_base.TransportError:
# Kodi abruptly ends ws connection when exiting. We will try
# to reconnect on the next poll.
@@ -451,10 +445,9 @@ class KodiDevice(MediaPlayerDevice):
# run until the websocket connection is closed.
self.hass.loop.create_task(ws_loop_wrapper())
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Retrieve latest state."""
- self._players = yield from self._get_players()
+ self._players = await self._get_players()
if self._players is None:
self._properties = {}
@@ -463,10 +456,10 @@ class KodiDevice(MediaPlayerDevice):
return
if self._enable_websocket and not self._ws_server.connected:
- self.hass.async_add_job(self.async_ws_connect())
+ self.hass.async_create_task(self.async_ws_connect())
self._app_properties = \
- yield from self.server.Application.GetProperties(
+ await self.server.Application.GetProperties(
['volume', 'muted']
)
@@ -475,12 +468,12 @@ class KodiDevice(MediaPlayerDevice):
assert isinstance(player_id, int)
- self._properties = yield from self.server.Player.GetProperties(
+ self._properties = await self.server.Player.GetProperties(
player_id,
['time', 'totaltime', 'speed', 'live']
)
- self._item = (yield from self.server.Player.GetItem(
+ self._item = (await self.server.Player.GetItem(
player_id,
['title', 'file', 'uniqueid', 'thumbnail', 'artist',
'albumartist', 'showtitle', 'album', 'season', 'episode']
@@ -622,38 +615,34 @@ class KodiDevice(MediaPlayerDevice):
return supported_features
@cmd
- @asyncio.coroutine
- def async_turn_on(self):
+ async def async_turn_on(self):
"""Execute turn_on_action to turn on media player."""
if self._turn_on_action is not None:
- yield from self._turn_on_action.async_run(
+ await self._turn_on_action.async_run(
variables={"entity_id": self.entity_id})
else:
_LOGGER.warning("turn_on requested but turn_on_action is none")
@cmd
- @asyncio.coroutine
- def async_turn_off(self):
+ async def async_turn_off(self):
"""Execute turn_off_action to turn off media player."""
if self._turn_off_action is not None:
- yield from self._turn_off_action.async_run(
+ await self._turn_off_action.async_run(
variables={"entity_id": self.entity_id})
else:
_LOGGER.warning("turn_off requested but turn_off_action is none")
@cmd
- @asyncio.coroutine
- def async_volume_up(self):
+ async def async_volume_up(self):
"""Volume up the media player."""
assert (
- yield from self.server.Input.ExecuteAction('volumeup')) == 'OK'
+ await self.server.Input.ExecuteAction('volumeup')) == 'OK'
@cmd
- @asyncio.coroutine
- def async_volume_down(self):
+ async def async_volume_down(self):
"""Volume down the media player."""
assert (
- yield from self.server.Input.ExecuteAction('volumedown')) == 'OK'
+ await self.server.Input.ExecuteAction('volumedown')) == 'OK'
@cmd
def async_set_volume_level(self, volume):
@@ -671,13 +660,12 @@ class KodiDevice(MediaPlayerDevice):
"""
return self.server.Application.SetMute(mute)
- @asyncio.coroutine
- def async_set_play_state(self, state):
+ async def async_set_play_state(self, state):
"""Handle play/pause/toggle."""
- players = yield from self._get_players()
+ players = await self._get_players()
if players is not None and players:
- yield from self.server.Player.PlayPause(
+ await self.server.Player.PlayPause(
players[0]['playerid'], state)
@cmd
@@ -705,26 +693,24 @@ class KodiDevice(MediaPlayerDevice):
return self.async_set_play_state(False)
@cmd
- @asyncio.coroutine
- def async_media_stop(self):
+ async def async_media_stop(self):
"""Stop the media player."""
- players = yield from self._get_players()
+ players = await self._get_players()
if players:
- yield from self.server.Player.Stop(players[0]['playerid'])
+ await self.server.Player.Stop(players[0]['playerid'])
- @asyncio.coroutine
- def _goto(self, direction):
+ async def _goto(self, direction):
"""Handle for previous/next track."""
- players = yield from self._get_players()
+ players = await self._get_players()
if players:
if direction == 'previous':
# First seek to position 0. Kodi goes to the beginning of the
# current track if the current track is not at the beginning.
- yield from self.server.Player.Seek(players[0]['playerid'], 0)
+ await self.server.Player.Seek(players[0]['playerid'], 0)
- yield from self.server.Player.GoTo(
+ await self.server.Player.GoTo(
players[0]['playerid'], direction)
@cmd
@@ -744,10 +730,9 @@ class KodiDevice(MediaPlayerDevice):
return self._goto('previous')
@cmd
- @asyncio.coroutine
- def async_media_seek(self, position):
+ async def async_media_seek(self, position):
"""Send seek command."""
- players = yield from self._get_players()
+ players = await self._get_players()
time = {}
@@ -763,7 +748,7 @@ class KodiDevice(MediaPlayerDevice):
time['hours'] = int(position)
if players:
- yield from self.server.Player.Seek(players[0]['playerid'], time)
+ await self.server.Player.Seek(players[0]['playerid'], time)
@cmd
def async_play_media(self, media_type, media_id, **kwargs):
@@ -781,22 +766,20 @@ class KodiDevice(MediaPlayerDevice):
return self.server.Player.Open(
{"item": {"file": str(media_id)}})
- @asyncio.coroutine
- def async_set_shuffle(self, shuffle):
+ async def async_set_shuffle(self, shuffle):
"""Set shuffle mode, for the first player."""
if not self._players:
raise RuntimeError("Error: No active player.")
- yield from self.server.Player.SetShuffle(
+ await self.server.Player.SetShuffle(
{"playerid": self._players[0]['playerid'], "shuffle": shuffle})
- @asyncio.coroutine
- def async_call_method(self, method, **kwargs):
+ async def async_call_method(self, method, **kwargs):
"""Run Kodi JSONRPC API method with params."""
import jsonrpc_base
_LOGGER.debug("Run API method %s, kwargs=%s", method, kwargs)
result_ok = False
try:
- result = yield from getattr(self.server, method)(**kwargs)
+ result = await getattr(self.server, method)(**kwargs)
result_ok = True
except jsonrpc_base.jsonrpc.ProtocolError as exc:
result = exc.args[2]['error']
@@ -817,8 +800,7 @@ class KodiDevice(MediaPlayerDevice):
event_data=event_data)
return result
- @asyncio.coroutine
- def async_add_media_to_playlist(
+ async def async_add_media_to_playlist(
self, media_type, media_id=None, media_name='ALL', artist_name=''):
"""Add a media to default playlist (i.e. playlistid=0).
@@ -832,7 +814,7 @@ class KodiDevice(MediaPlayerDevice):
params = {"playlistid": 0}
if media_type == "SONG":
if media_id is None:
- media_id = yield from self.async_find_song(
+ media_id = await self.async_find_song(
media_name, artist_name)
if media_id:
params["item"] = {"songid": int(media_id)}
@@ -840,10 +822,10 @@ class KodiDevice(MediaPlayerDevice):
elif media_type == "ALBUM":
if media_id is None:
if media_name == "ALL":
- yield from self.async_add_all_albums(artist_name)
+ await self.async_add_all_albums(artist_name)
return
- media_id = yield from self.async_find_album(
+ media_id = await self.async_find_album(
media_name, artist_name)
if media_id:
params["item"] = {"albumid": int(media_id)}
@@ -853,7 +835,7 @@ class KodiDevice(MediaPlayerDevice):
if media_id is not None:
try:
- yield from self.server.Playlist.Add(params)
+ await self.server.Playlist.Add(params)
except jsonrpc_base.jsonrpc.ProtocolError as exc:
result = exc.args[2]['error']
_LOGGER.error("Run API method %s.Playlist.Add(%s) error: %s",
@@ -864,43 +846,38 @@ class KodiDevice(MediaPlayerDevice):
else:
_LOGGER.warning("No media detected for Playlist.Add")
- @asyncio.coroutine
- def async_add_all_albums(self, artist_name):
+ async def async_add_all_albums(self, artist_name):
"""Add all albums of an artist to default playlist (i.e. playlistid=0).
The artist is specified in terms of name.
"""
- artist_id = yield from self.async_find_artist(artist_name)
+ artist_id = await self.async_find_artist(artist_name)
- albums = yield from self.async_get_albums(artist_id)
+ albums = await self.async_get_albums(artist_id)
for alb in albums['albums']:
- yield from self.server.Playlist.Add(
+ await self.server.Playlist.Add(
{"playlistid": 0, "item": {"albumid": int(alb['albumid'])}})
- @asyncio.coroutine
- def async_clear_playlist(self):
+ async def async_clear_playlist(self):
"""Clear default playlist (i.e. playlistid=0)."""
return self.server.Playlist.Clear({"playlistid": 0})
- @asyncio.coroutine
- def async_get_artists(self):
+ async def async_get_artists(self):
"""Get artists list."""
- return (yield from self.server.AudioLibrary.GetArtists())
+ return await self.server.AudioLibrary.GetArtists()
- @asyncio.coroutine
- def async_get_albums(self, artist_id=None):
+ async def async_get_albums(self, artist_id=None):
"""Get albums list."""
if artist_id is None:
- return (yield from self.server.AudioLibrary.GetAlbums())
+ return await self.server.AudioLibrary.GetAlbums()
- return (yield from self.server.AudioLibrary.GetAlbums(
+ return (await self.server.AudioLibrary.GetAlbums(
{"filter": {"artistid": int(artist_id)}}))
- @asyncio.coroutine
- def async_find_artist(self, artist_name):
+ async def async_find_artist(self, artist_name):
"""Find artist by name."""
- artists = yield from self.async_get_artists()
+ artists = await self.async_get_artists()
try:
out = self._find(
artist_name, [a['artist'] for a in artists['artists']])
@@ -909,37 +886,34 @@ class KodiDevice(MediaPlayerDevice):
_LOGGER.warning("No artists were found: %s", artist_name)
return None
- @asyncio.coroutine
- def async_get_songs(self, artist_id=None):
+ async def async_get_songs(self, artist_id=None):
"""Get songs list."""
if artist_id is None:
- return (yield from self.server.AudioLibrary.GetSongs())
+ return await self.server.AudioLibrary.GetSongs()
- return (yield from self.server.AudioLibrary.GetSongs(
+ return (await self.server.AudioLibrary.GetSongs(
{"filter": {"artistid": int(artist_id)}}))
- @asyncio.coroutine
- def async_find_song(self, song_name, artist_name=''):
+ async def async_find_song(self, song_name, artist_name=''):
"""Find song by name and optionally artist name."""
artist_id = None
if artist_name != '':
- artist_id = yield from self.async_find_artist(artist_name)
+ artist_id = await self.async_find_artist(artist_name)
- songs = yield from self.async_get_songs(artist_id)
+ songs = await self.async_get_songs(artist_id)
if songs['limits']['total'] == 0:
return None
out = self._find(song_name, [a['label'] for a in songs['songs']])
return songs['songs'][out[0][0]]['songid']
- @asyncio.coroutine
- def async_find_album(self, album_name, artist_name=''):
+ async def async_find_album(self, album_name, artist_name=''):
"""Find album by name and optionally artist name."""
artist_id = None
if artist_name != '':
- artist_id = yield from self.async_find_artist(artist_name)
+ artist_id = await self.async_find_artist(artist_name)
- albums = yield from self.async_get_albums(artist_id)
+ albums = await self.async_get_albums(artist_id)
try:
out = self._find(
album_name, [a['label'] for a in albums['albums']])
diff --git a/homeassistant/components/media_player/liveboxplaytv.py b/homeassistant/components/media_player/liveboxplaytv.py
index 9a08ceeac93..3f8ea2cfd48 100644
--- a/homeassistant/components/media_player/liveboxplaytv.py
+++ b/homeassistant/components/media_player/liveboxplaytv.py
@@ -4,7 +4,6 @@ Support for interface with an Orange Livebox Play TV appliance.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.liveboxplaytv/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -44,9 +43,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Orange Livebox Play TV platform."""
host = config.get(CONF_HOST)
port = config.get(CONF_PORT)
@@ -83,8 +81,7 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
self._media_image_url = None
self._media_last_updated = None
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Retrieve the latest data."""
import pyteleloisirs
try:
@@ -95,7 +92,7 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
channel = self._client.channel
if channel is not None:
self._current_channel = channel
- program = yield from \
+ program = await \
self._client.async_get_current_program()
if program and self._current_program != program.get('name'):
self._current_program = program.get('name')
@@ -109,7 +106,7 @@ class LiveboxPlayTvDevice(MediaPlayerDevice):
# Set media image to current program if a thumbnail is
# available. Otherwise we'll use the channel's image.
img_size = 800
- prg_img_url = yield from \
+ prg_img_url = await \
self._client.async_get_current_program_image(img_size)
if prg_img_url:
self._media_image_url = prg_img_url
diff --git a/homeassistant/components/media_player/russound_rio.py b/homeassistant/components/media_player/russound_rio.py
index 74f6bfb35ab..19cc2228d32 100644
--- a/homeassistant/components/media_player/russound_rio.py
+++ b/homeassistant/components/media_player/russound_rio.py
@@ -4,7 +4,6 @@ Support for Russound multizone controllers using RIO Protocol.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.russound_rio/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -33,8 +32,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(
+async def async_setup_platform(
hass, config, async_add_entities, discovery_info=None):
"""Set up the Russound RIO platform."""
from russound_rio import Russound
@@ -44,15 +42,15 @@ def async_setup_platform(
russ = Russound(hass.loop, host, port)
- yield from russ.connect()
+ await russ.connect()
# Discover sources and zones
- sources = yield from russ.enumerate_sources()
- valid_zones = yield from russ.enumerate_zones()
+ sources = await russ.enumerate_sources()
+ valid_zones = await russ.enumerate_zones()
devices = []
for zone_id, name in valid_zones:
- yield from russ.watch_zone(zone_id)
+ await russ.watch_zone(zone_id)
dev = RussoundZoneDevice(russ, zone_id, name, sources)
devices.append(dev)
@@ -108,8 +106,7 @@ class RussoundZoneDevice(MediaPlayerDevice):
if source_id == current:
self.schedule_update_ha_state()
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callback handlers."""
self._russ.add_zone_callback(self._zone_callback_handler)
self._russ.add_source_callback(self._source_callback_handler)
diff --git a/homeassistant/components/media_player/samsungtv.py b/homeassistant/components/media_player/samsungtv.py
index c0a5d617f19..3a66aa66dc0 100644
--- a/homeassistant/components/media_player/samsungtv.py
+++ b/homeassistant/components/media_player/samsungtv.py
@@ -30,7 +30,7 @@ _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'Samsung TV Remote'
DEFAULT_PORT = 55000
-DEFAULT_TIMEOUT = 0
+DEFAULT_TIMEOUT = 1
KEY_PRESS_TIMEOUT = 1.2
KNOWN_DEVICES_KEY = 'samsungtv_known_devices'
@@ -125,10 +125,14 @@ class SamsungTVDevice(MediaPlayerDevice):
def update(self):
"""Update state of device."""
if sys.platform == 'win32':
- _ping_cmd = ['ping', '-n 1', '-w', '1000', self._config['host']]
+ timeout_arg = '-w {}000'.format(self._config['timeout'])
+ _ping_cmd = [
+ 'ping', '-n 3', timeout_arg, self._config['host']]
else:
- _ping_cmd = ['ping', '-n', '-q', '-c1', '-W1',
- self._config['host']]
+ timeout_arg = '-W{}'.format(self._config['timeout'])
+ _ping_cmd = [
+ 'ping', '-n', '-q',
+ '-c3', timeout_arg, self._config['host']]
ping = subprocess.Popen(
_ping_cmd,
diff --git a/homeassistant/components/media_player/snapcast.py b/homeassistant/components/media_player/snapcast.py
index 84864caeed1..fca440df783 100644
--- a/homeassistant/components/media_player/snapcast.py
+++ b/homeassistant/components/media_player/snapcast.py
@@ -4,7 +4,6 @@ Support for interacting with Snapcast clients.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.snapcast/
"""
-import asyncio
import logging
import socket
@@ -46,17 +45,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Snapcast platform."""
import snapcast.control
from snapcast.control.server import CONTROL_PORT
host = config.get(CONF_HOST)
port = config.get(CONF_PORT, CONTROL_PORT)
- @asyncio.coroutine
- def _handle_service(service):
+ async def _handle_service(service):
"""Handle services."""
entity_ids = service.data.get(ATTR_ENTITY_ID)
devices = [device for device in hass.data[DATA_KEY]
@@ -65,7 +62,7 @@ def async_setup_platform(hass, config, async_add_entities,
if service.service == SERVICE_SNAPSHOT:
device.snapshot()
elif service.service == SERVICE_RESTORE:
- yield from device.async_restore()
+ await device.async_restore()
hass.services.async_register(
DOMAIN, SERVICE_SNAPSHOT, _handle_service, schema=SERVICE_SCHEMA)
@@ -73,7 +70,7 @@ def async_setup_platform(hass, config, async_add_entities,
DOMAIN, SERVICE_RESTORE, _handle_service, schema=SERVICE_SCHEMA)
try:
- server = yield from snapcast.control.create_server(
+ server = await snapcast.control.create_server(
hass.loop, host, port, reconnect=True)
except socket.gaierror:
_LOGGER.error("Could not connect to Snapcast server at %s:%d",
@@ -157,34 +154,30 @@ class SnapcastGroupDevice(MediaPlayerDevice):
"""Do not poll for state."""
return False
- @asyncio.coroutine
- def async_select_source(self, source):
+ async def async_select_source(self, source):
"""Set input source."""
streams = self._group.streams_by_name()
if source in streams:
- yield from self._group.set_stream(streams[source].identifier)
+ await self._group.set_stream(streams[source].identifier)
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_mute_volume(self, mute):
+ async def async_mute_volume(self, mute):
"""Send the mute command."""
- yield from self._group.set_muted(mute)
+ await self._group.set_muted(mute)
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_volume_level(self, volume):
+ async def async_set_volume_level(self, volume):
"""Set the volume level."""
- yield from self._group.set_volume(round(volume * 100))
+ await self._group.set_volume(round(volume * 100))
self.async_schedule_update_ha_state()
def snapshot(self):
"""Snapshot the group state."""
self._group.snapshot()
- @asyncio.coroutine
- def async_restore(self):
+ async def async_restore(self):
"""Restore the group state."""
- yield from self._group.restore()
+ await self._group.restore()
class SnapcastClientDevice(MediaPlayerDevice):
@@ -246,23 +239,20 @@ class SnapcastClientDevice(MediaPlayerDevice):
"""Do not poll for state."""
return False
- @asyncio.coroutine
- def async_mute_volume(self, mute):
+ async def async_mute_volume(self, mute):
"""Send the mute command."""
- yield from self._client.set_muted(mute)
+ await self._client.set_muted(mute)
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_volume_level(self, volume):
+ async def async_set_volume_level(self, volume):
"""Set the volume level."""
- yield from self._client.set_volume(round(volume * 100))
+ await self._client.set_volume(round(volume * 100))
self.async_schedule_update_ha_state()
def snapshot(self):
"""Snapshot the client state."""
self._client.snapshot()
- @asyncio.coroutine
- def async_restore(self):
+ async def async_restore(self):
"""Restore the client state."""
- yield from self._client.restore()
+ await self._client.restore()
diff --git a/homeassistant/components/media_player/sonos.py b/homeassistant/components/media_player/sonos.py
index fd735a5b830..41ca1b4e85e 100644
--- a/homeassistant/components/media_player/sonos.py
+++ b/homeassistant/components/media_player/sonos.py
@@ -4,7 +4,6 @@ Support to interface with Sonos players.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.sonos/
"""
-import asyncio
import datetime
import functools as ft
import logging
@@ -33,9 +32,7 @@ _LOGGER = logging.getLogger(__name__)
# Quiet down pysonos logging to just actual problems.
logging.getLogger('pysonos').setLevel(logging.WARNING)
-logging.getLogger('pysonos.events').setLevel(logging.ERROR)
logging.getLogger('pysonos.data_structures_entry').setLevel(logging.ERROR)
-_SOCO_SERVICES_LOGGER = logging.getLogger('pysonos.services')
SUPPORT_SONOS = SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PLAY | SUPPORT_PAUSE | SUPPORT_STOP | SUPPORT_SELECT_SOURCE |\
@@ -72,7 +69,7 @@ ATTR_SPEECH_ENHANCE = 'speech_enhance'
ATTR_SONOS_GROUP = 'sonos_group'
-UPNP_ERRORS_TO_IGNORE = ['701', '711']
+UPNP_ERRORS_TO_IGNORE = ['701', '711', '712']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ADVERTISE_ADDR): cv.string,
@@ -136,9 +133,9 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
"""Sync version of async add devices."""
hass.add_job(async_add_entities, devices, update_before_add)
- hass.add_job(_setup_platform, hass,
- hass.data[SONOS_DOMAIN].get('media_player', {}),
- add_entities, None)
+ hass.async_add_executor_job(
+ _setup_platform, hass, hass.data[SONOS_DOMAIN].get('media_player', {}),
+ add_entities, None)
def _setup_platform(hass, config, add_entities, discovery_info):
@@ -289,10 +286,6 @@ def soco_error(errorcodes=None):
"""Wrap for all soco UPnP exception."""
from pysonos.exceptions import SoCoUPnPException, SoCoException
- # Temporarily disable SoCo logging because it will log the
- # UPnP exception otherwise
- _SOCO_SERVICES_LOGGER.disabled = True
-
try:
return funct(*args, **kwargs)
except SoCoUPnPException as err:
@@ -302,8 +295,6 @@ def soco_error(errorcodes=None):
_LOGGER.error("Error on %s with %s", funct.__name__, err)
except SoCoException as err:
_LOGGER.error("Error on %s with %s", funct.__name__, err)
- finally:
- _SOCO_SERVICES_LOGGER.disabled = False
return wrapper
return decorator
@@ -344,13 +335,13 @@ class SonosDevice(MediaPlayerDevice):
def __init__(self, player):
"""Initialize the Sonos device."""
self._receives_events = False
- self._volume_increment = 5
+ self._volume_increment = 2
self._unique_id = player.uid
self._player = player
self._model = None
self._player_volume = None
self._player_muted = None
- self._play_mode = None
+ self._shuffle = None
self._name = None
self._coordinator = None
self._sonos_group = None
@@ -372,11 +363,10 @@ class SonosDevice(MediaPlayerDevice):
self._set_basic_information()
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe sonos events."""
self.hass.data[DATA_SONOS].devices.append(self)
- self.hass.async_add_job(self._subscribe_to_player_events)
+ self.hass.async_add_executor_job(self._subscribe_to_player_events)
@property
def unique_id(self):
@@ -447,7 +437,7 @@ class SonosDevice(MediaPlayerDevice):
speaker_info = self.soco.get_speaker_info(True)
self._name = speaker_info['zone_name']
self._model = speaker_info['model_name']
- self._play_mode = self.soco.play_mode
+ self._shuffle = self.soco.shuffle
self.update_volume()
@@ -540,7 +530,7 @@ class SonosDevice(MediaPlayerDevice):
if new_status == 'TRANSITIONING':
return
- self._play_mode = self.soco.play_mode
+ self._shuffle = self.soco.shuffle
if self.soco.is_playing_tv:
self.update_media_linein(SOURCE_TV)
@@ -765,7 +755,7 @@ class SonosDevice(MediaPlayerDevice):
@soco_coordinator
def shuffle(self):
"""Shuffling state."""
- return 'SHUFFLE' in self._play_mode
+ return self._shuffle
@property
def media_content_type(self):
@@ -841,11 +831,11 @@ class SonosDevice(MediaPlayerDevice):
"""Set volume level, range 0..1."""
self.soco.volume = str(int(volume * 100))
- @soco_error()
+ @soco_error(UPNP_ERRORS_TO_IGNORE)
@soco_coordinator
def set_shuffle(self, shuffle):
"""Enable/Disable shuffle mode."""
- self.soco.play_mode = 'SHUFFLE_NOREPEAT' if shuffle else 'NORMAL'
+ self.soco.shuffle = shuffle
@soco_error()
def mute_volume(self, mute):
diff --git a/homeassistant/components/media_player/soundtouch.py b/homeassistant/components/media_player/soundtouch.py
index b8ade374a46..037a9b88fc6 100644
--- a/homeassistant/components/media_player/soundtouch.py
+++ b/homeassistant/components/media_player/soundtouch.py
@@ -297,7 +297,7 @@ class SoundTouchDevice(MediaPlayerDevice):
def play_media(self, media_type, media_id, **kwargs):
"""Play a piece of media."""
_LOGGER.debug("Starting media with media_id: %s", media_id)
- if re.match(r'https?://', str(media_id)):
+ if re.match(r'http?://', str(media_id)):
# URL
_LOGGER.debug("Playing URL %s", str(media_id))
self._device.play_url(str(media_id))
diff --git a/homeassistant/components/media_player/squeezebox.py b/homeassistant/components/media_player/squeezebox.py
index 2d6a849aecb..f8347830141 100644
--- a/homeassistant/components/media_player/squeezebox.py
+++ b/homeassistant/components/media_player/squeezebox.py
@@ -64,9 +64,8 @@ SERVICE_TO_METHOD = {
}
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the squeezebox platform."""
import socket
@@ -106,13 +105,12 @@ def async_setup_platform(hass, config, async_add_entities,
_LOGGER.debug("Creating LMS object for %s", ipaddr)
lms = LogitechMediaServer(hass, host, port, username, password)
- players = yield from lms.create_players()
+ players = await lms.create_players()
hass.data[DATA_SQUEEZEBOX].extend(players)
async_add_entities(players)
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Map services to methods on MediaPlayerDevice."""
method = SERVICE_TO_METHOD.get(service.service)
if not method:
@@ -129,11 +127,11 @@ def async_setup_platform(hass, config, async_add_entities,
update_tasks = []
for player in target_players:
- yield from getattr(player, method['method'])(**params)
+ await getattr(player, method['method'])(**params)
update_tasks.append(player.async_update_ha_state(True))
if update_tasks:
- yield from asyncio.wait(update_tasks, loop=hass.loop)
+ await asyncio.wait(update_tasks, loop=hass.loop)
for service in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[service]['schema']
@@ -155,22 +153,20 @@ class LogitechMediaServer:
self._username = username
self._password = password
- @asyncio.coroutine
- def create_players(self):
+ async def create_players(self):
"""Create a list of devices connected to LMS."""
result = []
- data = yield from self.async_query('players', 'status')
+ data = await self.async_query('players', 'status')
if data is False:
return result
for players in data.get('players_loop', []):
player = SqueezeBoxDevice(
self, players['playerid'], players['name'])
- yield from player.async_update()
+ await player.async_update()
result.append(player)
return result
- @asyncio.coroutine
- def async_query(self, *command, player=""):
+ async def async_query(self, *command, player=""):
"""Abstract out the JSON-RPC connection."""
auth = None if self._username is None else aiohttp.BasicAuth(
self._username, self._password)
@@ -187,7 +183,7 @@ class LogitechMediaServer:
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
- response = yield from websession.post(
+ response = await websession.post(
url,
data=data,
auth=auth)
@@ -198,7 +194,7 @@ class LogitechMediaServer:
response.status, response)
return False
- data = yield from response.json()
+ data = await response.json()
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed communicating with LMS: %s", type(error))
@@ -256,11 +252,10 @@ class SqueezeBoxDevice(MediaPlayerDevice):
return self._lms.async_query(
*parameters, player=self._id)
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Retrieve the current state of the player."""
tags = 'adKl'
- response = yield from self.async_query(
+ response = await self.async_query(
"status", "-", "1", "tags:{tags}"
.format(tags=tags))
diff --git a/homeassistant/components/media_player/volumio.py b/homeassistant/components/media_player/volumio.py
index 743f19cb259..de0f726c2ce 100644
--- a/homeassistant/components/media_player/volumio.py
+++ b/homeassistant/components/media_player/volumio.py
@@ -51,9 +51,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Volumio platform."""
if DATA_VOLUMIO not in hass.data:
hass.data[DATA_VOLUMIO] = dict()
@@ -96,8 +95,7 @@ class Volumio(MediaPlayerDevice):
self._playlists = []
self._currentplaylist = None
- @asyncio.coroutine
- def send_volumio_msg(self, method, params=None):
+ async def send_volumio_msg(self, method, params=None):
"""Send message."""
url = "http://{}:{}/api/v1/{}/".format(self.host, self.port, method)
@@ -105,9 +103,9 @@ class Volumio(MediaPlayerDevice):
try:
websession = async_get_clientsession(self.hass)
- response = yield from websession.get(url, params=params)
+ response = await websession.get(url, params=params)
if response.status == 200:
- data = yield from response.json()
+ data = await response.json()
else:
_LOGGER.error(
"Query failed, response code: %s Full message: %s",
@@ -124,11 +122,10 @@ class Volumio(MediaPlayerDevice):
_LOGGER.error("Received invalid response: %s", data)
return False
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update state."""
- resp = yield from self.send_volumio_msg('getState')
- yield from self._async_update_playlists()
+ resp = await self.send_volumio_msg('getState')
+ await self._async_update_playlists()
if resp is False:
return
self._state = resp.copy()
diff --git a/homeassistant/components/microsoft_face.py b/homeassistant/components/microsoft_face.py
index e0e0e716d2e..9be2f8eadf5 100644
--- a/homeassistant/components/microsoft_face.py
+++ b/homeassistant/components/microsoft_face.py
@@ -69,45 +69,7 @@ SCHEMA_TRAIN_SERVICE = vol.Schema({
})
-def create_group(hass, name):
- """Create a new person group."""
- data = {ATTR_NAME: name}
- hass.services.call(DOMAIN, SERVICE_CREATE_GROUP, data)
-
-
-def delete_group(hass, name):
- """Delete a person group."""
- data = {ATTR_NAME: name}
- hass.services.call(DOMAIN, SERVICE_DELETE_GROUP, data)
-
-
-def train_group(hass, group):
- """Train a person group."""
- data = {ATTR_GROUP: group}
- hass.services.call(DOMAIN, SERVICE_TRAIN_GROUP, data)
-
-
-def create_person(hass, group, name):
- """Create a person in a group."""
- data = {ATTR_GROUP: group, ATTR_NAME: name}
- hass.services.call(DOMAIN, SERVICE_CREATE_PERSON, data)
-
-
-def delete_person(hass, group, name):
- """Delete a person in a group."""
- data = {ATTR_GROUP: group, ATTR_NAME: name}
- hass.services.call(DOMAIN, SERVICE_DELETE_PERSON, data)
-
-
-def face_person(hass, group, person, camera_entity):
- """Add a new face picture to a person."""
- data = {ATTR_GROUP: group, ATTR_PERSON: person,
- ATTR_CAMERA_ENTITY: camera_entity}
- hass.services.call(DOMAIN, SERVICE_FACE_PERSON, data)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up Microsoft Face."""
entities = {}
face = MicrosoftFace(
@@ -120,26 +82,25 @@ def async_setup(hass, config):
try:
# read exists group/person from cloud and create entities
- yield from face.update_store()
+ await face.update_store()
except HomeAssistantError as err:
_LOGGER.error("Can't load data from face api: %s", err)
return False
hass.data[DATA_MICROSOFT_FACE] = face
- @asyncio.coroutine
- def async_create_group(service):
+ async def async_create_group(service):
"""Create a new person group."""
name = service.data[ATTR_NAME]
g_id = slugify(name)
try:
- yield from face.call_api(
+ await face.call_api(
'put', "persongroups/{0}".format(g_id), {'name': name})
face.store[g_id] = {}
entities[g_id] = MicrosoftFaceGroupEntity(hass, face, g_id, name)
- yield from entities[g_id].async_update_ha_state()
+ await entities[g_id].async_update_ha_state()
except HomeAssistantError as err:
_LOGGER.error("Can't create group '%s' with error: %s", g_id, err)
@@ -147,13 +108,12 @@ def async_setup(hass, config):
DOMAIN, SERVICE_CREATE_GROUP, async_create_group,
schema=SCHEMA_GROUP_SERVICE)
- @asyncio.coroutine
- def async_delete_group(service):
+ async def async_delete_group(service):
"""Delete a person group."""
g_id = slugify(service.data[ATTR_NAME])
try:
- yield from face.call_api('delete', "persongroups/{0}".format(g_id))
+ await face.call_api('delete', "persongroups/{0}".format(g_id))
face.store.pop(g_id)
entity = entities.pop(g_id)
@@ -165,13 +125,12 @@ def async_setup(hass, config):
DOMAIN, SERVICE_DELETE_GROUP, async_delete_group,
schema=SCHEMA_GROUP_SERVICE)
- @asyncio.coroutine
- def async_train_group(service):
+ async def async_train_group(service):
"""Train a person group."""
g_id = service.data[ATTR_GROUP]
try:
- yield from face.call_api(
+ await face.call_api(
'post', "persongroups/{0}/train".format(g_id))
except HomeAssistantError as err:
_LOGGER.error("Can't train group '%s' with error: %s", g_id, err)
@@ -180,19 +139,18 @@ def async_setup(hass, config):
DOMAIN, SERVICE_TRAIN_GROUP, async_train_group,
schema=SCHEMA_TRAIN_SERVICE)
- @asyncio.coroutine
- def async_create_person(service):
+ async def async_create_person(service):
"""Create a person in a group."""
name = service.data[ATTR_NAME]
g_id = service.data[ATTR_GROUP]
try:
- user_data = yield from face.call_api(
+ user_data = await face.call_api(
'post', "persongroups/{0}/persons".format(g_id), {'name': name}
)
face.store[g_id][name] = user_data['personId']
- yield from entities[g_id].async_update_ha_state()
+ await entities[g_id].async_update_ha_state()
except HomeAssistantError as err:
_LOGGER.error("Can't create person '%s' with error: %s", name, err)
@@ -200,19 +158,18 @@ def async_setup(hass, config):
DOMAIN, SERVICE_CREATE_PERSON, async_create_person,
schema=SCHEMA_PERSON_SERVICE)
- @asyncio.coroutine
- def async_delete_person(service):
+ async def async_delete_person(service):
"""Delete a person in a group."""
name = service.data[ATTR_NAME]
g_id = service.data[ATTR_GROUP]
p_id = face.store[g_id].get(name)
try:
- yield from face.call_api(
+ await face.call_api(
'delete', "persongroups/{0}/persons/{1}".format(g_id, p_id))
face.store[g_id].pop(name)
- yield from entities[g_id].async_update_ha_state()
+ await entities[g_id].async_update_ha_state()
except HomeAssistantError as err:
_LOGGER.error("Can't delete person '%s' with error: %s", p_id, err)
@@ -220,8 +177,7 @@ def async_setup(hass, config):
DOMAIN, SERVICE_DELETE_PERSON, async_delete_person,
schema=SCHEMA_PERSON_SERVICE)
- @asyncio.coroutine
- def async_face_person(service):
+ async def async_face_person(service):
"""Add a new face picture to a person."""
g_id = service.data[ATTR_GROUP]
p_id = face.store[g_id].get(service.data[ATTR_PERSON])
@@ -230,9 +186,9 @@ def async_setup(hass, config):
camera = hass.components.camera
try:
- image = yield from camera.async_get_image(hass, camera_entity)
+ image = await camera.async_get_image(hass, camera_entity)
- yield from face.call_api(
+ await face.call_api(
'post',
"persongroups/{0}/persons/{1}/persistedFaces".format(
g_id, p_id),
@@ -307,10 +263,9 @@ class MicrosoftFace:
"""Store group/person data and IDs."""
return self._store
- @asyncio.coroutine
- def update_store(self):
+ async def update_store(self):
"""Load all group/person data into local store."""
- groups = yield from self.call_api('get', 'persongroups')
+ groups = await self.call_api('get', 'persongroups')
tasks = []
for group in groups:
@@ -319,7 +274,7 @@ class MicrosoftFace:
self._entities[g_id] = MicrosoftFaceGroupEntity(
self.hass, self, g_id, group['name'])
- persons = yield from self.call_api(
+ persons = await self.call_api(
'get', "persongroups/{0}/persons".format(g_id))
for person in persons:
@@ -328,11 +283,10 @@ class MicrosoftFace:
tasks.append(self._entities[g_id].async_update_ha_state())
if tasks:
- yield from asyncio.wait(tasks, loop=self.hass.loop)
+ await asyncio.wait(tasks, loop=self.hass.loop)
- @asyncio.coroutine
- def call_api(self, method, function, data=None, binary=False,
- params=None):
+ async def call_api(self, method, function, data=None, binary=False,
+ params=None):
"""Make an api call."""
headers = {"Ocp-Apim-Subscription-Key": self._api_key}
url = self._server_url.format(function)
@@ -350,10 +304,10 @@ class MicrosoftFace:
try:
with async_timeout.timeout(self.timeout, loop=self.hass.loop):
- response = yield from getattr(self.websession, method)(
+ response = await getattr(self.websession, method)(
url, data=payload, headers=headers, params=params)
- answer = yield from response.json()
+ answer = await response.json()
_LOGGER.debug("Read from microsoft face api: %s", answer)
if response.status < 300:
diff --git a/homeassistant/components/mqtt/.translations/ca.json b/homeassistant/components/mqtt/.translations/ca.json
index b6c73f35f26..72a2636fb60 100644
--- a/homeassistant/components/mqtt/.translations/ca.json
+++ b/homeassistant/components/mqtt/.translations/ca.json
@@ -10,13 +10,20 @@
"broker": {
"data": {
"broker": "Broker",
- "discovery": "Activar descobreix automaticament",
+ "discovery": "Habilita descobriment autom\u00e0tic",
"password": "Contrasenya",
"port": "Port",
"username": "Nom d'usuari"
},
"description": "Introdu\u00efu la informaci\u00f3 de connexi\u00f3 del vostre broker MQTT.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Habilita descobriment autom\u00e0tic"
+ },
+ "description": "Voleu configurar Home Assistant perqu\u00e8 es connecti amb el broker MQTT proporcionat pel complement de hass.io {addon}?",
+ "title": "Broker MQTT a trav\u00e9s del complement de Hass.io"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/de.json b/homeassistant/components/mqtt/.translations/de.json
index 2a35e95f559..1c895136d9d 100644
--- a/homeassistant/components/mqtt/.translations/de.json
+++ b/homeassistant/components/mqtt/.translations/de.json
@@ -9,12 +9,21 @@
"step": {
"broker": {
"data": {
+ "broker": "Server",
"discovery": "Suche aktivieren",
"password": "Passwort",
+ "port": "Port",
"username": "Benutzername"
},
"description": "Bitte gib die Verbindungsinformationen deines MQTT-Brokers ein.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Suche aktivieren"
+ },
+ "description": "M\u00f6chten Sie den Home Assistant so konfigurieren, dass er eine Verbindung mit dem MQTT-Broker herstellt, der vom Add-on hass.io {addon} bereitgestellt wird?",
+ "title": "MQTT Broker per Hass.io add-on"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/en.json b/homeassistant/components/mqtt/.translations/en.json
index c0b83a1323f..b0de6dcd782 100644
--- a/homeassistant/components/mqtt/.translations/en.json
+++ b/homeassistant/components/mqtt/.translations/en.json
@@ -17,6 +17,13 @@
},
"description": "Please enter the connection information of your MQTT broker.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Enable discovery"
+ },
+ "description": "Do you want to configure Home Assistant to connect to the MQTT broker provided by the hass.io add-on {addon}?",
+ "title": "MQTT Broker via Hass.io add-on"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/es-419.json b/homeassistant/components/mqtt/.translations/es-419.json
new file mode 100644
index 00000000000..e9e869ae966
--- /dev/null
+++ b/homeassistant/components/mqtt/.translations/es-419.json
@@ -0,0 +1,9 @@
+{
+ "config": {
+ "step": {
+ "hassio_confirm": {
+ "title": "MQTT Broker a trav\u00e9s del complemento Hass.io"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mqtt/.translations/es.json b/homeassistant/components/mqtt/.translations/es.json
new file mode 100644
index 00000000000..e9e869ae966
--- /dev/null
+++ b/homeassistant/components/mqtt/.translations/es.json
@@ -0,0 +1,9 @@
+{
+ "config": {
+ "step": {
+ "hassio_confirm": {
+ "title": "MQTT Broker a trav\u00e9s del complemento Hass.io"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/mqtt/.translations/fr.json b/homeassistant/components/mqtt/.translations/fr.json
index 916b4fdaf39..648c2f972d7 100644
--- a/homeassistant/components/mqtt/.translations/fr.json
+++ b/homeassistant/components/mqtt/.translations/fr.json
@@ -10,13 +10,20 @@
"broker": {
"data": {
"broker": "Broker",
- "discovery": "Activer la d\u00e9couverte automatique",
+ "discovery": "Activer la d\u00e9couverte",
"password": "Mot de passe",
"port": "Port",
"username": "Nom d'utilisateur"
},
"description": "Veuillez entrer les informations de connexion de votre broker MQTT.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Activer la d\u00e9couverte"
+ },
+ "description": "Vous voulez configurer Home Assistant pour vous connecter au broker MQTT fourni par l\u2019Add-on hass.io {addon} ?",
+ "title": "MQTT Broker via le module compl\u00e9mentaire Hass.io"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/hu.json b/homeassistant/components/mqtt/.translations/hu.json
index d85814e917c..ba08d36d581 100644
--- a/homeassistant/components/mqtt/.translations/hu.json
+++ b/homeassistant/components/mqtt/.translations/hu.json
@@ -16,6 +16,12 @@
},
"description": "K\u00e9rlek, add meg az MQTT br\u00f3ker kapcsol\u00f3d\u00e1si adatait.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Felfedez\u00e9s enged\u00e9lyez\u00e9se"
+ },
+ "description": "Szeretn\u00e9d, hogy a Home Assistant csatlakozzon a hass.io addon {addon} \u00e1ltal biztos\u00edtott MQTT br\u00f3kerhez?"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/ko.json b/homeassistant/components/mqtt/.translations/ko.json
index f20658d252c..ed552c0d994 100644
--- a/homeassistant/components/mqtt/.translations/ko.json
+++ b/homeassistant/components/mqtt/.translations/ko.json
@@ -15,8 +15,15 @@
"port": "\ud3ec\ud2b8",
"username": "\uc0ac\uc6a9\uc790 \uc774\ub984"
},
- "description": "MQTT \ube0c\ub85c\ucee4\uc640\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.",
+ "description": "MQTT \ube0c\ub85c\ucee4\uc758 \uc5f0\uacb0 \uc815\ubcf4\ub97c \uc785\ub825\ud574\uc8fc\uc138\uc694.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "\uae30\uae30 \uac80\uc0c9 \ud65c\uc131\ud654"
+ },
+ "description": "Hass.io \uc560\ub4dc\uc628 {addon} \uc5d0\uc11c \uc81c\uacf5\ud558\ub294 MQTT \ube0c\ub85c\ucee4\uc5d0 \uc5f0\uacb0\ud558\ub3c4\ub85d Home Assistant \ub97c \uad6c\uc131 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "title": "Hass.io \uc560\ub4dc\uc628\uc758 MQTT \ube0c\ub85c\ucee4"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/lb.json b/homeassistant/components/mqtt/.translations/lb.json
index 166fce9fbfb..9dcd9c58a3a 100644
--- a/homeassistant/components/mqtt/.translations/lb.json
+++ b/homeassistant/components/mqtt/.translations/lb.json
@@ -17,6 +17,13 @@
},
"description": "Gitt Verbindungs Informatioune vun \u00e4rem MQTT Broker an.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Entdeckung aktiv\u00e9ieren"
+ },
+ "description": "W\u00ebllt dir Home Assistant konfigur\u00e9iere fir sech mam MQTT broker ze verbannen dee vum hass.io add-on {addon} bereet gestallt g\u00ebtt?",
+ "title": "MQTT Broker via Hass.io add-on"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/nl.json b/homeassistant/components/mqtt/.translations/nl.json
index b375f353810..247755d8e89 100644
--- a/homeassistant/components/mqtt/.translations/nl.json
+++ b/homeassistant/components/mqtt/.translations/nl.json
@@ -1,15 +1,29 @@
{
"config": {
+ "abort": {
+ "single_instance_allowed": "Slechts \u00e9\u00e9n configuratie van MQTT is toegestaan."
+ },
+ "error": {
+ "cannot_connect": "Kan geen verbinding maken met de broker."
+ },
"step": {
"broker": {
"data": {
"broker": "Broker",
+ "discovery": "Detectie inschakelen",
"password": "Wachtwoord",
"port": "Poort",
"username": "Gebruikersnaam"
},
"description": "MQTT",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Detectie inschakelen"
+ },
+ "description": "Wilt u Home Assistant configureren om verbinding te maken met de MQTT-broker die wordt aangeboden door de hass.io add-on {addon} ?",
+ "title": "MQTTT Broker via Hass.io add-on"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/no.json b/homeassistant/components/mqtt/.translations/no.json
index 412efd3e107..b3f1e4740b9 100644
--- a/homeassistant/components/mqtt/.translations/no.json
+++ b/homeassistant/components/mqtt/.translations/no.json
@@ -17,6 +17,13 @@
},
"description": "Vennligst oppgi tilkoblingsinformasjonen for din MQTT megler.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Aktiver oppdagelse"
+ },
+ "description": "Vil du konfigurere Home Assistant til \u00e5 koble til MQTT megler gitt av hass.io tillegget {addon}?",
+ "title": "MQTT megler via Hass.io tillegg"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/pl.json b/homeassistant/components/mqtt/.translations/pl.json
index e87e550b98d..33c33c5c095 100644
--- a/homeassistant/components/mqtt/.translations/pl.json
+++ b/homeassistant/components/mqtt/.translations/pl.json
@@ -17,6 +17,13 @@
},
"description": "Wprowad\u017a informacje o po\u0142\u0105czeniu po\u015brednika MQTT.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "W\u0142\u0105cz wykrywanie"
+ },
+ "description": "Czy chcesz skonfigurowa\u0107 Home Assistant'a, aby po\u0142\u0105czy\u0142 si\u0119 z po\u015brednikiem MQTT dostarczonym przez dodatek Hass.io {addon}?",
+ "title": "Po\u015brednik MQTT za po\u015brednictwem dodatku Hass.io"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/pt.json b/homeassistant/components/mqtt/.translations/pt.json
index 42f7c7f5ad2..1b8c3946b7c 100644
--- a/homeassistant/components/mqtt/.translations/pt.json
+++ b/homeassistant/components/mqtt/.translations/pt.json
@@ -10,6 +10,7 @@
"broker": {
"data": {
"broker": "",
+ "discovery": "Ativar descoberta",
"password": "Palavra-passe",
"port": "Porto",
"username": "Utilizador"
diff --git a/homeassistant/components/mqtt/.translations/ru.json b/homeassistant/components/mqtt/.translations/ru.json
index f1ff498dd72..8b0ed27f3ae 100644
--- a/homeassistant/components/mqtt/.translations/ru.json
+++ b/homeassistant/components/mqtt/.translations/ru.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "single_instance_allowed": "\u0414\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442\u0441\u044f \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f MQTT."
+ "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": {
"cannot_connect": "\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443."
@@ -17,6 +17,13 @@
},
"description": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u044e \u043e \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0438 \u043a \u0432\u0430\u0448\u0435\u043c\u0443 \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "\u0420\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u044c \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u0438\u0435"
+ },
+ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Home Assistant \u0434\u043b\u044f \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u044f \u043a \u0431\u0440\u043e\u043a\u0435\u0440\u0443 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io {addon}?",
+ "title": "\u0411\u0440\u043e\u043a\u0435\u0440 MQTT \u0447\u0435\u0440\u0435\u0437 \u0430\u0434\u0434\u043e\u043d Hass.io"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/sl.json b/homeassistant/components/mqtt/.translations/sl.json
index a12498ac4c2..d8d331449a2 100644
--- a/homeassistant/components/mqtt/.translations/sl.json
+++ b/homeassistant/components/mqtt/.translations/sl.json
@@ -17,6 +17,13 @@
},
"description": "Prosimo vnesite informacije o povezavi va\u0161ega MQTT posrednika.",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Omogo\u010di odkrivanje"
+ },
+ "description": "\u017delite konfigurirati Home Assistent-a za povezavo s posrednikom MQTT, ki ga ponuja hass.io add-on {addon} ?",
+ "title": "MQTT Broker prek dodatka Hass.io"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/sv.json b/homeassistant/components/mqtt/.translations/sv.json
index 7cf6d75b9c1..70e3720038d 100644
--- a/homeassistant/components/mqtt/.translations/sv.json
+++ b/homeassistant/components/mqtt/.translations/sv.json
@@ -1,13 +1,31 @@
{
"config": {
+ "abort": {
+ "single_instance_allowed": "Endast en enda konfiguration av MQTT \u00e4r till\u00e5ten."
+ },
+ "error": {
+ "cannot_connect": "Det gick inte att ansluta till broker."
+ },
"step": {
"broker": {
"data": {
+ "broker": "Broker",
+ "discovery": "Aktivera uppt\u00e4ckt",
"password": "L\u00f6senord",
"port": "Port",
"username": "Anv\u00e4ndarnamn"
- }
+ },
+ "description": "V\u00e4nligen ange anslutningsinformationen f\u00f6r din MQTT broker.",
+ "title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "Aktivera uppt\u00e4ckt"
+ },
+ "description": "Vill du konfigurera Home Assistant f\u00f6r att ansluta till MQTT Broker som tillhandah\u00e5lls av hass.io-till\u00e4gget {addon} ?",
+ "title": "MQTT Broker via Hass.io till\u00e4gg"
}
- }
+ },
+ "title": "MQTT"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/mqtt/.translations/zh-Hans.json b/homeassistant/components/mqtt/.translations/zh-Hans.json
index 98a7d9eb4be..f30e1bf10b4 100644
--- a/homeassistant/components/mqtt/.translations/zh-Hans.json
+++ b/homeassistant/components/mqtt/.translations/zh-Hans.json
@@ -17,6 +17,13 @@
},
"description": "\u8bf7\u8f93\u5165\u60a8\u7684 MQTT \u670d\u52a1\u5668\u7684\u8fde\u63a5\u4fe1\u606f\u3002",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "\u542f\u7528\u53d1\u73b0"
+ },
+ "description": "\u662f\u5426\u8981\u914d\u7f6e Home Assistant \u8fde\u63a5\u5230 Hass.io \u52a0\u8f7d\u9879 {addon} \u63d0\u4f9b\u7684 MQTT \u670d\u52a1\u5668\uff1f",
+ "title": "\u6765\u81ea Hass.io \u52a0\u8f7d\u9879\u7684 MQTT \u670d\u52a1\u5668"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/.translations/zh-Hant.json b/homeassistant/components/mqtt/.translations/zh-Hant.json
index cf87ceb8f98..535ed848793 100644
--- a/homeassistant/components/mqtt/.translations/zh-Hant.json
+++ b/homeassistant/components/mqtt/.translations/zh-Hant.json
@@ -10,13 +10,20 @@
"broker": {
"data": {
"broker": "Broker",
- "discovery": "\u958b\u555f\u63a2\u7d22",
+ "discovery": "\u958b\u555f\u641c\u5c0b",
"password": "\u4f7f\u7528\u8005\u5bc6\u78bc",
"port": "\u901a\u8a0a\u57e0",
"username": "\u4f7f\u7528\u8005\u540d\u7a31"
},
"description": "\u8acb\u8f38\u5165 MQTT Broker \u9023\u7dda\u8cc7\u8a0a\u3002",
"title": "MQTT"
+ },
+ "hassio_confirm": {
+ "data": {
+ "discovery": "\u958b\u555f\u641c\u5c0b"
+ },
+ "description": "\u662f\u5426\u8981\u8a2d\u5b9a Home Assistant \u4ee5\u9023\u7dda\u81f3 Hass.io \u9644\u52a0\u7d44\u4ef6 {addon} \u4e4b MQTT broker\uff1f",
+ "title": "\u4f7f\u7528 Hass.io \u9644\u52a0\u7d44\u4ef6 MQTT Broker"
}
},
"title": "MQTT"
diff --git a/homeassistant/components/mqtt/__init__.py b/homeassistant/components/mqtt/__init__.py
index 874f21e5168..3e25563e9ba 100644
--- a/homeassistant/components/mqtt/__init__.py
+++ b/homeassistant/components/mqtt/__init__.py
@@ -254,7 +254,8 @@ def async_publish(hass: HomeAssistantType, topic: Any, payload, qos=None,
"""Publish message to an MQTT topic."""
data = _build_publish_data(topic, qos, retain)
data[ATTR_PAYLOAD] = payload
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_PUBLISH, data))
+ hass.async_create_task(
+ hass.services.async_call(DOMAIN, SERVICE_PUBLISH, data))
@bind_hass
@@ -321,7 +322,8 @@ async def _async_setup_server(hass: HomeAssistantType, config: ConfigType):
async def _async_setup_discovery(hass: HomeAssistantType, conf: ConfigType,
- hass_config: ConfigType) -> bool:
+ hass_config: ConfigType,
+ config_entry) -> bool:
"""Try to start the discovery of MQTT devices.
This method is a coroutine.
@@ -334,7 +336,8 @@ async def _async_setup_discovery(hass: HomeAssistantType, conf: ConfigType,
return False
success = await discovery.async_start(
- hass, conf[CONF_DISCOVERY_PREFIX], hass_config) # type: bool
+ hass, conf[CONF_DISCOVERY_PREFIX], hass_config,
+ config_entry) # type: bool
return success
@@ -409,7 +412,7 @@ async def async_setup_entry(hass, entry):
# If user didn't have configuration.yaml config, generate defaults
if conf is None:
conf = CONFIG_SCHEMA({
- DOMAIN: entry.data
+ DOMAIN: entry.data,
})[DOMAIN]
elif any(key in conf for key in entry.data):
_LOGGER.warning(
@@ -522,7 +525,7 @@ async def async_setup_entry(hass, entry):
if conf.get(CONF_DISCOVERY):
await _async_setup_discovery(
- hass, conf, hass.data[DATA_MQTT_HASS_CONFIG])
+ hass, conf, hass.data[DATA_MQTT_HASS_CONFIG], entry)
return True
@@ -668,7 +671,7 @@ class MQTT:
if any(other.topic == topic for other in self.subscriptions):
# Other subscriptions on topic remaining - don't unsubscribe.
return
- self.hass.async_add_job(self._async_unsubscribe(topic))
+ self.hass.async_create_task(self._async_unsubscribe(topic))
return async_remove
diff --git a/homeassistant/components/mqtt/config_flow.py b/homeassistant/components/mqtt/config_flow.py
index 22072857b03..e0d1e692c60 100644
--- a/homeassistant/components/mqtt/config_flow.py
+++ b/homeassistant/components/mqtt/config_flow.py
@@ -5,7 +5,8 @@ import queue
import voluptuous as vol
from homeassistant import config_entries
-from homeassistant.const import CONF_PASSWORD, CONF_PORT, CONF_USERNAME
+from homeassistant.const import (
+ CONF_PASSWORD, CONF_PORT, CONF_USERNAME, CONF_PROTOCOL)
from .const import CONF_BROKER, CONF_DISCOVERY, DEFAULT_DISCOVERY
@@ -17,6 +18,8 @@ class FlowHandler(config_entries.ConfigFlow):
VERSION = 1
CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_PUSH
+ _hassio_discovery = None
+
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
if self._async_current_entries():
@@ -60,11 +63,65 @@ class FlowHandler(config_entries.ConfigFlow):
return self.async_create_entry(title='configuration.yaml', data={})
+ async def async_step_hassio(self, user_input=None):
+ """Receive a Hass.io discovery."""
+ if self._async_current_entries():
+ return self.async_abort(reason='single_instance_allowed')
-def try_connection(broker, port, username, password):
+ self._hassio_discovery = user_input
+
+ return await self.async_step_hassio_confirm()
+
+ async def async_step_hassio_confirm(self, user_input=None):
+ """Confirm a Hass.io discovery."""
+ errors = {}
+
+ if user_input is not None:
+ data = self._hassio_discovery
+ can_connect = await self.hass.async_add_executor_job(
+ try_connection,
+ data[CONF_BROKER],
+ data[CONF_PORT],
+ data.get(CONF_USERNAME),
+ data.get(CONF_PASSWORD),
+ data.get(CONF_PROTOCOL)
+ )
+
+ if can_connect:
+ return self.async_create_entry(
+ title=data['addon'], data={
+ CONF_BROKER: data[CONF_BROKER],
+ CONF_PORT: data[CONF_PORT],
+ CONF_USERNAME: data.get(CONF_USERNAME),
+ CONF_PASSWORD: data.get(CONF_PASSWORD),
+ CONF_PROTOCOL: data.get(CONF_PROTOCOL),
+ CONF_DISCOVERY: user_input[CONF_DISCOVERY],
+ })
+
+ errors['base'] = 'cannot_connect'
+
+ return self.async_show_form(
+ step_id='hassio_confirm',
+ description_placeholders={
+ 'addon': self._hassio_discovery['addon']
+ },
+ data_schema=vol.Schema({
+ vol.Optional(CONF_DISCOVERY, default=DEFAULT_DISCOVERY): bool
+ }),
+ errors=errors,
+ )
+
+
+def try_connection(broker, port, username, password, protocol='3.1'):
"""Test if we can connect to an MQTT broker."""
import paho.mqtt.client as mqtt
- client = mqtt.Client()
+
+ if protocol == '3.1':
+ proto = mqtt.MQTTv31
+ else:
+ proto = mqtt.MQTTv311
+
+ client = mqtt.Client(protocol=proto)
if username and password:
client.username_pw_set(username, password)
diff --git a/homeassistant/components/mqtt/discovery.py b/homeassistant/components/mqtt/discovery.py
index f42c1ed58e9..a762978a330 100644
--- a/homeassistant/components/mqtt/discovery.py
+++ b/homeassistant/components/mqtt/discovery.py
@@ -4,6 +4,7 @@ Support for MQTT discovery.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt/#discovery
"""
+import asyncio
import json
import logging
import re
@@ -13,6 +14,7 @@ from homeassistant.components.mqtt import CONF_STATE_TOPIC, ATTR_DISCOVERY_HASH
from homeassistant.const import CONF_PLATFORM
from homeassistant.helpers.discovery import async_load_platform
from homeassistant.helpers.dispatcher import async_dispatcher_send
+from homeassistant.helpers.typing import HomeAssistantType
_LOGGER = logging.getLogger(__name__)
@@ -38,11 +40,26 @@ ALLOWED_PLATFORMS = {
'alarm_control_panel': ['mqtt'],
}
+CONFIG_ENTRY_PLATFORMS = {
+ 'binary_sensor': ['mqtt'],
+ 'camera': ['mqtt'],
+ 'cover': ['mqtt'],
+ 'light': ['mqtt'],
+ 'sensor': ['mqtt'],
+ 'switch': ['mqtt'],
+ 'climate': ['mqtt'],
+ 'alarm_control_panel': ['mqtt'],
+}
+
ALREADY_DISCOVERED = 'mqtt_discovered_components'
+DATA_CONFIG_ENTRY_LOCK = 'mqtt_config_entry_lock'
+CONFIG_ENTRY_IS_SETUP = 'mqtt_config_entry_is_setup'
MQTT_DISCOVERY_UPDATED = 'mqtt_discovery_updated_{}'
+MQTT_DISCOVERY_NEW = 'mqtt_discovery_new_{}_{}'
-async def async_start(hass, discovery_topic, hass_config):
+async def async_start(hass: HomeAssistantType, discovery_topic, hass_config,
+ config_entry=None) -> bool:
"""Initialize of MQTT Discovery."""
async def async_device_message_received(topic, payload, qos):
"""Process the received message."""
@@ -98,8 +115,23 @@ async def async_start(hass, discovery_topic, hass_config):
_LOGGER.info("Found new component: %s %s", component, discovery_id)
- await async_load_platform(
- hass, component, platform, payload, hass_config)
+ if platform not in CONFIG_ENTRY_PLATFORMS.get(component, []):
+ await async_load_platform(
+ hass, component, platform, payload, hass_config)
+ return
+
+ config_entries_key = '{}.{}'.format(component, platform)
+ async with hass.data[DATA_CONFIG_ENTRY_LOCK]:
+ if config_entries_key not in hass.data[CONFIG_ENTRY_IS_SETUP]:
+ await hass.config_entries.async_forward_entry_setup(
+ config_entry, component)
+ hass.data[CONFIG_ENTRY_IS_SETUP].add(config_entries_key)
+
+ async_dispatcher_send(hass, MQTT_DISCOVERY_NEW.format(
+ component, platform), payload)
+
+ hass.data[DATA_CONFIG_ENTRY_LOCK] = asyncio.Lock()
+ hass.data[CONFIG_ENTRY_IS_SETUP] = set()
await mqtt.async_subscribe(
hass, discovery_topic + '/#', async_device_message_received, 0)
diff --git a/homeassistant/components/mqtt/strings.json b/homeassistant/components/mqtt/strings.json
index 0a2cb255cc4..40a68195f26 100644
--- a/homeassistant/components/mqtt/strings.json
+++ b/homeassistant/components/mqtt/strings.json
@@ -12,6 +12,13 @@
"password": "Password",
"discovery": "Enable discovery"
}
+ },
+ "hassio_confirm": {
+ "title": "MQTT Broker via Hass.io add-on",
+ "description": "Do you want to configure Home Assistant to connect to the MQTT broker provided by the hass.io add-on {addon}?",
+ "data": {
+ "discovery": "Enable discovery"
+ }
}
},
"abort": {
diff --git a/homeassistant/components/mqtt_statestream.py b/homeassistant/components/mqtt_statestream.py
index 592e31cbff1..3a0e5d39ff0 100644
--- a/homeassistant/components/mqtt_statestream.py
+++ b/homeassistant/components/mqtt_statestream.py
@@ -4,7 +4,6 @@ Publish simple item state changes via MQTT.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/mqtt_statestream/
"""
-import asyncio
import json
import voluptuous as vol
@@ -43,8 +42,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the MQTT state feed."""
conf = config.get(DOMAIN, {})
base_topic = conf.get(CONF_BASE_TOPIC)
diff --git a/homeassistant/components/mysensors/__init__.py b/homeassistant/components/mysensors/__init__.py
index 725494cd197..4f00247495a 100644
--- a/homeassistant/components/mysensors/__init__.py
+++ b/homeassistant/components/mysensors/__init__.py
@@ -111,7 +111,7 @@ async def async_setup(hass, config):
hass.data[MYSENSORS_GATEWAYS] = gateways
- hass.async_add_job(finish_setup(hass, gateways))
+ hass.async_create_task(finish_setup(hass, gateways))
return True
diff --git a/homeassistant/components/mysensors/gateway.py b/homeassistant/components/mysensors/gateway.py
index 88725e67940..558e944f727 100644
--- a/homeassistant/components/mysensors/gateway.py
+++ b/homeassistant/components/mysensors/gateway.py
@@ -106,7 +106,7 @@ async def _get_gateway(hass, config, gateway_conf, persistence_file):
"""Call callback."""
sub_cb(*args)
- hass.async_add_job(
+ hass.async_create_task(
mqtt.async_subscribe(topic, internal_callback, qos))
gateway = mysensors.AsyncMQTTGateway(
@@ -192,7 +192,7 @@ async def _gw_start(hass, gateway):
@callback
def gw_stop(event):
"""Trigger to stop the gateway."""
- hass.async_add_job(gateway.stop())
+ hass.async_create_task(gateway.stop())
if not connect_task.done():
connect_task.cancel()
diff --git a/homeassistant/components/namecheapdns.py b/homeassistant/components/namecheapdns.py
index dcca8829535..32a5c318852 100644
--- a/homeassistant/components/namecheapdns.py
+++ b/homeassistant/components/namecheapdns.py
@@ -4,7 +4,6 @@ Integrate with namecheap DNS services.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/namecheapdns/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -32,8 +31,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize the namecheap DNS component."""
host = config[DOMAIN][CONF_HOST]
domain = config[DOMAIN][CONF_DOMAIN]
@@ -41,23 +39,21 @@ def async_setup(hass, config):
session = async_get_clientsession(hass)
- result = yield from _update_namecheapdns(session, host, domain, password)
+ result = await _update_namecheapdns(session, host, domain, password)
if not result:
return False
- @asyncio.coroutine
- def update_domain_interval(now):
+ async def update_domain_interval(now):
"""Update the namecheap DNS entry."""
- yield from _update_namecheapdns(session, host, domain, password)
+ await _update_namecheapdns(session, host, domain, password)
async_track_time_interval(hass, update_domain_interval, INTERVAL)
return result
-@asyncio.coroutine
-def _update_namecheapdns(session, host, domain, password):
+async def _update_namecheapdns(session, host, domain, password):
"""Update namecheap DNS entry."""
import xml.etree.ElementTree as ET
@@ -67,8 +63,8 @@ def _update_namecheapdns(session, host, domain, password):
'password': password,
}
- resp = yield from session.get(UPDATE_URL, params=params)
- xml_string = yield from resp.text()
+ resp = await session.get(UPDATE_URL, params=params)
+ xml_string = await resp.text()
root = ET.fromstring(xml_string)
err_count = root.find('ErrCount').text
diff --git a/homeassistant/components/nest/.translations/hu.json b/homeassistant/components/nest/.translations/hu.json
index abf8f79599f..142747a016f 100644
--- a/homeassistant/components/nest/.translations/hu.json
+++ b/homeassistant/components/nest/.translations/hu.json
@@ -1,13 +1,19 @@
{
"config": {
+ "abort": {
+ "authorize_url_timeout": "Id\u0151t\u00fall\u00e9p\u00e9s az \u00e9rv\u00e9nyes\u00edt\u00e9si url gener\u00e1l\u00e1sa sor\u00e1n."
+ },
"error": {
- "invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d"
+ "invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d",
+ "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n.",
+ "unknown": "Ismeretlen hiba t\u00f6rt\u00e9nt a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n"
},
"step": {
"init": {
"data": {
"flow_impl": "Szolg\u00e1ltat\u00f3"
- }
+ },
+ "title": "Hiteles\u00edt\u00e9si Szolg\u00e1ltat\u00f3"
},
"link": {
"data": {
diff --git a/homeassistant/components/nest/.translations/ru.json b/homeassistant/components/nest/.translations/ru.json
index 0f7b9b8dd71..6a73bd47203 100644
--- a/homeassistant/components/nest/.translations/ru.json
+++ b/homeassistant/components/nest/.translations/ru.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "already_setup": "\u0412\u044b \u043c\u043e\u0436\u0435\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0443 \u0443\u0447\u0435\u0442\u043d\u0443\u044e \u0437\u0430\u043f\u0438\u0441\u044c Nest.",
+ "already_setup": "\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.",
"authorize_url_fail": "\u041d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043d\u0430\u044f \u043e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u0438 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
"authorize_url_timeout": "\u0418\u0441\u0442\u0435\u043a\u043b\u043e \u0432\u0440\u0435\u043c\u044f \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438 \u0441\u0441\u044b\u043b\u043a\u0438 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438.",
"no_flows": "\u0412\u0430\u043c \u043d\u0443\u0436\u043d\u043e \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Nest \u043f\u0435\u0440\u0435\u0434 \u0442\u0435\u043c, \u043a\u0430\u043a \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e. [\u041f\u043e\u0436\u0430\u043b\u0443\u0439\u0441\u0442\u0430, \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 \u0438\u043d\u0441\u0442\u0440\u0443\u043a\u0446\u0438\u044f\u043c\u0438](https://www.home-assistant.io/components/nest/)."
diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py
index f609c774b12..c66abf1a8bd 100644
--- a/homeassistant/components/nest/__init__.py
+++ b/homeassistant/components/nest/__init__.py
@@ -105,7 +105,7 @@ async def async_setup(hass, config):
filename = config.get(CONF_FILENAME, NEST_CONFIG_FILE)
access_token_cache_file = hass.config.path(filename)
- hass.async_add_job(hass.config_entries.flow.async_init(
+ hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
data={
'nest_conf_path': access_token_cache_file,
diff --git a/homeassistant/components/netatmo.py b/homeassistant/components/netatmo.py
index 59b0a64f6e9..d8924c6c301 100644
--- a/homeassistant/components/netatmo.py
+++ b/homeassistant/components/netatmo.py
@@ -16,7 +16,7 @@ from homeassistant.helpers import discovery
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyatmo==1.1.1']
+REQUIREMENTS = ['pyatmo==1.2']
_LOGGER = logging.getLogger(__name__)
diff --git a/homeassistant/components/no_ip.py b/homeassistant/components/no_ip.py
index 6051fa85f55..beb11ed738f 100644
--- a/homeassistant/components/no_ip.py
+++ b/homeassistant/components/no_ip.py
@@ -53,8 +53,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize the NO-IP component."""
domain = config[DOMAIN].get(CONF_DOMAIN)
user = config[DOMAIN].get(CONF_USERNAME)
@@ -65,16 +64,15 @@ def async_setup(hass, config):
session = hass.helpers.aiohttp_client.async_get_clientsession()
- result = yield from _update_no_ip(
+ result = await _update_no_ip(
hass, session, domain, auth_str, timeout)
if not result:
return False
- @asyncio.coroutine
- def update_domain_interval(now):
+ async def update_domain_interval(now):
"""Update the NO-IP entry."""
- yield from _update_no_ip(hass, session, domain, auth_str, timeout)
+ await _update_no_ip(hass, session, domain, auth_str, timeout)
hass.helpers.event.async_track_time_interval(
update_domain_interval, INTERVAL)
@@ -82,8 +80,7 @@ def async_setup(hass, config):
return True
-@asyncio.coroutine
-def _update_no_ip(hass, session, domain, auth_str, timeout):
+async def _update_no_ip(hass, session, domain, auth_str, timeout):
"""Update NO-IP."""
url = UPDATE_URL
@@ -98,8 +95,8 @@ def _update_no_ip(hass, session, domain, auth_str, timeout):
try:
with async_timeout.timeout(timeout, loop=hass.loop):
- resp = yield from session.get(url, params=params, headers=headers)
- body = yield from resp.text()
+ resp = await session.get(url, params=params, headers=headers)
+ body = await resp.text()
if body.startswith('good') or body.startswith('nochg'):
return True
diff --git a/homeassistant/components/notify/__init__.py b/homeassistant/components/notify/__init__.py
index 4de35d3f850..f0320617e19 100644
--- a/homeassistant/components/notify/__init__.py
+++ b/homeassistant/components/notify/__init__.py
@@ -12,7 +12,6 @@ import voluptuous as vol
from homeassistant.setup import async_prepare_setup_platform
from homeassistant.exceptions import HomeAssistantError
-from homeassistant.loader import bind_hass
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_NAME, CONF_PLATFORM
from homeassistant.helpers import config_per_platform, discovery
@@ -50,34 +49,16 @@ NOTIFY_SERVICE_SCHEMA = vol.Schema({
})
-@bind_hass
-def send_message(hass, message, title=None, data=None):
- """Send a notification message."""
- info = {
- ATTR_MESSAGE: message
- }
-
- if title is not None:
- info[ATTR_TITLE] = title
-
- if data is not None:
- info[ATTR_DATA] = data
-
- hass.services.call(DOMAIN, SERVICE_NOTIFY, info)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the notify services."""
targets = {}
- @asyncio.coroutine
- def async_setup_platform(p_type, p_config=None, discovery_info=None):
+ async def async_setup_platform(p_type, p_config=None, discovery_info=None):
"""Set up a notify platform."""
if p_config is None:
p_config = {}
- platform = yield from async_prepare_setup_platform(
+ platform = await async_prepare_setup_platform(
hass, config, DOMAIN, p_type)
if platform is None:
@@ -88,10 +69,10 @@ def async_setup(hass, config):
notify_service = None
try:
if hasattr(platform, 'async_get_service'):
- notify_service = yield from \
+ notify_service = await \
platform.async_get_service(hass, p_config, discovery_info)
elif hasattr(platform, 'get_service'):
- notify_service = yield from hass.async_add_job(
+ notify_service = await hass.async_add_job(
platform.get_service, hass, p_config, discovery_info)
else:
raise HomeAssistantError("Invalid notify platform.")
@@ -114,8 +95,7 @@ def async_setup(hass, config):
if discovery_info is None:
discovery_info = {}
- @asyncio.coroutine
- def async_notify_message(service):
+ async def async_notify_message(service):
"""Handle sending notification message service calls."""
kwargs = {}
message = service.data[ATTR_MESSAGE]
@@ -134,7 +114,7 @@ def async_setup(hass, config):
kwargs[ATTR_MESSAGE] = message.async_render()
kwargs[ATTR_DATA] = service.data.get(ATTR_DATA)
- yield from notify_service.async_send_message(**kwargs)
+ await notify_service.async_send_message(**kwargs)
if hasattr(notify_service, 'targets'):
platform_name = (
@@ -164,12 +144,11 @@ def async_setup(hass, config):
in config_per_platform(config, DOMAIN)]
if setup_tasks:
- yield from asyncio.wait(setup_tasks, loop=hass.loop)
+ await asyncio.wait(setup_tasks, loop=hass.loop)
- @asyncio.coroutine
- def async_platform_discovered(platform, info):
+ async def async_platform_discovered(platform, info):
"""Handle for discovered platform."""
- yield from async_setup_platform(platform, discovery_info=info)
+ await async_setup_platform(platform, discovery_info=info)
discovery.async_listen_platform(hass, DOMAIN, async_platform_discovered)
diff --git a/homeassistant/components/notify/discord.py b/homeassistant/components/notify/discord.py
index 0cf4bced360..8bd4e27155d 100644
--- a/homeassistant/components/notify/discord.py
+++ b/homeassistant/components/notify/discord.py
@@ -4,7 +4,6 @@ Discord platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.discord/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -39,8 +38,7 @@ class DiscordNotificationService(BaseNotificationService):
self.token = token
self.hass = hass
- @asyncio.coroutine
- def async_send_message(self, message, **kwargs):
+ async def async_send_message(self, message, **kwargs):
"""Login to Discord, send message to channel(s) and log out."""
import discord
discord_bot = discord.Client(loop=self.hass.loop)
@@ -51,8 +49,7 @@ class DiscordNotificationService(BaseNotificationService):
# pylint: disable=unused-variable
@discord_bot.event
- @asyncio.coroutine
- def on_ready():
+ async def on_ready():
"""Send the messages when the bot is ready."""
try:
data = kwargs.get(ATTR_DATA)
@@ -60,14 +57,14 @@ class DiscordNotificationService(BaseNotificationService):
images = data.get(ATTR_IMAGES)
for channelid in kwargs[ATTR_TARGET]:
channel = discord.Object(id=channelid)
- yield from discord_bot.send_message(channel, message)
+ await discord_bot.send_message(channel, message)
if images:
for anum, f_name in enumerate(images):
- yield from discord_bot.send_file(channel, f_name)
+ await discord_bot.send_file(channel, f_name)
except (discord.errors.HTTPException,
discord.errors.NotFound) as error:
_LOGGER.warning("Communication error: %s", error)
- yield from discord_bot.logout()
- yield from discord_bot.close()
+ await discord_bot.logout()
+ await discord_bot.close()
- yield from discord_bot.start(self.token)
+ await discord_bot.start(self.token)
diff --git a/homeassistant/components/notify/group.py b/homeassistant/components/notify/group.py
index 94856c730b1..5d25c2d815e 100644
--- a/homeassistant/components/notify/group.py
+++ b/homeassistant/components/notify/group.py
@@ -41,8 +41,7 @@ def update(input_dict, update_source):
return input_dict
-@asyncio.coroutine
-def async_get_service(hass, config, discovery_info=None):
+async def async_get_service(hass, config, discovery_info=None):
"""Get the Group notification service."""
return GroupNotifyPlatform(hass, config.get(CONF_SERVICES))
@@ -55,8 +54,7 @@ class GroupNotifyPlatform(BaseNotificationService):
self.hass = hass
self.entities = entities
- @asyncio.coroutine
- def async_send_message(self, message="", **kwargs):
+ async def async_send_message(self, message="", **kwargs):
"""Send message to all entities in the group."""
payload = {ATTR_MESSAGE: message}
payload.update({key: val for key, val in kwargs.items() if val})
@@ -70,4 +68,4 @@ class GroupNotifyPlatform(BaseNotificationService):
DOMAIN, entity.get(ATTR_SERVICE), sending_payload))
if tasks:
- yield from asyncio.wait(tasks, loop=self.hass.loop)
+ await asyncio.wait(tasks, loop=self.hass.loop)
diff --git a/homeassistant/components/notify/kodi.py b/homeassistant/components/notify/kodi.py
index 3eb492f7fa6..74bfe61d3f2 100644
--- a/homeassistant/components/notify/kodi.py
+++ b/homeassistant/components/notify/kodi.py
@@ -4,7 +4,6 @@ Kodi notification service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.kodi/
"""
-import asyncio
import logging
import aiohttp
@@ -38,8 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
ATTR_DISPLAYTIME = 'displaytime'
-@asyncio.coroutine
-def async_get_service(hass, config, discovery_info=None):
+async def async_get_service(hass, config, discovery_info=None):
"""Return the notify service."""
url = '{}:{}'.format(config.get(CONF_HOST), config.get(CONF_PORT))
@@ -86,8 +84,7 @@ class KodiNotificationService(BaseNotificationService):
self._server = jsonrpc_async.Server(self._url, **kwargs)
- @asyncio.coroutine
- def async_send_message(self, message="", **kwargs):
+ async def async_send_message(self, message="", **kwargs):
"""Send a message to Kodi."""
import jsonrpc_async
try:
@@ -96,7 +93,7 @@ class KodiNotificationService(BaseNotificationService):
displaytime = data.get(ATTR_DISPLAYTIME, 10000)
icon = data.get(ATTR_ICON, "info")
title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
- yield from self._server.GUI.ShowNotification(
+ await self._server.GUI.ShowNotification(
title, message, icon, displaytime)
except jsonrpc_async.TransportError:
diff --git a/homeassistant/components/notify/prowl.py b/homeassistant/components/notify/prowl.py
index 3928fa81167..f0741766a70 100644
--- a/homeassistant/components/notify/prowl.py
+++ b/homeassistant/components/notify/prowl.py
@@ -25,8 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_get_service(hass, config, discovery_info=None):
+async def async_get_service(hass, config, discovery_info=None):
"""Get the Prowl notification service."""
return ProwlNotificationService(hass, config[CONF_API_KEY])
@@ -39,8 +38,7 @@ class ProwlNotificationService(BaseNotificationService):
self._hass = hass
self._api_key = api_key
- @asyncio.coroutine
- def async_send_message(self, message, **kwargs):
+ async def async_send_message(self, message, **kwargs):
"""Send the message to the user."""
response = None
session = None
@@ -59,8 +57,8 @@ class ProwlNotificationService(BaseNotificationService):
try:
with async_timeout.timeout(10, loop=self._hass.loop):
- response = yield from session.post(url, data=payload)
- result = yield from response.text()
+ response = await session.post(url, data=payload)
+ result = await response.text()
if response.status != 200 or 'error' in result:
_LOGGER.error("Prowl service returned http "
diff --git a/homeassistant/components/notify/telegram.py b/homeassistant/components/notify/telegram.py
index b012506acd9..1dff82fa2cd 100644
--- a/homeassistant/components/notify/telegram.py
+++ b/homeassistant/components/notify/telegram.py
@@ -47,7 +47,7 @@ class TelegramNotificationService(BaseNotificationService):
def send_message(self, message="", **kwargs):
"""Send a message to a user."""
- service_data = dict(target=kwargs.get(ATTR_TARGET, self._chat_id))
+ service_data = {ATTR_TARGET: kwargs.get(ATTR_TARGET, self._chat_id)}
if ATTR_TITLE in kwargs:
service_data.update({ATTR_TITLE: kwargs.get(ATTR_TITLE)})
if message:
diff --git a/homeassistant/components/notify/tibber.py b/homeassistant/components/notify/tibber.py
new file mode 100644
index 00000000000..ddbcb3f6c65
--- /dev/null
+++ b/homeassistant/components/notify/tibber.py
@@ -0,0 +1,37 @@
+"""
+Tibber platform for notify component.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/notify.tibber/
+"""
+import asyncio
+import logging
+
+from homeassistant.components.notify import (
+ ATTR_TITLE, ATTR_TITLE_DEFAULT, BaseNotificationService)
+from homeassistant.components.tibber import DOMAIN as TIBBER_DOMAIN
+
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_get_service(hass, config, discovery_info=None):
+ """Get the Tibber notification service."""
+ tibber_connection = hass.data[TIBBER_DOMAIN]
+ return TibberNotificationService(tibber_connection.send_notification)
+
+
+class TibberNotificationService(BaseNotificationService):
+ """Implement the notification service for Tibber."""
+
+ def __init__(self, notify):
+ """Initialize the service."""
+ self._notify = notify
+
+ async def async_send_message(self, message=None, **kwargs):
+ """Send a message to Tibber devices."""
+ title = kwargs.get(ATTR_TITLE, ATTR_TITLE_DEFAULT)
+ try:
+ await self._notify(title=title, message=message)
+ except asyncio.TimeoutError:
+ _LOGGER.error("Timeout sending message with Tibber")
diff --git a/homeassistant/components/notify/yessssms.py b/homeassistant/components/notify/yessssms.py
index 37a6a90a62e..e16e384ca25 100644
--- a/homeassistant/components/notify/yessssms.py
+++ b/homeassistant/components/notify/yessssms.py
@@ -13,7 +13,8 @@ from homeassistant.components.notify import (
from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_RECIPIENT
import homeassistant.helpers.config_validation as cv
-REQUIREMENTS = ['YesssSMS==0.1.1b3']
+
+REQUIREMENTS = ['YesssSMS==0.2.3']
_LOGGER = logging.getLogger(__name__)
@@ -38,14 +39,38 @@ class YesssSMSNotificationService(BaseNotificationService):
from YesssSMS import YesssSMS
self.yesss = YesssSMS(username, password)
self._recipient = recipient
+ _LOGGER.debug(
+ "initialized; library version: %s", self.yesss.version())
def send_message(self, message="", **kwargs):
"""Send a SMS message via Yesss.at's website."""
+ if self.yesss.account_is_suspended():
+ # only retry to login after HASS was restarted with (hopefully)
+ # new login data.
+ _LOGGER.error(
+ "Account is suspended, cannot send SMS. "
+ "Check your login data and restart Home Assistant")
+ return
try:
self.yesss.send(self._recipient, message)
- except ValueError as ex:
- if str(ex).startswith("YesssSMS:"):
- _LOGGER.error(str(ex))
- except RuntimeError as ex:
- if str(ex).startswith("YesssSMS:"):
- _LOGGER.error(str(ex))
+ except self.yesss.NoRecipientError as ex:
+ _LOGGER.error(
+ "You need to provide a recipient for SMS notification: %s",
+ ex)
+ except self.yesss.EmptyMessageError as ex:
+ _LOGGER.error(
+ "Cannot send empty SMS message: %s", ex)
+ except self.yesss.SMSSendingError as ex:
+ _LOGGER.error(str(ex), exc_info=ex)
+ except ConnectionError as ex:
+ _LOGGER.error(
+ "YesssSMS: unable to connect to yesss.at server.",
+ exc_info=ex)
+ except self.yesss.AccountSuspendedError as ex:
+ _LOGGER.error(
+ "Wrong login credentials!! Verify correct credentials and "
+ "restart Home Assistant: %s", ex)
+ except self.yesss.LoginError as ex:
+ _LOGGER.error("Wrong login credentials: %s", ex)
+ else:
+ _LOGGER.info("SMS sent")
diff --git a/homeassistant/components/onboarding/__init__.py b/homeassistant/components/onboarding/__init__.py
index 52d18b9a870..376575e3440 100644
--- a/homeassistant/components/onboarding/__init__.py
+++ b/homeassistant/components/onboarding/__init__.py
@@ -23,7 +23,8 @@ def async_is_onboarded(hass):
async def async_setup(hass, config):
"""Set up the onboarding component."""
- store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
+ store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
+ private=True)
data = await store.async_load()
if data is None:
diff --git a/homeassistant/components/openuv/__init__.py b/homeassistant/components/openuv/__init__.py
index 35ab16b4d1f..8485e1e3201 100644
--- a/homeassistant/components/openuv/__init__.py
+++ b/homeassistant/components/openuv/__init__.py
@@ -212,8 +212,15 @@ class OpenUV:
async def async_update(self):
"""Update sensor/binary sensor data."""
if TYPE_PROTECTION_WINDOW in self.binary_sensor_conditions:
- data = await self.client.uv_protection_window()
- self.data[DATA_PROTECTION_WINDOW] = data
+ resp = await self.client.uv_protection_window()
+ data = resp['result']
+
+ if data.get('from_time') and data.get('to_time'):
+ self.data[DATA_PROTECTION_WINDOW] = data
+ else:
+ _LOGGER.error(
+ 'No valid protection window data for this location')
+ self.data[DATA_PROTECTION_WINDOW] = {}
if any(c in self.sensor_conditions for c in SENSORS):
data = await self.client.uv_index()
diff --git a/homeassistant/components/persistent_notification/__init__.py b/homeassistant/components/persistent_notification/__init__.py
index 6b8fd68bc26..d38501b9b07 100644
--- a/homeassistant/components/persistent_notification/__init__.py
+++ b/homeassistant/components/persistent_notification/__init__.py
@@ -4,7 +4,6 @@ A component which is collecting configuration errors.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/persistent_notification/
"""
-import asyncio
import logging
from collections import OrderedDict
from typing import Awaitable
@@ -18,7 +17,9 @@ from homeassistant.loader import bind_hass
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import async_generate_entity_id
from homeassistant.util import slugify
+import homeassistant.util.dt as dt_util
+ATTR_CREATED_AT = 'created_at'
ATTR_MESSAGE = 'message'
ATTR_NOTIFICATION_ID = 'notification_id'
ATTR_TITLE = 'title'
@@ -96,11 +97,11 @@ def async_dismiss(hass: HomeAssistant, notification_id: str) -> None:
"""Remove a notification."""
data = {ATTR_NOTIFICATION_ID: notification_id}
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_DISMISS, data))
+ hass.async_create_task(
+ hass.services.async_call(DOMAIN, SERVICE_DISMISS, data))
-@asyncio.coroutine
-def async_setup(hass: HomeAssistant, config: dict) -> Awaitable[bool]:
+async def async_setup(hass: HomeAssistant, config: dict) -> Awaitable[bool]:
"""Set up the persistent notification component."""
persistent_notifications = OrderedDict()
hass.data[DOMAIN] = {'notifications': persistent_notifications}
@@ -148,6 +149,7 @@ def async_setup(hass: HomeAssistant, config: dict) -> Awaitable[bool]:
ATTR_NOTIFICATION_ID: notification_id,
ATTR_STATUS: STATUS_UNREAD,
ATTR_TITLE: title,
+ ATTR_CREATED_AT: dt_util.utcnow(),
}
hass.bus.async_fire(EVENT_PERSISTENT_NOTIFICATIONS_UPDATED)
@@ -201,11 +203,12 @@ def async_setup(hass: HomeAssistant, config: dict) -> Awaitable[bool]:
def websocket_get_notifications(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
"""Return a list of persistent_notifications."""
- connection.to_write.put_nowait(
+ connection.send_message(
websocket_api.result_message(msg['id'], [
{
- key: data[key] for key in (ATTR_NOTIFICATION_ID, ATTR_MESSAGE,
- ATTR_STATUS, ATTR_TITLE)
+ key: data[key] for key in (ATTR_NOTIFICATION_ID,
+ ATTR_MESSAGE, ATTR_STATUS,
+ ATTR_TITLE, ATTR_CREATED_AT)
}
for data in hass.data[DOMAIN]['notifications'].values()
])
diff --git a/homeassistant/components/plant.py b/homeassistant/components/plant.py
index 84dc8402742..9659fd4f7e1 100644
--- a/homeassistant/components/plant.py
+++ b/homeassistant/components/plant.py
@@ -4,7 +4,6 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/plant/
"""
import logging
-import asyncio
from datetime import datetime, timedelta
from collections import deque
import voluptuous as vol
@@ -97,8 +96,7 @@ CONFIG_SCHEMA = vol.Schema({
ENABLE_LOAD_HISTORY = False
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Plant component."""
component = EntityComponent(_LOGGER, DOMAIN, hass,
group_name=GROUP_NAME_ALL_PLANTS)
@@ -112,7 +110,7 @@ def async_setup(hass, config):
async_track_state_change(hass, sensor_entity_ids, entity.state_changed)
entities.append(entity)
- yield from component.async_add_entities(entities)
+ await component.async_add_entities(entities)
return True
@@ -246,15 +244,13 @@ class Plant(Entity):
return '{} high'.format(sensor_name)
return None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""After being added to hass, load from history."""
if ENABLE_LOAD_HISTORY and 'recorder' in self.hass.config.components:
# only use the database if it's configured
self.hass.async_add_job(self._load_history_from_db)
- @asyncio.coroutine
- def _load_history_from_db(self):
+ async def _load_history_from_db(self):
"""Load the history of the brightness values from the database.
This only needs to be done once during startup.
diff --git a/homeassistant/components/prometheus.py b/homeassistant/components/prometheus.py
index 5fa768b6983..ee4b88d4d9b 100644
--- a/homeassistant/components/prometheus.py
+++ b/homeassistant/components/prometheus.py
@@ -4,7 +4,6 @@ Support for Prometheus metrics export.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/prometheus/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -265,8 +264,7 @@ class PrometheusView(HomeAssistantView):
"""Initialize Prometheus view."""
self.prometheus_client = prometheus_client
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Handle request for Prometheus metrics."""
_LOGGER.debug("Received Prometheus metrics request")
diff --git a/homeassistant/components/rachio.py b/homeassistant/components/rachio.py
index cd80b7bec9b..27827da0182 100644
--- a/homeassistant/components/rachio.py
+++ b/homeassistant/components/rachio.py
@@ -13,7 +13,7 @@ import voluptuous as vol
from homeassistant.auth.util import generate_secret
from homeassistant.components.http import HomeAssistantView
from homeassistant.const import CONF_API_KEY, EVENT_HOMEASSISTANT_STOP, URL_API
-import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers import discovery, config_validation as cv
from homeassistant.helpers.dispatcher import async_dispatcher_send
REQUIREMENTS = ['rachiopy==0.1.3']
@@ -22,11 +22,19 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = 'rachio'
+SUPPORTED_DOMAINS = ['switch', 'binary_sensor']
+
+# Manual run length
+CONF_MANUAL_RUN_MINS = 'manual_run_mins'
+DEFAULT_MANUAL_RUN_MINS = 10
CONF_CUSTOM_URL = 'hass_url_override'
+
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_CUSTOM_URL): cv.string,
+ vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS):
+ cv.positive_int,
})
}, extra=vol.ALLOW_EXTRA)
@@ -112,7 +120,7 @@ def setup(hass, config) -> bool:
# Get the API user
try:
- person = RachioPerson(hass, rachio)
+ person = RachioPerson(hass, rachio, config[DOMAIN])
except AssertionError as error:
_LOGGER.error("Could not reach the Rachio API: %s", error)
return False
@@ -126,17 +134,23 @@ def setup(hass, config) -> bool:
# Enable component
hass.data[DOMAIN] = person
+
+ # Load platforms
+ for component in SUPPORTED_DOMAINS:
+ discovery.load_platform(hass, component, DOMAIN, {}, config)
+
return True
class RachioPerson:
"""Represent a Rachio user."""
- def __init__(self, hass, rachio):
+ def __init__(self, hass, rachio, config):
"""Create an object from the provided API instance."""
# Use API token to get user ID
self._hass = hass
self.rachio = rachio
+ self.config = config
response = rachio.person.getInfo()
assert int(response[0][KEY_STATUS]) == 200, "API key error"
diff --git a/homeassistant/components/raincloud.py b/homeassistant/components/raincloud.py
index 53cd8e79d7e..47f6176d5f8 100644
--- a/homeassistant/components/raincloud.py
+++ b/homeassistant/components/raincloud.py
@@ -4,7 +4,6 @@ Support for Melnor RainCloud sprinkler water timer.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/raincloud/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -148,8 +147,7 @@ class RainCloudEntity(Entity):
"""Return the name of the sensor."""
return self._name
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_RAINCLOUD, self._update_callback)
diff --git a/homeassistant/components/remote/__init__.py b/homeassistant/components/remote/__init__.py
index 11ecb20f7aa..4fc491e57e8 100644
--- a/homeassistant/components/remote/__init__.py
+++ b/homeassistant/components/remote/__init__.py
@@ -4,7 +4,6 @@ Component to interface with universal remote control devices.
For more details about this component, please refer to the documentation
at https://home-assistant.io/components/remote/
"""
-import asyncio
from datetime import timedelta
import functools as ft
import logging
@@ -70,69 +69,11 @@ def is_on(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, activity=None, entity_id=None):
- """Turn all or specified remote on."""
- data = {
- key: value for key, value in [
- (ATTR_ACTIVITY, activity),
- (ATTR_ENTITY_ID, entity_id),
- ] if value is not None}
- hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
-
-
-@bind_hass
-def turn_off(hass, activity=None, entity_id=None):
- """Turn all or specified remote off."""
- data = {}
- if activity:
- data[ATTR_ACTIVITY] = activity
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
-
-
-@bind_hass
-def toggle(hass, activity=None, entity_id=None):
- """Toggle all or specified remote."""
- data = {}
- if activity:
- data[ATTR_ACTIVITY] = activity
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
-
-
-@bind_hass
-def send_command(hass, command, entity_id=None, device=None,
- num_repeats=None, delay_secs=None):
- """Send a command to a device."""
- data = {ATTR_COMMAND: command}
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- if device:
- data[ATTR_DEVICE] = device
-
- if num_repeats:
- data[ATTR_NUM_REPEATS] = num_repeats
-
- if delay_secs:
- data[ATTR_DELAY_SECS] = delay_secs
-
- hass.services.call(DOMAIN, SERVICE_SEND_COMMAND, data)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Track states and offer events for remotes."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_REMOTES)
- yield from component.async_setup(config)
+ await component.async_setup(config)
component.async_register_entity_service(
SERVICE_TURN_OFF, REMOTE_SERVICE_ACTIVITY_SCHEMA,
diff --git a/homeassistant/components/remote/apple_tv.py b/homeassistant/components/remote/apple_tv.py
index d8eac11372c..72696143bfe 100644
--- a/homeassistant/components/remote/apple_tv.py
+++ b/homeassistant/components/remote/apple_tv.py
@@ -4,7 +4,6 @@ Remote control support for Apple TV.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/remote.apple_tv/
"""
-import asyncio
from homeassistant.components.apple_tv import (
ATTR_ATV, ATTR_POWER, DATA_APPLE_TV)
@@ -15,9 +14,8 @@ from homeassistant.const import (CONF_NAME, CONF_HOST)
DEPENDENCIES = ['apple_tv']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Apple TV remote platform."""
if not discovery_info:
return
@@ -59,16 +57,14 @@ class AppleTVRemote(remote.RemoteDevice):
"""No polling needed for Apple TV."""
return False
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on.
This method is a coroutine.
"""
self._power.set_power_on(True)
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the device off.
This method is a coroutine.
@@ -81,12 +77,11 @@ class AppleTVRemote(remote.RemoteDevice):
This method must be run in the event loop and returns a coroutine.
"""
# Send commands in specified order but schedule only one coroutine
- @asyncio.coroutine
- def _send_commands():
+ async def _send_commands():
for single_command in command:
if not hasattr(self._atv.remote_control, single_command):
continue
- yield from getattr(self._atv.remote_control, single_command)()
+ await getattr(self._atv.remote_control, single_command)()
return _send_commands()
diff --git a/homeassistant/components/remote/harmony.py b/homeassistant/components/remote/harmony.py
index 5b7d0d1df78..14008d49760 100644
--- a/homeassistant/components/remote/harmony.py
+++ b/homeassistant/components/remote/harmony.py
@@ -4,7 +4,6 @@ Support for Harmony Hub devices.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/remote.harmony/
"""
-import asyncio
import logging
import time
@@ -152,8 +151,7 @@ class HarmonyRemote(remote.RemoteDevice):
pyharmony.ha_write_config_file(self._config, self._config_path)
self._delay_secs = delay_secs
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Complete the initialization."""
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP,
diff --git a/homeassistant/components/remote/xiaomi_miio.py b/homeassistant/components/remote/xiaomi_miio.py
index 723f575ba34..7cd588683de 100644
--- a/homeassistant/components/remote/xiaomi_miio.py
+++ b/homeassistant/components/remote/xiaomi_miio.py
@@ -61,9 +61,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Xiaomi IR Remote (Chuangmi IR) platform."""
from miio import ChuangmiIr, DeviceException
@@ -109,8 +108,7 @@ def async_setup_platform(hass, config, async_add_entities,
async_add_entities([xiaomi_miio_remote])
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Handle a learn command."""
if service.service != SERVICE_LEARN:
_LOGGER.error("We should not handle service: %s", service.service)
@@ -130,14 +128,14 @@ def async_setup_platform(hass, config, async_add_entities,
slot = service.data.get(CONF_SLOT, entity.slot)
- yield from hass.async_add_job(device.learn, slot)
+ await hass.async_add_job(device.learn, slot)
timeout = service.data.get(CONF_TIMEOUT, entity.timeout)
_LOGGER.info("Press the key you want Home Assistant to learn")
start_time = utcnow()
while (utcnow() - start_time) < timedelta(seconds=timeout):
- message = yield from hass.async_add_job(
+ message = await hass.async_add_job(
device.read, slot)
_LOGGER.debug("Message received from device: '%s'", message)
@@ -150,9 +148,9 @@ def async_setup_platform(hass, config, async_add_entities,
if ('error' in message and
message['error']['message'] == "learn timeout"):
- yield from hass.async_add_job(device.learn, slot)
+ await hass.async_add_job(device.learn, slot)
- yield from asyncio.sleep(1, loop=hass.loop)
+ await asyncio.sleep(1, loop=hass.loop)
_LOGGER.error("Timeout. No infrared command captured")
hass.components.persistent_notification.async_create(
@@ -230,14 +228,12 @@ class XiaomiMiioRemote(RemoteDevice):
return {'hidden': 'true'}
return
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on."""
_LOGGER.error("Device does not support turn_on, "
"please use 'remote.send_command' to send commands.")
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the device off."""
_LOGGER.error("Device does not support turn_off, "
"please use 'remote.send_command' to send commands.")
diff --git a/homeassistant/components/rest_command.py b/homeassistant/components/rest_command.py
index 4632315b757..3f9b258634d 100644
--- a/homeassistant/components/rest_command.py
+++ b/homeassistant/components/rest_command.py
@@ -53,8 +53,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the REST command component."""
websession = async_get_clientsession(hass)
@@ -87,8 +86,7 @@ def async_setup(hass, config):
headers = {}
headers[hdrs.CONTENT_TYPE] = content_type
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Execute a shell command service."""
payload = None
if template_payload:
@@ -98,7 +96,7 @@ def async_setup(hass, config):
try:
with async_timeout.timeout(timeout, loop=hass.loop):
- request = yield from getattr(websession, method)(
+ request = await getattr(websession, method)(
template_url.async_render(variables=service.data),
data=payload,
auth=auth,
diff --git a/homeassistant/components/rflink.py b/homeassistant/components/rflink.py
index b8af971b3ff..a8aeca273d6 100644
--- a/homeassistant/components/rflink.py
+++ b/homeassistant/components/rflink.py
@@ -105,8 +105,7 @@ def identify_event_type(event):
return 'unknown'
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Rflink component."""
from rflink.protocol import create_rflink_connection
import serial
@@ -125,11 +124,10 @@ def async_setup(hass, config):
# Allow platform to specify function to register new unknown devices
hass.data[DATA_DEVICE_REGISTER] = {}
- @asyncio.coroutine
- def async_send_command(call):
+ async def async_send_command(call):
"""Send Rflink command."""
_LOGGER.debug('Rflink command for %s', str(call.data))
- if not (yield from RflinkCommand.send_command(
+ if not (await RflinkCommand.send_command(
call.data.get(CONF_DEVICE_ID),
call.data.get(CONF_COMMAND))):
_LOGGER.error('Failed Rflink command for %s', str(call.data))
@@ -196,8 +194,7 @@ def async_setup(hass, config):
_LOGGER.warning('disconnected from Rflink, reconnecting')
hass.async_add_job(connect)
- @asyncio.coroutine
- def connect():
+ async def connect():
"""Set up connection and hook it into HA for reconnect/shutdown."""
_LOGGER.info('Initiating Rflink connection')
@@ -217,7 +214,7 @@ def async_setup(hass, config):
try:
with async_timeout.timeout(CONNECTION_TIMEOUT,
loop=hass.loop):
- transport, protocol = yield from connection
+ transport, protocol = await connection
except (serial.serialutil.SerialException, ConnectionRefusedError,
TimeoutError, OSError, asyncio.TimeoutError) as exc:
@@ -330,8 +327,7 @@ class RflinkDevice(Entity):
self._available = availability
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register update callback."""
async_dispatcher_connect(self.hass, SIGNAL_AVAILABILITY,
self.set_availability)
@@ -367,13 +363,11 @@ class RflinkCommand(RflinkDevice):
return bool(cls._protocol)
@classmethod
- @asyncio.coroutine
- def send_command(cls, device_id, action):
+ async def send_command(cls, device_id, action):
"""Send device command to Rflink and wait for acknowledgement."""
- return (yield from cls._protocol.send_command_ack(device_id, action))
+ return await cls._protocol.send_command_ack(device_id, action)
- @asyncio.coroutine
- def _async_handle_command(self, command, *args):
+ async def _async_handle_command(self, command, *args):
"""Do bookkeeping for command, send it to rflink and update state."""
self.cancel_queued_send_commands()
@@ -412,10 +406,10 @@ class RflinkCommand(RflinkDevice):
# Send initial command and queue repetitions.
# This allows the entity state to be updated quickly and not having to
# wait for all repetitions to be sent
- yield from self._async_send_command(cmd, self._signal_repetitions)
+ await self._async_send_command(cmd, self._signal_repetitions)
# Update state of entity
- yield from self.async_update_ha_state()
+ await self.async_update_ha_state()
def cancel_queued_send_commands(self):
"""Cancel queued signal repetition commands.
@@ -428,8 +422,7 @@ class RflinkCommand(RflinkDevice):
if self._repetition_task:
self._repetition_task.cancel()
- @asyncio.coroutine
- def _async_send_command(self, cmd, repetitions):
+ async def _async_send_command(self, cmd, repetitions):
"""Send a command for device to Rflink gateway."""
_LOGGER.debug(
"Sending command: %s to Rflink device: %s", cmd, self._device_id)
@@ -440,7 +433,7 @@ class RflinkCommand(RflinkDevice):
if self._wait_ack:
# Puts command on outgoing buffer then waits for Rflink to confirm
# the command has been send out in the ether.
- yield from self._protocol.send_command_ack(self._device_id, cmd)
+ await self._protocol.send_command_ack(self._device_id, cmd)
else:
# Puts command on outgoing buffer and returns straight away.
# Rflink protocol/transport handles asynchronous writing of buffer
@@ -450,7 +443,7 @@ class RflinkCommand(RflinkDevice):
self._protocol.send_command, self._device_id, cmd))
if repetitions > 1:
- self._repetition_task = self.hass.async_add_job(
+ self._repetition_task = self.hass.async_create_task(
self._async_send_command(cmd, repetitions - 1))
diff --git a/homeassistant/components/rfxtrx.py b/homeassistant/components/rfxtrx.py
index b5bc97b7ffa..f2c82842bc1 100644
--- a/homeassistant/components/rfxtrx.py
+++ b/homeassistant/components/rfxtrx.py
@@ -4,7 +4,6 @@ Support for RFXtrx components.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/rfxtrx/
"""
-import asyncio
from collections import OrderedDict
import logging
@@ -316,8 +315,7 @@ class RfxtrxDevice(Entity):
self._brightness = 0
self.added_to_hass = False
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe RFXtrx events."""
self.added_to_hass = True
diff --git a/homeassistant/components/rss_feed_template.py b/homeassistant/components/rss_feed_template.py
index 1441a98c0a8..34bee1ec5fc 100644
--- a/homeassistant/components/rss_feed_template.py
+++ b/homeassistant/components/rss_feed_template.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/rss_feed_template/
"""
-import asyncio
from html import escape
from aiohttp import web
@@ -76,8 +75,7 @@ class RssView(HomeAssistantView):
self._title = title
self._items = items
- @asyncio.coroutine
- def get(self, request, entity_id=None):
+ async def get(self, request, entity_id=None):
"""Generate the RSS view XML."""
response = '\n\n'
diff --git a/homeassistant/components/satel_integra.py b/homeassistant/components/satel_integra.py
index 128377d19f7..8d7d1e619db 100644
--- a/homeassistant/components/satel_integra.py
+++ b/homeassistant/components/satel_integra.py
@@ -67,8 +67,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Satel Integra component."""
conf = config.get(DOMAIN)
@@ -83,13 +82,12 @@ def async_setup(hass, config):
hass.data[DATA_SATEL] = controller
- result = yield from controller.connect()
+ result = await controller.connect()
if not result:
return False
- @asyncio.coroutine
- def _close():
+ async def _close():
controller.close()
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close())
@@ -105,7 +103,7 @@ def async_setup(hass, config):
async_load_platform(hass, 'binary_sensor', DOMAIN,
{CONF_ZONES: zones}, config))
- yield from asyncio.wait([task_control_panel, task_zones], loop=hass.loop)
+ await asyncio.wait([task_control_panel, task_zones], loop=hass.loop)
@callback
def alarm_status_update_callback(status):
diff --git a/homeassistant/components/scene/__init__.py b/homeassistant/components/scene/__init__.py
index 8771a84c1d6..2bcb1c8e16d 100644
--- a/homeassistant/components/scene/__init__.py
+++ b/homeassistant/components/scene/__init__.py
@@ -12,7 +12,6 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, SERVICE_TURN_ON)
-from homeassistant.loader import bind_hass
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
@@ -61,17 +60,6 @@ SCENE_SERVICE_SCHEMA = vol.Schema({
})
-@bind_hass
-def activate(hass, entity_id=None):
- """Activate a scene."""
- data = {}
-
- if entity_id:
- data[ATTR_ENTITY_ID] = entity_id
-
- hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
-
-
async def async_setup(hass, config):
"""Set up the scenes."""
logger = logging.getLogger(__name__)
diff --git a/homeassistant/components/scene/homeassistant.py b/homeassistant/components/scene/homeassistant.py
index 7e1d670ca69..5812512ccef 100644
--- a/homeassistant/components/scene/homeassistant.py
+++ b/homeassistant/components/scene/homeassistant.py
@@ -4,7 +4,6 @@ Allow users to set and activate scenes.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/scene/
"""
-import asyncio
from collections import namedtuple
import voluptuous as vol
@@ -35,9 +34,8 @@ PLATFORM_SCHEMA = vol.Schema({
SCENECONFIG = namedtuple('SceneConfig', [CONF_NAME, STATES])
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up home assistant scene entries."""
scene_config = config.get(STATES)
@@ -97,8 +95,7 @@ class HomeAssistantScene(Scene):
ATTR_ENTITY_ID: list(self.scene_config.states.keys()),
}
- @asyncio.coroutine
- def async_activate(self):
+ async def async_activate(self):
"""Activate scene. Try to get entities into requested state."""
- yield from async_reproduce_state(
+ await async_reproduce_state(
self.hass, self.scene_config.states.values(), True)
diff --git a/homeassistant/components/scene/hunterdouglas_powerview.py b/homeassistant/components/scene/hunterdouglas_powerview.py
index 40534b68635..8c1ffa17e90 100644
--- a/homeassistant/components/scene/hunterdouglas_powerview.py
+++ b/homeassistant/components/scene/hunterdouglas_powerview.py
@@ -4,7 +4,6 @@ Support for Powerview scenes from a Powerview hub.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/scene.hunterdouglas_powerview/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -36,9 +35,8 @@ ROOM_ID_IN_SCENE = 'roomId'
STATE_ATTRIBUTE_ROOM_NAME = 'roomName'
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up home assistant scene entries."""
# from aiopvapi.hub import Hub
from aiopvapi.scenes import Scenes
@@ -48,9 +46,9 @@ def async_setup_platform(hass, config, async_add_entities,
hub_address = config.get(HUB_ADDRESS)
websession = async_get_clientsession(hass)
- _scenes = yield from Scenes(
+ _scenes = await Scenes(
hub_address, hass.loop, websession).get_resources()
- _rooms = yield from Rooms(
+ _rooms = await Rooms(
hub_address, hass.loop, websession).get_resources()
if not _scenes or not _rooms:
diff --git a/homeassistant/components/scene/lifx_cloud.py b/homeassistant/components/scene/lifx_cloud.py
index 3169acb3a31..c1dda86343d 100644
--- a/homeassistant/components/scene/lifx_cloud.py
+++ b/homeassistant/components/scene/lifx_cloud.py
@@ -29,9 +29,8 @@ PLATFORM_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the scenes stored in the LIFX Cloud."""
token = config.get(CONF_TOKEN)
timeout = config.get(CONF_TIMEOUT)
@@ -45,7 +44,7 @@ def async_setup_platform(hass, config, async_add_entities,
try:
httpsession = async_get_clientsession(hass)
with async_timeout.timeout(timeout, loop=hass.loop):
- scenes_resp = yield from httpsession.get(url, headers=headers)
+ scenes_resp = await httpsession.get(url, headers=headers)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", url)
@@ -53,7 +52,7 @@ def async_setup_platform(hass, config, async_add_entities,
status = scenes_resp.status
if status == 200:
- data = yield from scenes_resp.json()
+ data = await scenes_resp.json()
devices = []
for scene in data:
devices.append(LifxCloudScene(hass, headers, timeout, scene))
@@ -83,15 +82,14 @@ class LifxCloudScene(Scene):
"""Return the name of the scene."""
return self._name
- @asyncio.coroutine
- def async_activate(self):
+ async def async_activate(self):
"""Activate the scene."""
url = LIFX_API_URL.format('scenes/scene_id:%s/activate' % self._uuid)
try:
httpsession = async_get_clientsession(self.hass)
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
- yield from httpsession.put(url, headers=self._headers)
+ await httpsession.put(url, headers=self._headers)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.exception("Error on %s", url)
diff --git a/homeassistant/components/scene/lutron_caseta.py b/homeassistant/components/scene/lutron_caseta.py
index 0f9173663a9..0ef974e2778 100644
--- a/homeassistant/components/scene/lutron_caseta.py
+++ b/homeassistant/components/scene/lutron_caseta.py
@@ -4,7 +4,6 @@ Support for Lutron Caseta scenes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/scene.lutron_caseta/
"""
-import asyncio
import logging
from homeassistant.components.lutron_caseta import LUTRON_CASETA_SMARTBRIDGE
@@ -15,9 +14,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron_caseta']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Lutron Caseta lights."""
devs = []
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
@@ -43,7 +41,6 @@ class LutronCasetaScene(Scene):
"""Return the name of the scene."""
return self._scene_name
- @asyncio.coroutine
- def async_activate(self):
+ async def async_activate(self):
"""Activate the scene."""
self._bridge.activate_scene(self._scene_id)
diff --git a/homeassistant/components/scene/wink.py b/homeassistant/components/scene/wink.py
index 62da668694b..35db96c3b8b 100644
--- a/homeassistant/components/scene/wink.py
+++ b/homeassistant/components/scene/wink.py
@@ -4,7 +4,6 @@ Support for Wink scenes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/scene.wink/
"""
-import asyncio
import logging
from homeassistant.components.scene import Scene
@@ -33,8 +32,7 @@ class WinkScene(WinkDevice, Scene):
super().__init__(wink, hass)
hass.data[DOMAIN]['entities']['scene'].append(self)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['scene'].append(self)
diff --git a/homeassistant/components/script.py b/homeassistant/components/script.py
index 247ac07283e..16c9f65420c 100644
--- a/homeassistant/components/script.py
+++ b/homeassistant/components/script.py
@@ -15,7 +15,6 @@ import voluptuous as vol
from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_TOGGLE, SERVICE_RELOAD, STATE_ON, CONF_ALIAS)
-from homeassistant.core import split_entity_id
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.helpers.entity_component import EntityComponent
@@ -62,41 +61,6 @@ def is_on(hass, entity_id):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, entity_id, variables=None, context=None):
- """Turn script on."""
- _, object_id = split_entity_id(entity_id)
-
- hass.services.call(DOMAIN, object_id, variables, context=context)
-
-
-@bind_hass
-def turn_off(hass, entity_id):
- """Turn script on."""
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
-
-
-@bind_hass
-def toggle(hass, entity_id):
- """Toggle the script."""
- hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
-
-
-@bind_hass
-def reload(hass):
- """Reload script component."""
- hass.services.call(DOMAIN, SERVICE_RELOAD)
-
-
-@bind_hass
-def async_reload(hass):
- """Reload the scripts from config.
-
- Returns a coroutine object.
- """
- return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
-
-
async def async_setup(hass, config):
"""Load the scripts from the configuration."""
component = EntityComponent(
diff --git a/homeassistant/components/sensor/__init__.py b/homeassistant/components/sensor/__init__.py
index 948f844cfd4..be599cc295a 100644
--- a/homeassistant/components/sensor/__init__.py
+++ b/homeassistant/components/sensor/__init__.py
@@ -14,7 +14,7 @@ from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
from homeassistant.const import (
DEVICE_CLASS_BATTERY, DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE,
- DEVICE_CLASS_TEMPERATURE)
+ DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_PRESSURE)
_LOGGER = logging.getLogger(__name__)
@@ -28,6 +28,7 @@ DEVICE_CLASSES = [
DEVICE_CLASS_HUMIDITY, # % of humidity in the air
DEVICE_CLASS_ILLUMINANCE, # current light level (lx/lm)
DEVICE_CLASS_TEMPERATURE, # temperature (C/F)
+ DEVICE_CLASS_PRESSURE, # pressure (hPa/mbar)
]
DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES))
diff --git a/homeassistant/components/sensor/ads.py b/homeassistant/components/sensor/ads.py
index 5d5cbb379bf..24515357f5e 100644
--- a/homeassistant/components/sensor/ads.py
+++ b/homeassistant/components/sensor/ads.py
@@ -4,7 +4,6 @@ Support for ADS sensors.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/sensor.ads/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -62,8 +61,7 @@ class AdsSensor(Entity):
self.ads_type = ads_type
self.factor = factor
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register device notification."""
def update(name, value):
"""Handle device notifications."""
diff --git a/homeassistant/components/sensor/alarmdecoder.py b/homeassistant/components/sensor/alarmdecoder.py
index 51e166bfce6..546d09299dc 100644
--- a/homeassistant/components/sensor/alarmdecoder.py
+++ b/homeassistant/components/sensor/alarmdecoder.py
@@ -4,7 +4,6 @@ Support for AlarmDecoder Sensors (Shows Panel Display).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.alarmdecoder/
"""
-import asyncio
import logging
from homeassistant.helpers.entity import Entity
@@ -34,8 +33,7 @@ class AlarmDecoderSensor(Entity):
self._icon = 'mdi:alarm-check'
self._name = 'Alarm Panel Display'
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_PANEL_MESSAGE, self._message_callback)
diff --git a/homeassistant/components/sensor/amcrest.py b/homeassistant/components/sensor/amcrest.py
index 53a8c663f21..50d6e9b7fa9 100644
--- a/homeassistant/components/sensor/amcrest.py
+++ b/homeassistant/components/sensor/amcrest.py
@@ -4,7 +4,6 @@ This component provides HA sensor support for Amcrest IP cameras.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.amcrest/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -19,9 +18,8 @@ _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=10)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up a sensor for an Amcrest IP Camera."""
if discovery_info is None:
return
diff --git a/homeassistant/components/sensor/android_ip_webcam.py b/homeassistant/components/sensor/android_ip_webcam.py
index 333bf12ec21..0f795f85dcd 100644
--- a/homeassistant/components/sensor/android_ip_webcam.py
+++ b/homeassistant/components/sensor/android_ip_webcam.py
@@ -4,7 +4,6 @@ Support for IP Webcam sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.android_ip_webcam/
"""
-import asyncio
from homeassistant.components.android_ip_webcam import (
KEY_MAP, ICON_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST,
@@ -14,9 +13,8 @@ from homeassistant.helpers.icon import icon_for_battery_level
DEPENDENCIES = ['android_ip_webcam']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the IP Webcam Sensor."""
if discovery_info is None:
return
@@ -62,8 +60,7 @@ class IPWebcamSensor(AndroidIPCamEntity):
"""Return the state of the sensor."""
return self._state
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Retrieve latest state."""
if self._sensor in ('audio_connections', 'video_connections'):
if not self._ipcam.status_data:
diff --git a/homeassistant/components/sensor/api_streams.py b/homeassistant/components/sensor/api_streams.py
index 1ecae1e753e..ac9b15754d0 100644
--- a/homeassistant/components/sensor/api_streams.py
+++ b/homeassistant/components/sensor/api_streams.py
@@ -4,7 +4,6 @@ Entity to track connections to stream API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.api_stream/
"""
-import asyncio
import logging
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
@@ -48,9 +47,8 @@ class StreamHandler(logging.Handler):
self.entity.schedule_update_ha_state()
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the API stream platform."""
entity = APICount()
handler = StreamHandler(entity)
diff --git a/homeassistant/components/sensor/aqualogic.py b/homeassistant/components/sensor/aqualogic.py
new file mode 100644
index 00000000000..f10fd05b83f
--- /dev/null
+++ b/homeassistant/components/sensor/aqualogic.py
@@ -0,0 +1,111 @@
+"""
+Support for AquaLogic sensors.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/sensor.aqualogic/
+"""
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (CONF_MONITORED_CONDITIONS,
+ TEMP_CELSIUS, TEMP_FAHRENHEIT)
+from homeassistant.core import callback
+from homeassistant.helpers.entity import Entity
+import homeassistant.components.aqualogic as aq
+import homeassistant.helpers.config_validation as cv
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['aqualogic']
+
+TEMP_UNITS = [TEMP_CELSIUS, TEMP_FAHRENHEIT]
+PERCENT_UNITS = ['%', '%']
+SALT_UNITS = ['g/L', 'PPM']
+WATT_UNITS = ['W', 'W']
+NO_UNITS = [None, None]
+
+# sensor_type [ description, unit, icon ]
+# sensor_type corresponds to property names in aqualogic.core.AquaLogic
+SENSOR_TYPES = {
+ 'air_temp': ['Air Temperature', TEMP_UNITS, 'mdi:thermometer'],
+ 'pool_temp': ['Pool Temperature', TEMP_UNITS, 'mdi:oil-temperature'],
+ 'spa_temp': ['Spa Temperature', TEMP_UNITS, 'mdi:oil-temperature'],
+ 'pool_chlorinator': ['Pool Chlorinator', PERCENT_UNITS, 'mdi:gauge'],
+ 'spa_chlorinator': ['Spa Chlorinator', PERCENT_UNITS, 'mdi:gauge'],
+ 'salt_level': ['Salt Level', SALT_UNITS, 'mdi:gauge'],
+ 'pump_speed': ['Pump Speed', PERCENT_UNITS, 'mdi:speedometer'],
+ 'pump_power': ['Pump Power', WATT_UNITS, 'mdi:gauge'],
+ 'status': ['Status', NO_UNITS, 'mdi:alert']
+}
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
+ vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)])
+})
+
+
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
+ """Set up the sensor platform."""
+ sensors = []
+
+ processor = hass.data[aq.DOMAIN]
+ for sensor_type in config.get(CONF_MONITORED_CONDITIONS):
+ sensors.append(AquaLogicSensor(processor, sensor_type))
+
+ async_add_entities(sensors)
+
+
+class AquaLogicSensor(Entity):
+ """Sensor implementation for the AquaLogic component."""
+
+ def __init__(self, processor, sensor_type):
+ """Initialize sensor."""
+ self._processor = processor
+ self._type = sensor_type
+ self._state = None
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return self._state
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return "AquaLogic {}".format(SENSOR_TYPES[self._type][0])
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit of measurement the value is expressed in."""
+ panel = self._processor.panel
+ if panel is None:
+ return None
+ if panel.is_metric:
+ return SENSOR_TYPES[self._type][1][0]
+ return SENSOR_TYPES[self._type][1][1]
+
+ @property
+ def should_poll(self):
+ """Return the polling state."""
+ return False
+
+ @property
+ def icon(self):
+ """Icon to use in the frontend, if any."""
+ return SENSOR_TYPES[self._type][2]
+
+ async def async_added_to_hass(self):
+ """Register callbacks."""
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ aq.UPDATE_TOPIC, self.async_update_callback)
+
+ @callback
+ def async_update_callback(self):
+ """Update callback."""
+ panel = self._processor.panel
+ if panel is not None:
+ self._state = getattr(panel, self._type)
+ self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/sensor/arwn.py b/homeassistant/components/sensor/arwn.py
index 580701490a6..2b79e4c3a9a 100644
--- a/homeassistant/components/sensor/arwn.py
+++ b/homeassistant/components/sensor/arwn.py
@@ -4,7 +4,6 @@ Support for collecting data from the ARWN project.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.arwn/
"""
-import asyncio
import json
import logging
@@ -58,9 +57,8 @@ def _slug(name):
return 'sensor.arwn_{}'.format(slugify(name))
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the ARWN platform."""
@callback
def async_sensor_event_received(topic, payload, qos):
@@ -102,7 +100,7 @@ def async_setup_platform(hass, config, async_add_entities,
else:
store[sensor.name].set_event(event)
- yield from mqtt.async_subscribe(
+ await mqtt.async_subscribe(
hass, TOPIC, async_sensor_event_received, 0)
return True
diff --git a/homeassistant/components/sensor/bh1750.py b/homeassistant/components/sensor/bh1750.py
index 6230ae8a74d..fb0a0116818 100644
--- a/homeassistant/components/sensor/bh1750.py
+++ b/homeassistant/components/sensor/bh1750.py
@@ -4,7 +4,6 @@ Support for BH1750 light sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bh1750/
"""
-import asyncio
from functools import partial
import logging
@@ -66,9 +65,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=import-error
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the BH1750 sensor."""
import smbus
from i2csense.bh1750 import BH1750
@@ -80,7 +78,7 @@ def async_setup_platform(hass, config, async_add_entities,
bus = smbus.SMBus(bus_number)
- sensor = yield from hass.async_add_job(
+ sensor = await hass.async_add_job(
partial(BH1750, bus, i2c_address,
operation_mode=operation_mode,
measurement_delay=config.get(CONF_DELAY),
@@ -133,10 +131,9 @@ class BH1750Sensor(Entity):
"""Return the class of this device, from component DEVICE_CLASSES."""
return DEVICE_CLASS_ILLUMINANCE
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from the BH1750 and update the states."""
- yield from self.hass.async_add_job(self.bh1750_sensor.update)
+ await self.hass.async_add_job(self.bh1750_sensor.update)
if self.bh1750_sensor.sample_ok \
and self.bh1750_sensor.light_level >= 0:
self._state = int(round(self.bh1750_sensor.light_level
diff --git a/homeassistant/components/sensor/blink.py b/homeassistant/components/sensor/blink.py
index 97356b6fc61..885bb939edf 100644
--- a/homeassistant/components/sensor/blink.py
+++ b/homeassistant/components/sensor/blink.py
@@ -6,34 +6,24 @@ https://home-assistant.io/components/sensor.blink/
"""
import logging
-from homeassistant.components.blink import DOMAIN
-from homeassistant.const import TEMP_FAHRENHEIT
+from homeassistant.components.blink import BLINK_DATA, SENSORS
from homeassistant.helpers.entity import Entity
+from homeassistant.const import CONF_MONITORED_CONDITIONS
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['blink']
-SENSOR_TYPES = {
- 'temperature': ['Temperature', TEMP_FAHRENHEIT],
- 'battery': ['Battery', ''],
- 'notifications': ['Notifications', '']
-}
-
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a Blink sensor."""
if discovery_info is None:
return
-
- data = hass.data[DOMAIN].blink
- devs = list()
- index = 0
- for name in data.cameras:
- devs.append(BlinkSensor(name, 'temperature', index, data))
- devs.append(BlinkSensor(name, 'battery', index, data))
- devs.append(BlinkSensor(name, 'notifications', index, data))
- index += 1
+ data = hass.data[BLINK_DATA]
+ devs = []
+ for camera in data.sync.cameras:
+ for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
+ devs.append(BlinkSensor(data, camera, sensor_type))
add_entities(devs, True)
@@ -41,21 +31,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class BlinkSensor(Entity):
"""A Blink camera sensor."""
- def __init__(self, name, sensor_type, index, data):
+ def __init__(self, data, camera, sensor_type):
"""Initialize sensors from Blink camera."""
- self._name = 'blink_' + name + '_' + SENSOR_TYPES[sensor_type][0]
+ name, units, icon = SENSORS[sensor_type]
+ self._name = "{} {} {}".format(
+ BLINK_DATA, camera, name)
self._camera_name = name
self._type = sensor_type
self.data = data
- self.index = index
+ self._camera = data.sync.cameras[camera]
self._state = None
- self._unit_of_measurement = SENSOR_TYPES[sensor_type][1]
+ self._unit_of_measurement = units
+ self._icon = icon
@property
def name(self):
"""Return the name of the camera."""
return self._name
+ @property
+ def icon(self):
+ """Return the icon of the sensor."""
+ return self._icon
+
@property
def state(self):
"""Return the camera's current state."""
@@ -68,13 +66,11 @@ class BlinkSensor(Entity):
def update(self):
"""Retrieve sensor data from the camera."""
- camera = self.data.cameras[self._camera_name]
- if self._type == 'temperature':
- self._state = camera.temperature
- elif self._type == 'battery':
- self._state = camera.battery_string
- elif self._type == 'notifications':
- self._state = camera.notifications
- else:
+ self.data.refresh()
+ try:
+ self._state = self._camera.attributes[self._type]
+ except KeyError:
self._state = None
- _LOGGER.warning("Could not retrieve state from %s", self.name)
+ _LOGGER.error(
+ "%s not a valid camera attribute. Did the API change?",
+ self._type)
diff --git a/homeassistant/components/sensor/bme280.py b/homeassistant/components/sensor/bme280.py
index 676800c1069..f67dace817e 100644
--- a/homeassistant/components/sensor/bme280.py
+++ b/homeassistant/components/sensor/bme280.py
@@ -4,7 +4,6 @@ Support for BME280 temperature, humidity and pressure sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bme280/
"""
-import asyncio
from datetime import timedelta
from functools import partial
import logging
@@ -81,9 +80,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=import-error
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the BME280 sensor."""
import smbus
from i2csense.bme280 import BME280
@@ -93,7 +91,7 @@ def async_setup_platform(hass, config, async_add_entities,
i2c_address = config.get(CONF_I2C_ADDRESS)
bus = smbus.SMBus(config.get(CONF_I2C_BUS))
- sensor = yield from hass.async_add_job(
+ sensor = await hass.async_add_job(
partial(BME280, bus, i2c_address,
osrs_t=config.get(CONF_OVERSAMPLING_TEMP),
osrs_p=config.get(CONF_OVERSAMPLING_PRES),
@@ -108,7 +106,7 @@ def async_setup_platform(hass, config, async_add_entities,
_LOGGER.error("BME280 sensor not detected at %s", i2c_address)
return False
- sensor_handler = yield from hass.async_add_job(BME280Handler, sensor)
+ sensor_handler = await hass.async_add_job(BME280Handler, sensor)
dev = []
try:
@@ -163,10 +161,9 @@ class BME280Sensor(Entity):
"""Return the unit of measurement of the sensor."""
return self._unit_of_measurement
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from the BME280 and update the states."""
- yield from self.hass.async_add_job(self.bme280_client.update)
+ await self.hass.async_add_job(self.bme280_client.update)
if self.bme280_client.sensor.sample_ok:
if self.type == SENSOR_TEMP:
temperature = round(self.bme280_client.sensor.temperature, 1)
diff --git a/homeassistant/components/sensor/bme680.py b/homeassistant/components/sensor/bme680.py
index 65d486ada36..c4e8baf6c05 100644
--- a/homeassistant/components/sensor/bme680.py
+++ b/homeassistant/components/sensor/bme680.py
@@ -7,7 +7,6 @@ Air Quality calculation based on humidity and volatile gas.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bme680/
"""
-import asyncio
import logging
from time import time, sleep
@@ -97,14 +96,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the BME680 sensor."""
SENSOR_TYPES[SENSOR_TEMP][1] = hass.config.units.temperature_unit
name = config.get(CONF_NAME)
- sensor_handler = yield from hass.async_add_job(_setup_bme680, config)
+ sensor_handler = await hass.async_add_job(_setup_bme680, config)
if sensor_handler is None:
return
@@ -351,10 +349,9 @@ class BME680Sensor(Entity):
"""Return the unit of measurement of the sensor."""
return self._unit_of_measurement
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from the BME680 and update the states."""
- yield from self.hass.async_add_job(self.bme680_client.update)
+ await self.hass.async_add_job(self.bme680_client.update)
if self.type == SENSOR_TEMP:
temperature = round(self.bme680_client.sensor_data.temperature, 1)
if self.temp_unit == TEMP_FAHRENHEIT:
diff --git a/homeassistant/components/sensor/bmw_connected_drive.py b/homeassistant/components/sensor/bmw_connected_drive.py
index ff80100e21d..964a8a4cb16 100644
--- a/homeassistant/components/sensor/bmw_connected_drive.py
+++ b/homeassistant/components/sensor/bmw_connected_drive.py
@@ -4,7 +4,6 @@ Reads vehicle status from BMW connected drive portal.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.bmw_connected_drive/
"""
-import asyncio
import logging
from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN
@@ -124,8 +123,7 @@ class BMWConnectedDriveSensor(Entity):
"""Schedule a state update."""
self.schedule_update_ha_state(True)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Add callback after being added to hass.
Show latest data after startup.
diff --git a/homeassistant/components/sensor/buienradar.py b/homeassistant/components/sensor/buienradar.py
index c7ca0c097ff..36585b8e103 100644
--- a/homeassistant/components/sensor/buienradar.py
+++ b/homeassistant/components/sensor/buienradar.py
@@ -140,9 +140,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Create the buienradar sensor."""
from homeassistant.components.weather.buienradar import DEFAULT_TIMEFRAME
@@ -168,7 +167,7 @@ def async_setup_platform(hass, config, async_add_entities,
data = BrData(hass, coordinates, timeframe, dev)
# schedule the first update in 1 minute from now:
- yield from data.schedule_update(1)
+ await data.schedule_update(1)
class BrSensor(Entity):
@@ -386,8 +385,7 @@ class BrData:
self.coordinates = coordinates
self.timeframe = timeframe
- @asyncio.coroutine
- def update_devices(self):
+ async def update_devices(self):
"""Update all devices/sensors."""
if self.devices:
tasks = []
@@ -397,18 +395,16 @@ class BrData:
tasks.append(dev.async_update_ha_state())
if tasks:
- yield from asyncio.wait(tasks, loop=self.hass.loop)
+ await asyncio.wait(tasks, loop=self.hass.loop)
- @asyncio.coroutine
- def schedule_update(self, minute=1):
+ async def schedule_update(self, minute=1):
"""Schedule an update after minute minutes."""
_LOGGER.debug("Scheduling next update in %s minutes.", minute)
nxt = dt_util.utcnow() + timedelta(minutes=minute)
async_track_point_in_utc_time(self.hass, self.async_update,
nxt)
- @asyncio.coroutine
- def get_data(self, url):
+ async def get_data(self, url):
"""Load data from specified url."""
from buienradar.buienradar import (CONTENT,
MESSAGE, STATUS_CODE, SUCCESS)
@@ -419,10 +415,10 @@ class BrData:
try:
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(10, loop=self.hass.loop):
- resp = yield from websession.get(url)
+ resp = await websession.get(url)
result[STATUS_CODE] = resp.status
- result[CONTENT] = yield from resp.text()
+ result[CONTENT] = await resp.text()
if resp.status == 200:
result[SUCCESS] = True
else:
@@ -434,17 +430,16 @@ class BrData:
return result
finally:
if resp is not None:
- yield from resp.release()
+ await resp.release()
- @asyncio.coroutine
- def async_update(self, *_):
+ async def async_update(self, *_):
"""Update the data from buienradar."""
from buienradar.buienradar import (parse_data, CONTENT,
DATA, MESSAGE, STATUS_CODE, SUCCESS)
- content = yield from self.get_data('http://xml.buienradar.nl')
+ content = await self.get_data('http://xml.buienradar.nl')
if not content.get(SUCCESS, False):
- content = yield from self.get_data('http://api.buienradar.nl')
+ content = await self.get_data('http://api.buienradar.nl')
if content.get(SUCCESS) is not True:
# unable to get the data
@@ -453,7 +448,7 @@ class BrData:
content.get(MESSAGE),
content.get(STATUS_CODE),)
# schedule new call
- yield from self.schedule_update(SCHEDULE_NOK)
+ await self.schedule_update(SCHEDULE_NOK)
return
# rounding coordinates prevents unnecessary redirects/calls
@@ -462,7 +457,7 @@ class BrData:
round(self.coordinates[CONF_LATITUDE], 2),
round(self.coordinates[CONF_LONGITUDE], 2)
)
- raincontent = yield from self.get_data(rainurl)
+ raincontent = await self.get_data(rainurl)
if raincontent.get(SUCCESS) is not True:
# unable to get the data
@@ -471,7 +466,7 @@ class BrData:
raincontent.get(MESSAGE),
raincontent.get(STATUS_CODE),)
# schedule new call
- yield from self.schedule_update(SCHEDULE_NOK)
+ await self.schedule_update(SCHEDULE_NOK)
return
result = parse_data(content.get(CONTENT),
@@ -486,12 +481,12 @@ class BrData:
_LOGGER.warning("Unable to parse data from Buienradar."
"(Msg: %s)",
result.get(MESSAGE),)
- yield from self.schedule_update(SCHEDULE_NOK)
+ await self.schedule_update(SCHEDULE_NOK)
return
self.data = result.get(DATA)
- yield from self.update_devices()
- yield from self.schedule_update(SCHEDULE_OK)
+ await self.update_devices()
+ await self.schedule_update(SCHEDULE_OK)
@property
def attribution(self):
diff --git a/homeassistant/components/sensor/citybikes.py b/homeassistant/components/sensor/citybikes.py
index 8003a77a452..12c475e62ff 100644
--- a/homeassistant/components/sensor/citybikes.py
+++ b/homeassistant/components/sensor/citybikes.py
@@ -104,16 +104,15 @@ class CityBikesRequestError(Exception):
pass
-@asyncio.coroutine
-def async_citybikes_request(hass, uri, schema):
+async def async_citybikes_request(hass, uri, schema):
"""Perform a request to CityBikes API endpoint, and parse the response."""
try:
session = async_get_clientsession(hass)
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
- req = yield from session.get(DEFAULT_ENDPOINT.format(uri=uri))
+ req = await session.get(DEFAULT_ENDPOINT.format(uri=uri))
- json_response = yield from req.json()
+ json_response = await req.json()
return schema(json_response)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Could not connect to CityBikes API endpoint")
@@ -125,9 +124,8 @@ def async_citybikes_request(hass, uri, schema):
raise CityBikesRequestError
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the CityBikes platform."""
if PLATFORM not in hass.data:
hass.data[PLATFORM] = {MONITORED_NETWORKS: {}}
@@ -142,7 +140,7 @@ def async_setup_platform(hass, config, async_add_entities,
radius = distance.convert(radius, LENGTH_FEET, LENGTH_METERS)
if not network_id:
- network_id = yield from CityBikesNetwork.get_closest_network_id(
+ network_id = await CityBikesNetwork.get_closest_network_id(
hass, latitude, longitude)
if network_id not in hass.data[PLATFORM][MONITORED_NETWORKS]:
@@ -153,7 +151,7 @@ def async_setup_platform(hass, config, async_add_entities,
else:
network = hass.data[PLATFORM][MONITORED_NETWORKS][network_id]
- yield from network.ready.wait()
+ await network.ready.wait()
devices = []
for station in network.stations:
@@ -177,13 +175,12 @@ class CityBikesNetwork:
NETWORKS_LIST_LOADING = asyncio.Condition()
@classmethod
- @asyncio.coroutine
- def get_closest_network_id(cls, hass, latitude, longitude):
+ async def get_closest_network_id(cls, hass, latitude, longitude):
"""Return the id of the network closest to provided location."""
try:
- yield from cls.NETWORKS_LIST_LOADING.acquire()
+ await cls.NETWORKS_LIST_LOADING.acquire()
if cls.NETWORKS_LIST is None:
- networks = yield from async_citybikes_request(
+ networks = await async_citybikes_request(
hass, NETWORKS_URI, NETWORKS_RESPONSE_SCHEMA)
cls.NETWORKS_LIST = networks[ATTR_NETWORKS_LIST]
result = None
@@ -210,11 +207,10 @@ class CityBikesNetwork:
self.stations = []
self.ready = asyncio.Event()
- @asyncio.coroutine
- def async_refresh(self, now=None):
+ async def async_refresh(self, now=None):
"""Refresh the state of the network."""
try:
- network = yield from async_citybikes_request(
+ network = await async_citybikes_request(
self.hass, STATIONS_URI.format(uid=self.network_id),
STATIONS_RESPONSE_SCHEMA)
self.stations = network[ATTR_NETWORK][ATTR_STATIONS_LIST]
@@ -253,8 +249,7 @@ class CityBikesStation(Entity):
return self._station_data[ATTR_NAME]
return None
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update station state."""
if self._network.ready.is_set():
for station in self._network.stations:
diff --git a/homeassistant/components/sensor/comed_hourly_pricing.py b/homeassistant/components/sensor/comed_hourly_pricing.py
index 3595bcaa227..b5d230d8517 100644
--- a/homeassistant/components/sensor/comed_hourly_pricing.py
+++ b/homeassistant/components/sensor/comed_hourly_pricing.py
@@ -49,9 +49,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the ComEd Hourly Pricing sensor."""
websession = async_get_clientsession(hass)
dev = []
@@ -101,8 +100,7 @@ class ComedHourlyPricingSensor(Entity):
attrs = {ATTR_ATTRIBUTION: CONF_ATTRIBUTION}
return attrs
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the ComEd Hourly Pricing data from the web service."""
try:
if self.type == CONF_FIVE_MINUTE or \
@@ -114,9 +112,9 @@ class ComedHourlyPricingSensor(Entity):
url_string += '?type=currenthouraverage'
with async_timeout.timeout(60, loop=self.loop):
- response = yield from self.websession.get(url_string)
+ response = await self.websession.get(url_string)
# The API responds with MIME type 'text/html'
- text = yield from response.text()
+ text = await response.text()
data = json.loads(text)
self._state = round(
float(data[0]['price']) + self.offset, 2)
diff --git a/homeassistant/components/sensor/discogs.py b/homeassistant/components/sensor/discogs.py
index 6e85c41ac6e..70d7155fec7 100644
--- a/homeassistant/components/sensor/discogs.py
+++ b/homeassistant/components/sensor/discogs.py
@@ -4,7 +4,6 @@ Show the amount of records in a user's Discogs collection.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.discogs/
"""
-import asyncio
from datetime import timedelta
import logging
@@ -36,9 +35,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Discogs sensor."""
import discogs_client
@@ -92,7 +90,6 @@ class DiscogsSensor(Entity):
ATTR_IDENTITY: self._identity.name,
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Set state to the amount of records in user's collection."""
self._state = self._identity.num_collection
diff --git a/homeassistant/components/sensor/dnsip.py b/homeassistant/components/sensor/dnsip.py
index 3027b6f8ca6..7b0d54cd934 100644
--- a/homeassistant/components/sensor/dnsip.py
+++ b/homeassistant/components/sensor/dnsip.py
@@ -4,7 +4,6 @@ Get your own public IP address or that of any host.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.dnsip/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -42,8 +41,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
+async def async_setup_platform(hass, config, async_add_devices,
+ discovery_info=None):
"""Set up the DNS IP sensor."""
hostname = config.get(CONF_HOSTNAME)
name = config.get(CONF_NAME)
@@ -86,11 +85,10 @@ class WanIpSensor(Entity):
"""Return the current DNS IP address for hostname."""
return self._state
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the current DNS IP address for hostname."""
- response = yield from self.resolver.query(self.hostname,
- self.querytype)
+ response = await self.resolver.query(self.hostname,
+ self.querytype)
if response:
self._state = response[0].host
else:
diff --git a/homeassistant/components/sensor/dsmr.py b/homeassistant/components/sensor/dsmr.py
index 13b13114150..d54959813f8 100644
--- a/homeassistant/components/sensor/dsmr.py
+++ b/homeassistant/components/sensor/dsmr.py
@@ -48,9 +48,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the DSMR sensor."""
# Suppress logging
logging.getLogger('dsmr_parser').setLevel(logging.ERROR)
@@ -168,7 +167,7 @@ def async_setup_platform(hass, config, async_add_entities,
# Make all device entities aware of new telegram
for device in devices:
device.telegram = telegram
- hass.async_add_job(device.async_update_ha_state())
+ hass.async_create_task(device.async_update_ha_state())
# Creates an asyncio.Protocol factory for reading DSMR telegrams from
# serial and calls update_entities_telegram to update entities on arrival
@@ -182,13 +181,12 @@ def async_setup_platform(hass, config, async_add_entities,
create_dsmr_reader, config[CONF_PORT], config[CONF_DSMR_VERSION],
update_entities_telegram, loop=hass.loop)
- @asyncio.coroutine
- def connect_and_reconnect():
+ async def connect_and_reconnect():
"""Connect to DSMR and keep reconnecting until Home Assistant stops."""
while hass.state != CoreState.stopping:
# Start DSMR asyncio.Protocol reader
try:
- transport, protocol = yield from hass.loop.create_task(
+ transport, protocol = await hass.loop.create_task(
reader_factory())
except (serial.serialutil.SerialException, ConnectionRefusedError,
TimeoutError):
@@ -203,7 +201,7 @@ def async_setup_platform(hass, config, async_add_entities,
EVENT_HOMEASSISTANT_STOP, transport.close)
# Wait for reader to close
- yield from protocol.wait_closed()
+ await protocol.wait_closed()
if hass.state != CoreState.stopping:
# Unexpected disconnect
@@ -216,8 +214,8 @@ def async_setup_platform(hass, config, async_add_entities,
update_entities_telegram({})
# throttle reconnect attempts
- yield from asyncio.sleep(config[CONF_RECONNECT_INTERVAL],
- loop=hass.loop)
+ await asyncio.sleep(config[CONF_RECONNECT_INTERVAL],
+ loop=hass.loop)
# Can't be hass.async_add_job because job runs forever
hass.loop.create_task(connect_and_reconnect())
@@ -309,8 +307,7 @@ class DerivativeDSMREntity(DSMREntity):
"""Return the calculated current hourly rate."""
return self._state
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Recalculate hourly rate if timestamp has changed.
DSMR updates gas meter reading every hour. Along with the new
diff --git a/homeassistant/components/sensor/dyson.py b/homeassistant/components/sensor/dyson.py
index 4097bff32bf..53913d47b72 100644
--- a/homeassistant/components/sensor/dyson.py
+++ b/homeassistant/components/sensor/dyson.py
@@ -4,7 +4,6 @@ Support for Dyson Pure Cool Link Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.dyson/
"""
-import asyncio
import logging
from homeassistant.components.dyson import DYSON_DEVICES
@@ -58,8 +57,7 @@ class DysonSensor(Entity):
self._name = None
self._sensor_type = sensor_type
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
diff --git a/homeassistant/components/sensor/enphase_envoy.py b/homeassistant/components/sensor/enphase_envoy.py
index 7f8cff0f885..4bbf7eec01b 100644
--- a/homeassistant/components/sensor/enphase_envoy.py
+++ b/homeassistant/components/sensor/enphase_envoy.py
@@ -14,7 +14,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import (CONF_IP_ADDRESS, CONF_MONITORED_CONDITIONS)
-REQUIREMENTS = ['envoy_reader==0.2']
+REQUIREMENTS = ['envoy_reader==0.3']
_LOGGER = logging.getLogger(__name__)
SENSORS = {
diff --git a/homeassistant/components/sensor/envisalink.py b/homeassistant/components/sensor/envisalink.py
index 91f99e31b48..aed2b056d2f 100644
--- a/homeassistant/components/sensor/envisalink.py
+++ b/homeassistant/components/sensor/envisalink.py
@@ -4,7 +4,6 @@ Support for Envisalink sensors (shows panel info).
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.envisalink/
"""
-import asyncio
import logging
from homeassistant.core import callback
@@ -19,9 +18,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['envisalink']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Perform the setup for Envisalink sensor devices."""
configured_partitions = discovery_info['partitions']
@@ -51,8 +49,7 @@ class EnvisalinkSensor(EnvisalinkDevice, Entity):
_LOGGER.debug("Setting up sensor for partition: %s", partition_name)
super().__init__(partition_name + ' Keypad', info, controller)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
diff --git a/homeassistant/components/sensor/fail2ban.py b/homeassistant/components/sensor/fail2ban.py
index 0f018af819d..f152a43e241 100644
--- a/homeassistant/components/sensor/fail2ban.py
+++ b/homeassistant/components/sensor/fail2ban.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.fail2ban/
"""
import os
-import asyncio
import logging
from datetime import timedelta
@@ -39,9 +38,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the fail2ban sensor."""
name = config.get(CONF_NAME)
jails = config.get(CONF_JAILS)
diff --git a/homeassistant/components/sensor/fastdotcom.py b/homeassistant/components/sensor/fastdotcom.py
index 6624265f60c..c6a56701f7c 100644
--- a/homeassistant/components/sensor/fastdotcom.py
+++ b/homeassistant/components/sensor/fastdotcom.py
@@ -4,7 +4,6 @@ Support for Fast.com internet speed testing sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.fastdotcom/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -88,10 +87,9 @@ class SpeedtestSensor(Entity):
self._state = data['download']
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle entity which will be added."""
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
if not state:
return
self._state = state.state
diff --git a/homeassistant/components/sensor/fido.py b/homeassistant/components/sensor/fido.py
index 4c027b906a2..00754c5ba68 100644
--- a/homeassistant/components/sensor/fido.py
+++ b/homeassistant/components/sensor/fido.py
@@ -7,7 +7,6 @@ https://www.fido.ca/pages/#/my-account/wireless
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.fido/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -70,16 +69,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Fido sensor."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
httpsession = hass.helpers.aiohttp_client.async_get_clientsession()
fido_data = FidoData(username, password, httpsession)
- ret = yield from fido_data.async_update()
+ ret = await fido_data.async_update()
if ret is False:
return
@@ -134,10 +132,9 @@ class FidoSensor(Entity):
'number': self._number,
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from Fido and update the state."""
- yield from self.fido_data.async_update()
+ await self.fido_data.async_update()
if self.type == 'balance':
if self.fido_data.data.get(self.type) is not None:
self._state = round(self.fido_data.data[self.type], 2)
diff --git a/homeassistant/components/sensor/file.py b/homeassistant/components/sensor/file.py
index 1839b3566ee..3e2a5c21be8 100644
--- a/homeassistant/components/sensor/file.py
+++ b/homeassistant/components/sensor/file.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.file/
"""
import os
-import asyncio
import logging
import voluptuous as vol
@@ -32,9 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the file sensor."""
file_path = config.get(CONF_FILE_PATH)
name = config.get(CONF_NAME)
diff --git a/homeassistant/components/sensor/geo_rss_events.py b/homeassistant/components/sensor/geo_rss_events.py
index 1ba0ce2e065..5085e113e92 100644
--- a/homeassistant/components/sensor/geo_rss_events.py
+++ b/homeassistant/components/sensor/geo_rss_events.py
@@ -9,7 +9,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.geo_rss_events/
"""
import logging
-from collections import namedtuple
from datetime import timedelta
import voluptuous as vol
@@ -19,9 +18,8 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
STATE_UNKNOWN, CONF_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_RADIUS, CONF_URL)
from homeassistant.helpers.entity import Entity
-from homeassistant.util import Throttle
-REQUIREMENTS = ['feedparser==5.2.1', 'haversine==0.4.5']
+REQUIREMENTS = ['georss_client==0.3']
_LOGGER = logging.getLogger(__name__)
@@ -38,9 +36,6 @@ DEFAULT_UNIT_OF_MEASUREMENT = 'Events'
DOMAIN = 'geo_rss_events'
-# Minimum time between updates from the source.
-MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1)
-
SCAN_INTERVAL = timedelta(minutes=5)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -67,18 +62,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_LOGGER.debug("latitude=%s, longitude=%s, url=%s, radius=%s",
home_latitude, home_longitude, url, radius_in_km)
- # Initialise update service.
- data = GeoRssServiceData(home_latitude, home_longitude, url, radius_in_km)
- data.update()
-
# Create all sensors based on categories.
devices = []
if not categories:
- device = GeoRssServiceSensor(None, data, name, unit_of_measurement)
+ device = GeoRssServiceSensor((home_latitude, home_longitude), url,
+ radius_in_km, None, name,
+ unit_of_measurement)
devices.append(device)
else:
for category in categories:
- device = GeoRssServiceSensor(category, data, name,
+ device = GeoRssServiceSensor((home_latitude, home_longitude), url,
+ radius_in_km, category, name,
unit_of_measurement)
devices.append(device)
add_entities(devices, True)
@@ -87,14 +81,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class GeoRssServiceSensor(Entity):
"""Representation of a Sensor."""
- def __init__(self, category, data, service_name, unit_of_measurement):
+ def __init__(self, home_coordinates, url, radius, category, service_name,
+ unit_of_measurement):
"""Initialize the sensor."""
self._category = category
- self._data = data
self._service_name = service_name
self._state = STATE_UNKNOWN
self._state_attributes = None
self._unit_of_measurement = unit_of_measurement
+ from georss_client.generic_feed import GenericFeed
+ self._feed = GenericFeed(home_coordinates, url, filter_radius=radius,
+ filter_categories=None if not category
+ else [category])
@property
def name(self):
@@ -125,115 +123,25 @@ class GeoRssServiceSensor(Entity):
def update(self):
"""Update this sensor from the GeoRSS service."""
- _LOGGER.debug("About to update sensor %s", self.entity_id)
- self._data.update()
- # If no events were found due to an error then just set state to zero.
- if self._data.events is None:
- self._state = 0
- else:
- if self._category is None:
- # Add all events regardless of category.
- my_events = self._data.events
- else:
- # Only keep events that belong to sensor's category.
- my_events = [event for event in self._data.events if
- event[ATTR_CATEGORY] == self._category]
+ import georss_client
+ status, feed_entries = self._feed.update()
+ if status == georss_client.UPDATE_OK:
_LOGGER.debug("Adding events to sensor %s: %s", self.entity_id,
- my_events)
- self._state = len(my_events)
+ feed_entries)
+ self._state = len(feed_entries)
# And now compute the attributes from the filtered events.
matrix = {}
- for event in my_events:
- matrix[event[ATTR_TITLE]] = '{:.0f}km'.format(
- event[ATTR_DISTANCE])
+ for entry in feed_entries:
+ matrix[entry.title] = '{:.0f}km'.format(
+ entry.distance_to_home)
self._state_attributes = matrix
-
-
-class GeoRssServiceData:
- """Provide access to GeoRSS feed and stores the latest data."""
-
- def __init__(self, home_latitude, home_longitude, url, radius_in_km):
- """Initialize the update service."""
- self._home_coordinates = [home_latitude, home_longitude]
- self._url = url
- self._radius_in_km = radius_in_km
- self.events = None
-
- @Throttle(MIN_TIME_BETWEEN_UPDATES)
- def update(self):
- """Retrieve data from GeoRSS feed and store events."""
- import feedparser
- feed_data = feedparser.parse(self._url)
- if not feed_data:
- _LOGGER.error("Error fetching feed data from %s", self._url)
+ elif status == georss_client.UPDATE_OK_NO_DATA:
+ _LOGGER.debug("Update successful, but no data received from %s",
+ self._feed)
+ # Don't change the state or state attributes.
else:
- events = self.filter_entries(feed_data)
- self.events = events
-
- def filter_entries(self, feed_data):
- """Filter entries by distance from home coordinates."""
- events = []
- _LOGGER.debug("%s entri(es) available in feed %s",
- len(feed_data.entries), self._url)
- for entry in feed_data.entries:
- geometry = None
- if hasattr(entry, 'where'):
- geometry = entry.where
- elif hasattr(entry, 'geo_lat') and hasattr(entry, 'geo_long'):
- coordinates = (float(entry.geo_long), float(entry.geo_lat))
- point = namedtuple('Point', ['type', 'coordinates'])
- geometry = point('Point', coordinates)
- if geometry:
- distance = self.calculate_distance_to_geometry(geometry)
- if distance <= self._radius_in_km:
- event = {
- ATTR_CATEGORY: None if not hasattr(
- entry, 'category') else entry.category,
- ATTR_TITLE: None if not hasattr(
- entry, 'title') else entry.title,
- ATTR_DISTANCE: distance
- }
- events.append(event)
- _LOGGER.debug("%s events found nearby", len(events))
- return events
-
- def calculate_distance_to_geometry(self, geometry):
- """Calculate the distance between HA and provided geometry."""
- distance = float("inf")
- if geometry.type == 'Point':
- distance = self.calculate_distance_to_point(geometry)
- elif geometry.type == 'Polygon':
- distance = self.calculate_distance_to_polygon(
- geometry.coordinates[0])
- else:
- _LOGGER.warning("Not yet implemented: %s", geometry.type)
- return distance
-
- def calculate_distance_to_point(self, point):
- """Calculate the distance between HA and the provided point."""
- # Swap coordinates to match: (lat, lon).
- coordinates = (point.coordinates[1], point.coordinates[0])
- return self.calculate_distance_to_coords(coordinates)
-
- def calculate_distance_to_coords(self, coordinates):
- """Calculate the distance between HA and the provided coordinates."""
- # Expecting coordinates in format: (lat, lon).
- from haversine import haversine
- distance = haversine(coordinates, self._home_coordinates)
- _LOGGER.debug("Distance from %s to %s: %s km", self._home_coordinates,
- coordinates, distance)
- return distance
-
- def calculate_distance_to_polygon(self, polygon):
- """Calculate the distance between HA and the provided polygon."""
- distance = float("inf")
- # Calculate distance from polygon by calculating the distance
- # to each point of the polygon but not to each edge of the
- # polygon; should be good enough
- for polygon_point in polygon:
- coordinates = (polygon_point[1], polygon_point[0])
- distance = min(distance,
- self.calculate_distance_to_coords(coordinates))
- _LOGGER.debug("Distance from %s to %s: %s km", self._home_coordinates,
- polygon, distance)
- return distance
+ _LOGGER.warning("Update not successful, no data received from %s",
+ self._feed)
+ # If no events were found due to an error then just set state to
+ # zero.
+ self._state = 0
diff --git a/homeassistant/components/sensor/gitlab_ci.py b/homeassistant/components/sensor/gitlab_ci.py
new file mode 100644
index 00000000000..ceb5f75cace
--- /dev/null
+++ b/homeassistant/components/sensor/gitlab_ci.py
@@ -0,0 +1,174 @@
+"""Module for retrieving latest GitLab CI job information."""
+from datetime import timedelta
+import logging
+
+import voluptuous as vol
+
+from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.const import (
+ ATTR_ATTRIBUTION, CONF_NAME, CONF_SCAN_INTERVAL, CONF_TOKEN, CONF_URL)
+import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.entity import Entity
+from homeassistant.util import Throttle
+
+CONF_GITLAB_ID = 'gitlab_id'
+CONF_ATTRIBUTION = "Information provided by https://gitlab.com/"
+
+ICON_HAPPY = 'mdi:emoticon-happy'
+ICON_SAD = 'mdi:emoticon-happy'
+ICON_OTHER = 'mdi:git'
+
+ATTR_BUILD_ID = 'build id'
+ATTR_BUILD_STATUS = 'build_status'
+ATTR_BUILD_STARTED = 'build_started'
+ATTR_BUILD_FINISHED = 'build_finished'
+ATTR_BUILD_DURATION = 'build_duration'
+ATTR_BUILD_COMMIT_ID = 'commit id'
+ATTR_BUILD_COMMIT_DATE = 'commit date'
+ATTR_BUILD_BRANCH = 'build branch'
+
+SCAN_INTERVAL = timedelta(seconds=300)
+
+_LOGGER = logging.getLogger(__name__)
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Required(CONF_TOKEN): cv.string,
+ vol.Required(CONF_GITLAB_ID): cv.string,
+ vol.Optional(CONF_NAME, default='GitLab CI Status'): cv.string,
+ vol.Optional(CONF_URL, default='https://gitlab.com'): cv.string
+})
+
+REQUIREMENTS = ['python-gitlab==1.6.0']
+
+
+def setup_platform(hass, config, add_entities, discovery_info=None):
+ """Sensor platform setup."""
+ _name = config.get(CONF_NAME)
+ _interval = config.get(CONF_SCAN_INTERVAL, SCAN_INTERVAL)
+ _url = config.get(CONF_URL)
+
+ _gitlab_data = GitLabData(
+ priv_token=config[CONF_TOKEN],
+ gitlab_id=config[CONF_GITLAB_ID],
+ interval=_interval,
+ url=_url
+ )
+
+ add_entities([GitLabSensor(_gitlab_data, _name)], True)
+
+
+class GitLabSensor(Entity):
+ """Representation of a Sensor."""
+
+ def __init__(self, gitlab_data, name):
+ """Initialize the sensor."""
+ self._available = False
+ self._state = None
+ self._started_at = None
+ self._finished_at = None
+ self._duration = None
+ self._commit_id = None
+ self._commit_date = None
+ self._build_id = None
+ self._branch = None
+ self._gitlab_data = gitlab_data
+ self._name = name
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @property
+ def state(self):
+ """Return the state of the sensor."""
+ return self._state
+
+ @property
+ def available(self):
+ """Return True if entity is available."""
+ return self._available
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ return {
+ ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
+ ATTR_BUILD_STATUS: self._state,
+ ATTR_BUILD_STARTED: self._started_at,
+ ATTR_BUILD_FINISHED: self._finished_at,
+ ATTR_BUILD_DURATION: self._duration,
+ ATTR_BUILD_COMMIT_ID: self._commit_id,
+ ATTR_BUILD_COMMIT_DATE: self._commit_date,
+ ATTR_BUILD_ID: self._build_id,
+ ATTR_BUILD_BRANCH: self._branch
+ }
+
+ @property
+ def icon(self):
+ """Return the icon to use in the frontend."""
+ if self._state == 'success':
+ return ICON_HAPPY
+ if self._state == 'failed':
+ return ICON_SAD
+ return ICON_OTHER
+
+ def update(self):
+ """Collect updated data from GitLab API."""
+ self._gitlab_data.update()
+
+ self._state = self._gitlab_data.status
+ self._started_at = self._gitlab_data.started_at
+ self._finished_at = self._gitlab_data.finished_at
+ self._duration = self._gitlab_data.duration
+ self._commit_id = self._gitlab_data.commit_id
+ self._commit_date = self._gitlab_data.commit_date
+ self._build_id = self._gitlab_data.build_id
+ self._branch = self._gitlab_data.branch
+ self._available = self._gitlab_data.available
+
+
+class GitLabData():
+ """GitLab Data object."""
+
+ def __init__(self, gitlab_id, priv_token, interval, url):
+ """Fetch data from GitLab API for most recent CI job."""
+ import gitlab
+ self._gitlab_id = gitlab_id
+ self._gitlab = gitlab.Gitlab(
+ url, private_token=priv_token, per_page=1)
+ self._gitlab.auth()
+ self._gitlab_exceptions = gitlab.exceptions
+ self.update = Throttle(interval)(self._update)
+
+ self.available = False
+ self.status = None
+ self.started_at = None
+ self.finished_at = None
+ self.duration = None
+ self.commit_id = None
+ self.commit_date = None
+ self.build_id = None
+ self.branch = None
+
+ def _update(self):
+ try:
+ _projects = self._gitlab.projects.get(self._gitlab_id)
+ _last_pipeline = _projects.pipelines.list(page=1)[0]
+ _last_job = _last_pipeline.jobs.list(page=1)[0]
+ self.status = _last_pipeline.attributes.get('status')
+ self.started_at = _last_job.attributes.get('started_at')
+ self.finished_at = _last_job.attributes.get('finished_at')
+ self.duration = _last_job.attributes.get('duration')
+ _commit = _last_job.attributes.get('commit')
+ self.commit_id = _commit.get('id')
+ self.commit_date = _commit.get('committed_date')
+ self.build_id = _last_job.attributes.get('id')
+ self.branch = _last_job.attributes.get('ref')
+ self.available = True
+ except self._gitlab_exceptions.GitlabAuthenticationError as erra:
+ _LOGGER.error("Authentication Error: %s", erra)
+ self.available = False
+ except self._gitlab_exceptions.GitlabGetError as errg:
+ _LOGGER.error("Project Not Found: %s", errg)
+ self.available = False
diff --git a/homeassistant/components/sensor/google_travel_time.py b/homeassistant/components/sensor/google_travel_time.py
index a69b865f30b..6c197475653 100644
--- a/homeassistant/components/sensor/google_travel_time.py
+++ b/homeassistant/components/sensor/google_travel_time.py
@@ -10,7 +10,7 @@ import logging
import voluptuous as vol
-from homeassistant.components.sensor import PLATFORM_SCHEMA
+from homeassistant.components.sensor import DOMAIN, PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
CONF_API_KEY, CONF_NAME, EVENT_HOMEASSISTANT_START, ATTR_LATITUDE,
@@ -68,6 +68,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
TRACKABLE_DOMAINS = ['device_tracker', 'sensor', 'zone']
+DATA_KEY = 'google_travel_time'
def convert_time_to_utc(timestr):
@@ -90,6 +91,10 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
if options.get('units') is None:
options['units'] = hass.config.units.name
+ if DATA_KEY not in hass.data:
+ hass.data[DATA_KEY] = []
+ hass.services.register(
+ DOMAIN, 'google_travel_sensor_update', update)
travel_mode = config.get(CONF_TRAVEL_MODE)
mode = options.get(CONF_MODE)
@@ -110,10 +115,19 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None):
sensor = GoogleTravelTimeSensor(
hass, name, api_key, origin, destination, options)
+ hass.data[DATA_KEY].append(sensor)
if sensor.valid_api_connection:
add_entities_callback([sensor])
+ def update(service):
+ """Update service for manual updates."""
+ entity_id = service.data.get('entity_id')
+ for sensor in hass.data[DATA_KEY]:
+ if sensor.entity_id == entity_id:
+ sensor.update(no_throttle=True)
+ sensor.schedule_update_ha_state()
+
# Wait until start event is sent to load this component.
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, run_setup)
diff --git a/homeassistant/components/sensor/htu21d.py b/homeassistant/components/sensor/htu21d.py
index 28ab933ff6c..ae2555f57f9 100644
--- a/homeassistant/components/sensor/htu21d.py
+++ b/homeassistant/components/sensor/htu21d.py
@@ -4,7 +4,6 @@ Support for HTU21D temperature and humidity sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.htu21d/
"""
-import asyncio
from datetime import timedelta
from functools import partial
import logging
@@ -40,9 +39,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=import-error
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the HTU21D sensor."""
import smbus
from i2csense.htu21d import HTU21D
@@ -52,14 +50,14 @@ def async_setup_platform(hass, config, async_add_entities,
temp_unit = hass.config.units.temperature_unit
bus = smbus.SMBus(config.get(CONF_I2C_BUS))
- sensor = yield from hass.async_add_job(
+ sensor = await hass.async_add_job(
partial(HTU21D, bus, logger=_LOGGER)
)
if not sensor.sample_ok:
_LOGGER.error("HTU21D sensor not detected in bus %s", bus_number)
return False
- sensor_handler = yield from hass.async_add_job(HTU21DHandler, sensor)
+ sensor_handler = await hass.async_add_job(HTU21DHandler, sensor)
dev = [HTU21DSensor(sensor_handler, name, SENSOR_TEMPERATURE, temp_unit),
HTU21DSensor(sensor_handler, name, SENSOR_HUMIDITY, '%')]
@@ -107,10 +105,9 @@ class HTU21DSensor(Entity):
"""Return the unit of measurement of the sensor."""
return self._unit_of_measurement
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from the HTU21D sensor and update the state."""
- yield from self.hass.async_add_job(self._client.update)
+ await self.hass.async_add_job(self._client.update)
if self._client.sensor.sample_ok:
if self._variable == SENSOR_TEMPERATURE:
value = round(self._client.sensor.temperature, 1)
diff --git a/homeassistant/components/sensor/hydroquebec.py b/homeassistant/components/sensor/hydroquebec.py
index 44b96bab1e9..cb75e69b919 100644
--- a/homeassistant/components/sensor/hydroquebec.py
+++ b/homeassistant/components/sensor/hydroquebec.py
@@ -7,7 +7,6 @@ https://www.hydroquebec.com/portail/en/group/clientele/portrait-de-consommation
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.hydroquebec/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -93,9 +92,8 @@ DAILY_MAP = (('yesterday_total_consumption', 'consoTotalQuot'),
('yesterday_higher_price_consumption', 'consoHautQuot'))
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the HydroQuebec sensor."""
# Create a data fetcher to support all of the configured sensors. Then make
# the first call to init the data.
@@ -107,7 +105,7 @@ def async_setup_platform(hass, config, async_add_entities,
httpsession = hass.helpers.aiohttp_client.async_get_clientsession()
hydroquebec_data = HydroquebecData(username, password, httpsession,
contract)
- contracts = yield from hydroquebec_data.get_contract_list()
+ contracts = await hydroquebec_data.get_contract_list()
if not contracts:
return
_LOGGER.info("Contract list: %s",
@@ -155,10 +153,9 @@ class HydroQuebecSensor(Entity):
"""Icon to use in the frontend, if any."""
return self._icon
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from Hydroquebec and update the state."""
- yield from self.hydroquebec_data.async_update()
+ await self.hydroquebec_data.async_update()
if self.hydroquebec_data.data.get(self.type) is not None:
self._state = round(self.hydroquebec_data.data[self.type], 2)
@@ -174,11 +171,10 @@ class HydroquebecData:
self._contract = contract
self.data = {}
- @asyncio.coroutine
- def get_contract_list(self):
+ async def get_contract_list(self):
"""Return the contract list."""
# Fetch data
- ret = yield from self._fetch_data()
+ ret = await self._fetch_data()
if ret:
return self.client.get_contracts()
return []
@@ -194,8 +190,7 @@ class HydroquebecData:
return False
return True
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Return the latest collected data from HydroQuebec."""
- yield from self._fetch_data()
+ await self._fetch_data()
self.data = self.client.get_data(self._contract)[self._contract]
diff --git a/homeassistant/components/sensor/insteon.py b/homeassistant/components/sensor/insteon.py
index 5b8a6b9a977..7854967395b 100644
--- a/homeassistant/components/sensor/insteon.py
+++ b/homeassistant/components/sensor/insteon.py
@@ -4,7 +4,6 @@ Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sensor.insteon/
"""
-import asyncio
import logging
from homeassistant.components.insteon import InsteonEntity
@@ -15,9 +14,8 @@ DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data['insteon'].get('modem')
diff --git a/homeassistant/components/sensor/jewish_calendar.py b/homeassistant/components/sensor/jewish_calendar.py
index e5838fa8543..1de45d6145e 100644
--- a/homeassistant/components/sensor/jewish_calendar.py
+++ b/homeassistant/components/sensor/jewish_calendar.py
@@ -64,7 +64,8 @@ async def async_setup_platform(
dev = []
for sensor_type in config[CONF_SENSORS]:
dev.append(JewishCalSensor(
- name, language, sensor_type, latitude, longitude, diaspora))
+ name, language, sensor_type, latitude, longitude,
+ hass.config.time_zone, diaspora))
async_add_entities(dev, True)
@@ -72,7 +73,8 @@ class JewishCalSensor(Entity):
"""Representation of an Jewish calendar sensor."""
def __init__(
- self, name, language, sensor_type, latitude, longitude, diaspora):
+ self, name, language, sensor_type, latitude, longitude, timezone,
+ diaspora):
"""Initialize the Jewish calendar sensor."""
self.client_name = name
self._name = SENSOR_TYPES[sensor_type][0]
@@ -81,6 +83,7 @@ class JewishCalSensor(Entity):
self._state = None
self.latitude = latitude
self.longitude = longitude
+ self.timezone = timezone
self.diaspora = diaspora
_LOGGER.debug("Sensor %s initialized", self.type)
@@ -116,10 +119,14 @@ class JewishCalSensor(Entity):
date.get_reading(self.diaspora), hebrew=self._hebrew)
elif self.type == 'holiday_name':
try:
- self._state = next(
- x.description[self._hebrew].long
+ description = next(
+ x.description[self._hebrew]
for x in hdate.htables.HOLIDAYS
if x.index == date.get_holyday())
+ if not self._hebrew:
+ self._state = description
+ else:
+ self._state = description.long
except StopIteration:
self._state = None
elif self.type == 'holyness':
@@ -127,7 +134,7 @@ class JewishCalSensor(Entity):
else:
times = hdate.Zmanim(
date=today, latitude=self.latitude, longitude=self.longitude,
- hebrew=self._hebrew).zmanim
+ timezone=self.timezone, hebrew=self._hebrew).zmanim
self._state = times[self.type].time()
_LOGGER.debug("New value: %s", self._state)
diff --git a/homeassistant/components/sensor/miflora.py b/homeassistant/components/sensor/miflora.py
index 6f0fb3aba30..74bb8261609 100644
--- a/homeassistant/components/sensor/miflora.py
+++ b/homeassistant/components/sensor/miflora.py
@@ -4,20 +4,17 @@ Support for Xiaomi Mi Flora BLE plant sensor.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.miflora/
"""
-import asyncio
from datetime import timedelta
import logging
import voluptuous as vol
-import async_timeout
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
-from homeassistant.exceptions import PlatformNotReady
from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_MAC,
- CONF_SCAN_INTERVAL)
-
+ CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_START)
+from homeassistant.core import callback
REQUIREMENTS = ['miflora==0.4.0']
@@ -75,13 +72,6 @@ async def async_setup_platform(hass, config, async_add_entities,
devs = []
- try:
- with async_timeout.timeout(9):
- await hass.async_add_executor_job(poller.fill_cache)
- except asyncio.TimeoutError:
- _LOGGER.error('Unable to connect to %s', config.get(CONF_MAC))
- raise PlatformNotReady
-
for parameter in config[CONF_MONITORED_CONDITIONS]:
name = SENSOR_TYPES[parameter][0]
unit = SENSOR_TYPES[parameter][1]
@@ -93,7 +83,7 @@ async def async_setup_platform(hass, config, async_add_entities,
devs.append(MiFloraSensor(
poller, parameter, name, unit, force_update, median))
- async_add_entities(devs, update_before_add=True)
+ async_add_entities(devs)
class MiFloraSensor(Entity):
@@ -113,6 +103,14 @@ class MiFloraSensor(Entity):
# Use median_count = 1 if no filtering is required.
self.median_count = median
+ async def async_added_to_hass(self):
+ """Set initial state."""
+ @callback
+ def on_startup(_):
+ self.async_schedule_update_ha_state(True)
+
+ self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, on_startup)
+
@property
def name(self):
"""Return the name of the sensor."""
diff --git a/homeassistant/components/sensor/min_max.py b/homeassistant/components/sensor/min_max.py
index 7956dd97b5e..7d9e91a1bf1 100644
--- a/homeassistant/components/sensor/min_max.py
+++ b/homeassistant/components/sensor/min_max.py
@@ -4,7 +4,6 @@ Support for displaying the minimal and the maximal value.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.min_max/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -54,9 +53,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the min/max/mean sensor."""
entity_ids = config.get(CONF_ENTITY_IDS)
name = config.get(CONF_NAME)
@@ -194,8 +192,7 @@ class MinMaxSensor(Entity):
"""Return the icon to use in the frontend, if any."""
return ICON
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data and updates the states."""
sensor_values = [self.states[k] for k in self._entity_ids
if k in self.states]
diff --git a/homeassistant/components/sensor/mqtt.py b/homeassistant/components/sensor/mqtt.py
index 6cf2d55755d..fe0b77b2024 100644
--- a/homeassistant/components/sensor/mqtt.py
+++ b/homeassistant/components/sensor/mqtt.py
@@ -12,9 +12,12 @@ from typing import Optional
import voluptuous as vol
from homeassistant.core import callback
+from homeassistant.components import sensor
from homeassistant.components.mqtt import (
- CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_PAYLOAD_AVAILABLE,
- CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
+ ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
+ CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
+ MqttAvailability, MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
from homeassistant.components.sensor import DEVICE_CLASSES_SCHEMA
from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, STATE_UNKNOWN,
@@ -23,6 +26,7 @@ from homeassistant.helpers.entity import Entity
from homeassistant.components import mqtt
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util import dt as dt_util
@@ -52,10 +56,26 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
async_add_entities, discovery_info=None):
- """Set up MQTT Sensor."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+ """Set up MQTT sensors through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT sensors dynamically through MQTT discovery."""
+ async def async_discover_sensor(discovery_payload):
+ """Discover and add a discovered MQTT sensor."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(hass,
+ MQTT_DISCOVERY_NEW.format(sensor.DOMAIN, 'mqtt'),
+ async_discover_sensor)
+
+
+async def _async_setup_entity(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_hash=None):
+ """Set up MQTT sensor."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
@@ -75,20 +95,22 @@ async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE),
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
+ discovery_hash,
)])
-class MqttSensor(MqttAvailability, Entity):
+class MqttSensor(MqttAvailability, MqttDiscoveryUpdate, Entity):
"""Representation of a sensor that can be updated using MQTT."""
def __init__(self, name, state_topic, qos, unit_of_measurement,
force_update, expire_after, icon, device_class: Optional[str],
value_template, json_attributes, unique_id: Optional[str],
- availability_topic, payload_available,
- payload_not_available):
+ availability_topic, payload_available, payload_not_available,
+ discovery_hash):
"""Initialize the sensor."""
- super().__init__(availability_topic, qos, payload_available,
- payload_not_available)
+ MqttAvailability.__init__(self, availability_topic, qos,
+ payload_available, payload_not_available)
+ MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._state = STATE_UNKNOWN
self._name = name
self._state_topic = state_topic
@@ -103,10 +125,12 @@ class MqttSensor(MqttAvailability, Entity):
self._json_attributes = set(json_attributes)
self._unique_id = unique_id
self._attributes = None
+ self._discovery_hash = discovery_hash
async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
- await super().async_added_to_hass()
+ await MqttAvailability.async_added_to_hass(self)
+ await MqttDiscoveryUpdate.async_added_to_hass(self)
@callback
def message_received(topic, payload, qos):
diff --git a/homeassistant/components/sensor/mqtt_room.py b/homeassistant/components/sensor/mqtt_room.py
index e12e8e033ac..39c202ef01c 100644
--- a/homeassistant/components/sensor/mqtt_room.py
+++ b/homeassistant/components/sensor/mqtt_room.py
@@ -4,7 +4,6 @@ Support for MQTT room presence detection.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mqtt_room/
"""
-import asyncio
import logging
import json
from datetime import timedelta
@@ -52,9 +51,8 @@ MQTT_PAYLOAD = vol.Schema(vol.All(json.loads, vol.Schema({
}, extra=vol.ALLOW_EXTRA)))
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up MQTT room Sensor."""
async_add_entities([MQTTRoomSensor(
config.get(CONF_NAME),
@@ -81,8 +79,7 @@ class MQTTRoomSensor(Entity):
self._distance = None
self._updated = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe to MQTT events."""
@callback
def update_state(device_id, room, distance):
@@ -118,7 +115,7 @@ class MQTTRoomSensor(Entity):
or timediff.seconds >= self._timeout:
update_state(**device)
- return mqtt.async_subscribe(
+ return await mqtt.async_subscribe(
self.hass, self._state_topic, message_received, 1)
@property
diff --git a/homeassistant/components/sensor/mychevy.py b/homeassistant/components/sensor/mychevy.py
index fa3343d7791..06d4dade6aa 100644
--- a/homeassistant/components/sensor/mychevy.py
+++ b/homeassistant/components/sensor/mychevy.py
@@ -4,7 +4,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mychevy/
"""
-import asyncio
import logging
from homeassistant.components.mychevy import (
@@ -55,8 +54,7 @@ class MyChevyStatus(Entity):
"""Initialize sensor with car connection."""
self._state = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.success)
@@ -129,8 +127,7 @@ class EVSensor(Entity):
slugify(self._car.name),
slugify(self._name)))
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.async_update_callback)
diff --git a/homeassistant/components/sensor/netatmo_public.py b/homeassistant/components/sensor/netatmo_public.py
index d1c6e03d1b0..7a500b66183 100644
--- a/homeassistant/components/sensor/netatmo_public.py
+++ b/homeassistant/components/sensor/netatmo_public.py
@@ -10,7 +10,9 @@ import logging
import voluptuous as vol
from homeassistant.components.sensor import PLATFORM_SCHEMA
-from homeassistant.const import (CONF_NAME, CONF_TYPE)
+from homeassistant.const import (
+ CONF_NAME, CONF_MODE, CONF_MONITORED_CONDITIONS, TEMP_CELSIUS,
+ DEVICE_CLASS_TEMPERATURE, DEVICE_CLASS_HUMIDITY)
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity import Entity
from homeassistant.util import Throttle
@@ -26,13 +28,22 @@ CONF_LAT_SW = 'lat_sw'
CONF_LON_SW = 'lon_sw'
DEFAULT_NAME = 'Netatmo Public Data'
-DEFAULT_TYPE = 'max'
-SENSOR_TYPES = {'max', 'avg'}
+DEFAULT_MODE = 'avg'
+MODE_TYPES = {'max', 'avg'}
+
+SENSOR_TYPES = {
+ 'temperature': ['Temperature', TEMP_CELSIUS, 'mdi:thermometer',
+ DEVICE_CLASS_TEMPERATURE],
+ 'pressure': ['Pressure', 'mbar', 'mdi:gauge', None],
+ 'humidity': ['Humidity', '%', 'mdi:water-percent', DEVICE_CLASS_HUMIDITY],
+ 'rain': ['Rain', 'mm', 'mdi:weather-rainy', None],
+ 'windstrength': ['Wind Strength', 'km/h', 'mdi:weather-windy', None],
+ 'guststrength': ['Gust Strength', 'km/h', 'mdi:weather-windy', None],
+}
# NetAtmo Data is uploaded to server every 10 minutes
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=600)
-
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_AREAS): vol.All(cv.ensure_list, [
{
@@ -40,9 +51,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_LAT_SW): cv.latitude,
vol.Required(CONF_LON_NE): cv.longitude,
vol.Required(CONF_LON_SW): cv.longitude,
- vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
- vol.Optional(CONF_TYPE, default=DEFAULT_TYPE):
- vol.In(SENSOR_TYPES)
+ vol.Required(CONF_MONITORED_CONDITIONS): [vol.In(SENSOR_TYPES)],
+ vol.Optional(CONF_MODE, default=DEFAULT_MODE): vol.In(MODE_TYPES),
+ vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string
}
]),
})
@@ -59,20 +70,29 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
lat_ne=area_conf.get(CONF_LAT_NE),
lon_ne=area_conf.get(CONF_LON_NE),
lat_sw=area_conf.get(CONF_LAT_SW),
- lon_sw=area_conf.get(CONF_LON_SW),
- calculation=area_conf.get(CONF_TYPE))
- sensors.append(NetatmoPublicSensor(area_conf.get(CONF_NAME), data))
- add_entities(sensors)
+ lon_sw=area_conf.get(CONF_LON_SW))
+ for sensor_type in area_conf.get(CONF_MONITORED_CONDITIONS):
+ sensors.append(NetatmoPublicSensor(area_conf.get(CONF_NAME),
+ data, sensor_type,
+ area_conf.get(CONF_MODE)))
+ add_entities(sensors, True)
class NetatmoPublicSensor(Entity):
"""Represent a single sensor in a Netatmo."""
- def __init__(self, name, data):
+ def __init__(self, area_name, data, sensor_type, mode):
"""Initialize the sensor."""
self.netatmo_data = data
- self._name = name
+ self.type = sensor_type
+ self._mode = mode
+ self._name = '{} {}'.format(area_name,
+ SENSOR_TYPES[self.type][0])
+ self._area_name = area_name
self._state = None
+ self._device_class = SENSOR_TYPES[self.type][3]
+ self._icon = SENSOR_TYPES[self.type][2]
+ self._unit_of_measurement = SENSOR_TYPES[self.type][1]
@property
def name(self):
@@ -82,33 +102,63 @@ class NetatmoPublicSensor(Entity):
@property
def icon(self):
"""Icon to use in the frontend."""
- return 'mdi:weather-rainy'
+ return self._icon
@property
def device_class(self):
"""Return the device class of the sensor."""
- return None
+ return self._device_class
@property
def state(self):
- """Return true if binary sensor is on."""
+ """Return the state of the device."""
return self._state
@property
def unit_of_measurement(self):
"""Return the unit of measurement of this entity."""
- return 'mm'
+ return self._unit_of_measurement
def update(self):
"""Get the latest data from NetAtmo API and updates the states."""
self.netatmo_data.update()
- self._state = self.netatmo_data.data
+
+ if self.netatmo_data.data is None:
+ _LOGGER.warning("No data found for %s", self._name)
+ self._state = None
+ return
+
+ data = None
+
+ if self.type == 'temperature':
+ data = self.netatmo_data.data.getLatestTemperatures()
+ elif self.type == 'pressure':
+ data = self.netatmo_data.data.getLatestPressures()
+ elif self.type == 'humidity':
+ data = self.netatmo_data.data.getLatestHumidities()
+ elif self.type == 'rain':
+ data = self.netatmo_data.data.getLatestRain()
+ elif self.type == 'windstrength':
+ data = self.netatmo_data.data.getLatestWindStrengths()
+ elif self.type == 'guststrength':
+ data = self.netatmo_data.data.getLatestGustStrengths()
+
+ if not data:
+ _LOGGER.warning("No station provides %s data in the area %s",
+ self.type, self._area_name)
+ self._state = None
+ return
+
+ if self._mode == 'avg':
+ self._state = round(sum(data.values()) / len(data), 1)
+ elif self._mode == 'max':
+ self._state = max(data.values())
class NetatmoPublicData:
"""Get the latest data from NetAtmo."""
- def __init__(self, auth, lat_ne, lon_ne, lat_sw, lon_sw, calculation):
+ def __init__(self, auth, lat_ne, lon_ne, lat_sw, lon_sw):
"""Initialize the data object."""
self.auth = auth
self.data = None
@@ -116,26 +166,20 @@ class NetatmoPublicData:
self.lon_ne = lon_ne
self.lat_sw = lat_sw
self.lon_sw = lon_sw
- self.calculation = calculation
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def update(self):
"""Request an update from the Netatmo API."""
import pyatmo
- raindata = pyatmo.PublicData(self.auth,
- LAT_NE=self.lat_ne,
- LON_NE=self.lon_ne,
- LAT_SW=self.lat_sw,
- LON_SW=self.lon_sw,
- required_data_type="rain")
+ data = pyatmo.PublicData(self.auth,
+ LAT_NE=self.lat_ne,
+ LON_NE=self.lon_ne,
+ LAT_SW=self.lat_sw,
+ LON_SW=self.lon_sw,
+ filtering=True)
- if raindata.CountStationInArea() == 0:
- _LOGGER.warning('No Rain Station available in this area.')
+ if data.CountStationInArea() == 0:
+ _LOGGER.warning('No Stations available in this area.')
return
- raindata_live = raindata.getLive()
-
- if self.calculation == 'avg':
- self.data = sum(raindata_live.values()) / len(raindata_live)
- else:
- self.data = max(raindata_live.values())
+ self.data = data
diff --git a/homeassistant/components/sensor/openweathermap.py b/homeassistant/components/sensor/openweathermap.py
index 2dbbb581741..08426ed3eb8 100644
--- a/homeassistant/components/sensor/openweathermap.py
+++ b/homeassistant/components/sensor/openweathermap.py
@@ -39,6 +39,7 @@ SENSOR_TYPES = {
'clouds': ['Cloud coverage', '%'],
'rain': ['Rain', 'mm'],
'snow': ['Snow', 'mm'],
+ 'weather_code': ['Weather code', None],
}
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@@ -177,6 +178,8 @@ class OpenWeatherMapSensor(Entity):
if fc_data is None:
return
self._state = fc_data.get_weathers()[0].get_detailed_status()
+ elif self.type == 'weather_code':
+ self._state = data.get_weather_code()
class WeatherData:
diff --git a/homeassistant/components/sensor/otp.py b/homeassistant/components/sensor/otp.py
index 2e3a13928e1..5394b49c389 100644
--- a/homeassistant/components/sensor/otp.py
+++ b/homeassistant/components/sensor/otp.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.otp/
"""
import time
-import asyncio
import logging
import voluptuous as vol
@@ -32,9 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the OTP sensor."""
name = config.get(CONF_NAME)
token = config.get(CONF_TOKEN)
@@ -55,8 +53,7 @@ class TOTPSensor(Entity):
self._state = None
self._next_expiration = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle when an entity is about to be added to Home Assistant."""
self._call_loop()
diff --git a/homeassistant/components/sensor/rflink.py b/homeassistant/components/sensor/rflink.py
index 3952c815dca..e01c441be84 100644
--- a/homeassistant/components/sensor/rflink.py
+++ b/homeassistant/components/sensor/rflink.py
@@ -4,7 +4,6 @@ Support for Rflink sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.rflink/
"""
-import asyncio
from functools import partial
import logging
@@ -74,14 +73,12 @@ def devices_from_config(domain_config, hass=None):
return devices
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Rflink platform."""
async_add_entities(devices_from_config(config, hass))
- @asyncio.coroutine
- def add_new_device(event):
+ async def add_new_device(event):
"""Check if device is known, otherwise create device entity."""
device_id = event[EVENT_KEY_ID]
diff --git a/homeassistant/components/sensor/scrape.py b/homeassistant/components/sensor/scrape.py
index 9a43c3ff295..0174407c7c3 100644
--- a/homeassistant/components/sensor/scrape.py
+++ b/homeassistant/components/sensor/scrape.py
@@ -13,7 +13,7 @@ from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.components.sensor.rest import RestData
from homeassistant.const import (
CONF_NAME, CONF_RESOURCE, CONF_UNIT_OF_MEASUREMENT, STATE_UNKNOWN,
- CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL, CONF_USERNAME,
+ CONF_VALUE_TEMPLATE, CONF_VERIFY_SSL, CONF_USERNAME, CONF_HEADERS,
CONF_PASSWORD, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION,
HTTP_DIGEST_AUTHENTICATION)
from homeassistant.helpers.entity import Entity
@@ -35,6 +35,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_ATTR): cv.string,
vol.Optional(CONF_AUTHENTICATION):
vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]),
+ vol.Optional(CONF_HEADERS): vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_PASSWORD): cv.string,
vol.Optional(CONF_UNIT_OF_MEASUREMENT): cv.string,
@@ -49,7 +50,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
name = config.get(CONF_NAME)
resource = config.get(CONF_RESOURCE)
method = 'GET'
- payload = headers = None
+ payload = None
+ headers = config.get(CONF_HEADERS)
verify_ssl = config.get(CONF_VERIFY_SSL)
select = config.get(CONF_SELECT)
attr = config.get(CONF_ATTR)
diff --git a/homeassistant/components/sensor/serial.py b/homeassistant/components/sensor/serial.py
index 39b69e0a8c4..5d49b065558 100644
--- a/homeassistant/components/sensor/serial.py
+++ b/homeassistant/components/sensor/serial.py
@@ -4,7 +4,6 @@ Support for reading data from a serial port.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.serial/
"""
-import asyncio
import logging
import json
@@ -35,9 +34,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Serial sensor platform."""
name = config.get(CONF_NAME)
port = config.get(CONF_SERIAL_PORT)
@@ -67,20 +65,18 @@ class SerialSensor(Entity):
self._template = value_template
self._attributes = []
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle when an entity is about to be added to Home Assistant."""
self._serial_loop_task = self.hass.loop.create_task(
self.serial_read(self._port, self._baudrate))
- @asyncio.coroutine
- def serial_read(self, device, rate, **kwargs):
+ async def serial_read(self, device, rate, **kwargs):
"""Read the data from the port."""
import serial_asyncio
- reader, _ = yield from serial_asyncio.open_serial_connection(
+ reader, _ = await serial_asyncio.open_serial_connection(
url=device, baudrate=rate, **kwargs)
while True:
- line = yield from reader.readline()
+ line = await reader.readline()
line = line.decode('utf-8').strip()
try:
@@ -98,8 +94,7 @@ class SerialSensor(Entity):
self._state = line
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def stop_serial_read(self):
+ async def stop_serial_read(self):
"""Close resources."""
if self._serial_loop_task:
self._serial_loop_task.cancel()
diff --git a/homeassistant/components/sensor/sma.py b/homeassistant/components/sensor/sma.py
index 945c3873bb6..dd5209a4e0a 100644
--- a/homeassistant/components/sensor/sma.py
+++ b/homeassistant/components/sensor/sma.py
@@ -63,9 +63,8 @@ PLATFORM_SCHEMA = vol.All(PLATFORM_SCHEMA.extend({
}, extra=vol.PREVENT_EXTRA), _check_sensor_schema)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up SMA WebConnect sensor."""
import pysma
@@ -107,10 +106,9 @@ def async_setup_platform(hass, config, async_add_entities,
sma = pysma.SMA(session, url, config[CONF_PASSWORD], group=grp)
# Ensure we logout on shutdown
- @asyncio.coroutine
- def async_close_session(event):
+ async def async_close_session(event):
"""Close the session."""
- yield from sma.close_session()
+ await sma.close_session()
hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, async_close_session)
@@ -120,15 +118,14 @@ def async_setup_platform(hass, config, async_add_entities,
backoff = 0
- @asyncio.coroutine
- def async_sma(event):
+ async def async_sma(event):
"""Update all the SMA sensors."""
nonlocal backoff
if backoff > 1:
backoff -= 1
return
- values = yield from sma.read(keys_to_query)
+ values = await sma.read(keys_to_query)
if values is None:
backoff = 3
return
@@ -142,7 +139,7 @@ def async_setup_platform(hass, config, async_add_entities,
if task:
tasks.append(task)
if tasks:
- yield from asyncio.wait(tasks, loop=hass.loop)
+ await asyncio.wait(tasks, loop=hass.loop)
interval = config.get(CONF_SCAN_INTERVAL) or timedelta(seconds=5)
async_track_time_interval(hass, async_sma, interval)
diff --git a/homeassistant/components/sensor/sochain.py b/homeassistant/components/sensor/sochain.py
index 9f8982f871f..b582ba04567 100644
--- a/homeassistant/components/sensor/sochain.py
+++ b/homeassistant/components/sensor/sochain.py
@@ -4,7 +4,6 @@ Support for watching multiple cryptocurrencies.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.sochain/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -35,9 +34,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the sochain sensors."""
from pysochain import ChainSo
address = config.get(CONF_ADDRESS)
@@ -82,7 +80,6 @@ class SochainSensor(Entity):
ATTR_ATTRIBUTION: CONF_ATTRIBUTION,
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest state of the sensor."""
- yield from self.chainso.async_get_data()
+ await self.chainso.async_get_data()
diff --git a/homeassistant/components/sensor/speedtest.py b/homeassistant/components/sensor/speedtest.py
index 8da7374f231..ee6cad61e20 100644
--- a/homeassistant/components/sensor/speedtest.py
+++ b/homeassistant/components/sensor/speedtest.py
@@ -4,7 +4,6 @@ Support for Speedtest.net.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.speedtest/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -139,10 +138,9 @@ class SpeedtestSensor(Entity):
elif self.type == 'upload':
self._state = round(self._data['upload'] / 10**6, 2)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Handle all entity which are about to be added."""
- state = yield from async_get_last_state(self.hass, self.entity_id)
+ state = await async_get_last_state(self.hass, self.entity_id)
if not state:
return
self._state = state.state
diff --git a/homeassistant/components/sensor/startca.py b/homeassistant/components/sensor/startca.py
index d9a52e4aa23..85939ea72ae 100644
--- a/homeassistant/components/sensor/startca.py
+++ b/homeassistant/components/sensor/startca.py
@@ -7,7 +7,6 @@ https://home-assistant.io/components/sensor.startca/
from datetime import timedelta
from xml.parsers.expat import ExpatError
import logging
-import asyncio
import async_timeout
import voluptuous as vol
@@ -57,16 +56,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the sensor platform."""
websession = async_get_clientsession(hass)
apikey = config.get(CONF_API_KEY)
bandwidthcap = config.get(CONF_TOTAL_BANDWIDTH)
ts_data = StartcaData(hass.loop, websession, apikey, bandwidthcap)
- ret = yield from ts_data.async_update()
+ ret = await ts_data.async_update()
if ret is False:
_LOGGER.error("Invalid Start.ca API key: %s", apikey)
return
@@ -111,10 +109,9 @@ class StartcaSensor(Entity):
"""Icon to use in the frontend, if any."""
return self._icon
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from Start.ca and update the state."""
- yield from self.startcadata.async_update()
+ await self.startcadata.async_update()
if self.type in self.startcadata.data:
self._state = round(self.startcadata.data[self.type], 2)
diff --git a/homeassistant/components/sensor/statistics.py b/homeassistant/components/sensor/statistics.py
index e7692001ffa..453acb94b11 100644
--- a/homeassistant/components/sensor/statistics.py
+++ b/homeassistant/components/sensor/statistics.py
@@ -4,7 +4,6 @@ Support for statistics for sensor values.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.statistics/
"""
-import asyncio
import logging
import statistics
from collections import deque
@@ -58,9 +57,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Statistics sensor."""
entity_id = config.get(CONF_ENTITY_ID)
name = config.get(CONF_NAME)
@@ -179,8 +177,7 @@ class StatisticsSensor(Entity):
self.ages.popleft()
self.states.popleft()
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data and updates the states."""
if self._max_age is not None:
self._purge_old()
@@ -236,8 +233,7 @@ class StatisticsSensor(Entity):
self.change = self.average_change = STATE_UNKNOWN
self.change_rate = STATE_UNKNOWN
- @asyncio.coroutine
- def _initialize_from_database(self):
+ async def _initialize_from_database(self):
"""Initialize the list of states from the database.
The query will get the list of states in DESCENDING order so that we
diff --git a/homeassistant/components/sensor/teksavvy.py b/homeassistant/components/sensor/teksavvy.py
index 87b89074cfb..0be18cbd6b6 100644
--- a/homeassistant/components/sensor/teksavvy.py
+++ b/homeassistant/components/sensor/teksavvy.py
@@ -6,7 +6,6 @@ https://home-assistant.io/components/sensor.teksavvy/
"""
from datetime import timedelta
import logging
-import asyncio
import async_timeout
import voluptuous as vol
@@ -58,16 +57,15 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the sensor platform."""
websession = async_get_clientsession(hass)
apikey = config.get(CONF_API_KEY)
bandwidthcap = config.get(CONF_TOTAL_BANDWIDTH)
ts_data = TekSavvyData(hass.loop, websession, apikey, bandwidthcap)
- ret = yield from ts_data.async_update()
+ ret = await ts_data.async_update()
if ret is False:
_LOGGER.error("Invalid Teksavvy API key: %s", apikey)
return
@@ -112,10 +110,9 @@ class TekSavvySensor(Entity):
"""Icon to use in the frontend, if any."""
return self._icon
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data from TekSavvy and update the state."""
- yield from self.teksavvydata.async_update()
+ await self.teksavvydata.async_update()
if self.type in self.teksavvydata.data:
self._state = round(self.teksavvydata.data[self.type], 2)
diff --git a/homeassistant/components/sensor/template.py b/homeassistant/components/sensor/template.py
index f64e8b122ca..77b3759d5fc 100644
--- a/homeassistant/components/sensor/template.py
+++ b/homeassistant/components/sensor/template.py
@@ -4,7 +4,6 @@ Allows the creation of a sensor that breaks out state_attributes.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.template/
"""
-import asyncio
import logging
from typing import Optional
@@ -41,9 +40,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the template sensors."""
sensors = []
@@ -123,8 +121,7 @@ class SensorTemplate(Entity):
self._entities = entity_ids
self._device_class = device_class
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def template_sensor_state_listener(entity, old_state, new_state):
@@ -177,8 +174,7 @@ class SensorTemplate(Entity):
"""No polling needed."""
return False
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update the state from the template."""
try:
self._state = self._template.async_render()
diff --git a/homeassistant/components/sensor/thethingsnetwork.py b/homeassistant/components/sensor/thethingsnetwork.py
index 02661f2211d..0f1220f9b07 100644
--- a/homeassistant/components/sensor/thethingsnetwork.py
+++ b/homeassistant/components/sensor/thethingsnetwork.py
@@ -38,9 +38,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up The Things Network Data storage sensors."""
ttn = hass.data.get(DATA_TTN)
device_id = config.get(CONF_DEVICE_ID)
@@ -50,7 +49,7 @@ def async_setup_platform(hass, config, async_add_entities,
ttn_data_storage = TtnDataStorage(
hass, app_id, device_id, access_key, values)
- success = yield from ttn_data_storage.async_update()
+ success = await ttn_data_storage.async_update()
if not success:
return False
@@ -104,10 +103,9 @@ class TtnDataSensor(Entity):
ATTR_TIME: self._state['time'],
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the current state."""
- yield from self._ttn_data_storage.async_update()
+ await self._ttn_data_storage.async_update()
self._state = self._ttn_data_storage.data
@@ -128,13 +126,12 @@ class TtnDataStorage:
AUTHORIZATION: 'key {}'.format(access_key),
}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the current state from The Things Network Data Storage."""
try:
session = async_get_clientsession(self._hass)
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=self._hass.loop):
- req = yield from session.get(self._url, headers=self._headers)
+ req = await session.get(self._url, headers=self._headers)
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Error while accessing: %s", self._url)
@@ -155,7 +152,7 @@ class TtnDataStorage:
_LOGGER.error("Application ID is not available: %s", self._app_id)
return False
- data = yield from req.json()
+ data = await req.json()
self.data = data[0]
for value in self._values.items():
diff --git a/homeassistant/components/sensor/tibber.py b/homeassistant/components/sensor/tibber.py
index dbea54ff353..1207c8dfe20 100644
--- a/homeassistant/components/sensor/tibber.py
+++ b/homeassistant/components/sensor/tibber.py
@@ -10,26 +10,17 @@ import logging
from datetime import timedelta
import aiohttp
-import voluptuous as vol
-import homeassistant.helpers.config_validation as cv
-from homeassistant.components.sensor import PLATFORM_SCHEMA
-from homeassistant.const import CONF_ACCESS_TOKEN
+from homeassistant.components.tibber import DOMAIN as TIBBER_DOMAIN
from homeassistant.exceptions import PlatformNotReady
-from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import Entity
from homeassistant.util import dt as dt_util
from homeassistant.util import Throttle
-REQUIREMENTS = ['pyTibber==0.5.1']
-
_LOGGER = logging.getLogger(__name__)
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Required(CONF_ACCESS_TOKEN): cv.string
-})
-
ICON = 'mdi:currency-usd'
+ICON_RT = 'mdi:power-plug'
SCAN_INTERVAL = timedelta(minutes=1)
MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
@@ -37,24 +28,28 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5)
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
"""Set up the Tibber sensor."""
- import tibber
- tibber_connection = tibber.Tibber(config[CONF_ACCESS_TOKEN],
- websession=async_get_clientsession(hass))
+ if discovery_info is None:
+ _LOGGER.error("Tibber sensor configuration has changed."
+ " Check https://home-assistant.io/components/tibber/")
+ return
+
+ tibber_connection = hass.data.get(TIBBER_DOMAIN)
try:
- await tibber_connection.update_info()
dev = []
for home in tibber_connection.get_homes():
await home.update_info()
- dev.append(TibberSensor(home))
+ dev.append(TibberSensorElPrice(home))
+ if home.has_real_time_consumption:
+ dev.append(TibberSensorRT(home))
except (asyncio.TimeoutError, aiohttp.ClientError):
raise PlatformNotReady()
async_add_entities(dev, True)
-class TibberSensor(Entity):
- """Representation of an Tibber sensor."""
+class TibberSensorElPrice(Entity):
+ """Representation of an Tibber sensor for el price."""
def __init__(self, tibber_home):
"""Initialize the sensor."""
@@ -155,3 +150,71 @@ class TibberSensor(Entity):
self._device_state_attributes['max_price'] = max_price
self._device_state_attributes['min_price'] = min_price
return state is not None
+
+
+class TibberSensorRT(Entity):
+ """Representation of an Tibber sensor for real time consumption."""
+
+ def __init__(self, tibber_home):
+ """Initialize the sensor."""
+ self._tibber_home = tibber_home
+ self._state = None
+ self._device_state_attributes = {}
+ self._unit_of_measurement = 'W'
+ nickname = tibber_home.info['viewer']['home']['appNickname']
+ self._name = 'Real time consumption {}'.format(nickname)
+
+ async def async_added_to_hass(self):
+ """Start unavailability tracking."""
+ await self._tibber_home.rt_subscribe(self.hass.loop,
+ self._async_callback)
+
+ async def _async_callback(self, payload):
+ """Handle received data."""
+ data = payload.get('data', {})
+ live_measurement = data.get('liveMeasurement', {})
+ self._state = live_measurement.pop('power', None)
+ self._device_state_attributes = live_measurement
+ self.async_schedule_update_ha_state()
+
+ @property
+ def device_state_attributes(self):
+ """Return the state attributes."""
+ return self._device_state_attributes
+
+ @property
+ def available(self):
+ """Return True if entity is available."""
+ return self._tibber_home.rt_subscription_running
+
+ @property
+ def name(self):
+ """Return the name of the sensor."""
+ return self._name
+
+ @property
+ def should_poll(self):
+ """Return the polling state."""
+ return False
+
+ @property
+ def state(self):
+ """Return the state of the device."""
+ return self._state
+
+ @property
+ def icon(self):
+ """Return the icon to use in the frontend."""
+ return ICON_RT
+
+ @property
+ def unit_of_measurement(self):
+ """Return the unit of measurement of this entity."""
+ return self._unit_of_measurement
+
+ @property
+ def unique_id(self):
+ """Return a unique ID."""
+ home = self._tibber_home.info['viewer']['home']
+ _id = home['meteringPointData']['consumptionEan']
+ return'{}_rt_consumption'.format(_id)
diff --git a/homeassistant/components/sensor/time_date.py b/homeassistant/components/sensor/time_date.py
index e4c719acd0d..1b346d409c4 100644
--- a/homeassistant/components/sensor/time_date.py
+++ b/homeassistant/components/sensor/time_date.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.time_date/
"""
from datetime import timedelta
-import asyncio
import logging
import voluptuous as vol
@@ -37,9 +36,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Time and Date sensor."""
if hass.config.time_zone is None:
_LOGGER.error("Timezone is not set in Home Assistant configuration")
diff --git a/homeassistant/components/sensor/tradfri.py b/homeassistant/components/sensor/tradfri.py
index 86d0c1abc19..45167874de2 100644
--- a/homeassistant/components/sensor/tradfri.py
+++ b/homeassistant/components/sensor/tradfri.py
@@ -26,7 +26,8 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
devices_commands = await api(gateway.get_devices())
all_devices = await api(devices_commands)
- devices = (dev for dev in all_devices if not dev.has_light_control)
+ devices = (dev for dev in all_devices if not dev.has_light_control and
+ not dev.has_socket_control)
async_add_entities(TradfriDevice(device, api) for device in devices)
@@ -92,7 +93,7 @@ class TradfriDevice(Entity):
cmd = self._device.observe(callback=self._observe_update,
err_callback=self._async_start_observe,
duration=0)
- self.hass.async_add_job(self._api(cmd))
+ self.hass.async_create_task(self._api(cmd))
except PytradfriError as err:
_LOGGER.warning("Observation failed, trying again", exc_info=err)
self._async_start_observe()
@@ -106,4 +107,4 @@ class TradfriDevice(Entity):
"""Receive new state data for this device."""
self._refresh(tradfri_device)
- self.hass.async_add_job(self.async_update_ha_state())
+ self.hass.async_create_task(self.async_update_ha_state())
diff --git a/homeassistant/components/sensor/upnp.py b/homeassistant/components/sensor/upnp.py
index d021312d15c..c05e2ce0ade 100644
--- a/homeassistant/components/sensor/upnp.py
+++ b/homeassistant/components/sensor/upnp.py
@@ -1,87 +1,268 @@
"""
-Support for UPnP Sensors (IGD).
+Support for UPnP/IGD Sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.upnp/
"""
+from datetime import datetime
import logging
-from homeassistant.components.upnp import DATA_UPNP, UNITS, CIC_SERVICE
+from homeassistant.core import callback
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity import Entity
+from homeassistant.components.upnp.const import DOMAIN as DATA_UPNP
+from homeassistant.components.upnp.const import SIGNAL_REMOVE_SENSOR
+
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['upnp']
-BYTES_RECEIVED = 1
-BYTES_SENT = 2
-PACKETS_RECEIVED = 3
-PACKETS_SENT = 4
+BYTES_RECEIVED = 'bytes_received'
+BYTES_SENT = 'bytes_sent'
+PACKETS_RECEIVED = 'packets_received'
+PACKETS_SENT = 'packets_sent'
-# sensor_type: [friendly_name, convert_unit, icon]
SENSOR_TYPES = {
- BYTES_RECEIVED: ['received bytes', True, 'mdi:server-network'],
- BYTES_SENT: ['sent bytes', True, 'mdi:server-network'],
- PACKETS_RECEIVED: ['packets received', False, 'mdi:server-network'],
- PACKETS_SENT: ['packets sent', False, 'mdi:server-network'],
+ BYTES_RECEIVED: {
+ 'name': 'bytes received',
+ 'unit': 'bytes',
+ },
+ BYTES_SENT: {
+ 'name': 'bytes sent',
+ 'unit': 'bytes',
+ },
+ PACKETS_RECEIVED: {
+ 'name': 'packets received',
+ 'unit': 'packets',
+ },
+ PACKETS_SENT: {
+ 'name': 'packets sent',
+ 'unit': 'packets',
+ },
}
+IN = 'received'
+OUT = 'sent'
+KBYTE = 1024
+
async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None):
- """Set up the IGD sensors."""
- if discovery_info is None:
- return
-
- device = hass.data[DATA_UPNP]
- service = device.find_first_service(CIC_SERVICE)
- unit = discovery_info['unit']
- async_add_entities([
- IGDSensor(service, t, unit if SENSOR_TYPES[t][1] else '#')
- for t in SENSOR_TYPES], True)
+ """Old way of setting up UPnP/IGD sensors."""
+ _LOGGER.debug('async_setup_platform: config: %s, discovery: %s',
+ config, discovery_info)
-class IGDSensor(Entity):
- """Representation of a UPnP IGD sensor."""
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up the UPnP/IGD sensor."""
+ @callback
+ def async_add_sensor(device):
+ """Add sensors from UPnP/IGD device."""
+ # raw sensors + per-second sensors
+ sensors = [
+ RawUPnPIGDSensor(device, name, sensor_type)
+ for name, sensor_type in SENSOR_TYPES.items()
+ ]
+ sensors += [
+ KBytePerSecondUPnPIGDSensor(device, IN),
+ KBytePerSecondUPnPIGDSensor(device, OUT),
+ PacketsPerSecondUPnPIGDSensor(device, IN),
+ PacketsPerSecondUPnPIGDSensor(device, OUT),
+ ]
+ async_add_entities(sensors, True)
- def __init__(self, service, sensor_type, unit=None):
- """Initialize the IGD sensor."""
- self._service = service
- self.type = sensor_type
- self.unit = unit
- self.unit_factor = UNITS[unit] if unit in UNITS else 1
- self._name = 'IGD {}'.format(SENSOR_TYPES[sensor_type][0])
+ data = config_entry.data
+ udn = data['udn']
+ device = hass.data[DATA_UPNP]['devices'][udn]
+ async_add_sensor(device)
+
+
+class UpnpSensor(Entity):
+ """Base class for UPnP/IGD sensors."""
+
+ def __init__(self, device):
+ """Initialize the base sensor."""
+ self._device = device
+
+ async def async_added_to_hass(self):
+ """Subscribe to sensors events."""
+ async_dispatcher_connect(self.hass,
+ SIGNAL_REMOVE_SENSOR,
+ self._upnp_remove_sensor)
+
+ @callback
+ def _upnp_remove_sensor(self, device):
+ """Remove sensor."""
+ if self._device != device:
+ # not for us
+ return
+
+ self.hass.async_create_task(self.async_remove())
+
+
+class RawUPnPIGDSensor(UpnpSensor):
+ """Representation of a UPnP/IGD sensor."""
+
+ def __init__(self, device, sensor_type_name, sensor_type):
+ """Initialize the UPnP/IGD sensor."""
+ super().__init__(device)
+ self._type_name = sensor_type_name
+ self._type = sensor_type
+ self._name = '{} {}'.format(device.name, sensor_type['name'])
self._state = None
@property
- def name(self):
+ def name(self) -> str:
"""Return the name of the sensor."""
return self._name
@property
- def state(self):
+ def unique_id(self) -> str:
+ """Return an unique ID."""
+ return '{}_{}'.format(self._device.udn, self._type_name)
+
+ @property
+ def state(self) -> str:
"""Return the state of the device."""
- if self._state:
- return format(float(self._state) / self.unit_factor, '.1f')
- return self._state
+ return format(self._state, 'd')
@property
- def icon(self):
+ def icon(self) -> str:
"""Icon to use in the frontend, if any."""
- return SENSOR_TYPES[self.type][2]
+ return 'mdi:server-network'
@property
- def unit_of_measurement(self):
+ def unit_of_measurement(self) -> str:
"""Return the unit of measurement of this entity, if any."""
- return self.unit
+ return self._type['unit']
async def async_update(self):
"""Get the latest information from the IGD."""
- if self.type == BYTES_RECEIVED:
- self._state = await self._service.get_total_bytes_received()
- elif self.type == BYTES_SENT:
- self._state = await self._service.get_total_bytes_sent()
- elif self.type == PACKETS_RECEIVED:
- self._state = await self._service.get_total_packets_received()
- elif self.type == PACKETS_SENT:
- self._state = await self._service.get_total_packets_sent()
+ if self._type_name == BYTES_RECEIVED:
+ self._state = await self._device.async_get_total_bytes_received()
+ elif self._type_name == BYTES_SENT:
+ self._state = await self._device.async_get_total_bytes_sent()
+ elif self._type_name == PACKETS_RECEIVED:
+ self._state = await self._device.async_get_total_packets_received()
+ elif self._type_name == PACKETS_SENT:
+ self._state = await self._device.async_get_total_packets_sent()
+
+
+class PerSecondUPnPIGDSensor(UpnpSensor):
+ """Abstract representation of a X Sent/Received per second sensor."""
+
+ def __init__(self, device, direction):
+ """Initializer."""
+ super().__init__(device)
+ self._direction = direction
+
+ self._state = None
+ self._last_value = None
+ self._last_update_time = None
+
+ @property
+ def unit(self) -> str:
+ """Get unit we are measuring in."""
+ raise NotImplementedError()
+
+ @property
+ def _async_fetch_value(self):
+ """Fetch a value from the IGD."""
+ raise NotImplementedError()
+
+ @property
+ def unique_id(self) -> str:
+ """Return an unique ID."""
+ return '{}_{}/sec_{}'.format(self._device.udn,
+ self.unit,
+ self._direction)
+
+ @property
+ def name(self) -> str:
+ """Return the name of the sensor."""
+ return '{} {}/sec {}'.format(self._device.name,
+ self.unit,
+ self._direction)
+
+ @property
+ def icon(self) -> str:
+ """Icon to use in the frontend, if any."""
+ return 'mdi:server-network'
+
+ @property
+ def unit_of_measurement(self) -> str:
+ """Return the unit of measurement of this entity, if any."""
+ return '{}/sec'.format(self.unit)
+
+ def _is_overflowed(self, new_value) -> bool:
+ """Check if value has overflowed."""
+ return new_value < self._last_value
+
+ async def async_update(self):
+ """Get the latest information from the UPnP/IGD."""
+ new_value = await self._async_fetch_value()
+
+ if self._last_value is None:
+ self._last_value = new_value
+ self._last_update_time = datetime.now()
+ return
+
+ now = datetime.now()
+ if self._is_overflowed(new_value):
+ self._state = None # temporarily report nothing
+ else:
+ delta_time = (now - self._last_update_time).seconds
+ delta_value = new_value - self._last_value
+ self._state = (delta_value / delta_time)
+
+ self._last_value = new_value
+ self._last_update_time = now
+
+
+class KBytePerSecondUPnPIGDSensor(PerSecondUPnPIGDSensor):
+ """Representation of a KBytes Sent/Received per second sensor."""
+
+ @property
+ def unit(self) -> str:
+ """Get unit we are measuring in."""
+ return 'kbyte'
+
+ async def _async_fetch_value(self) -> float:
+ """Fetch value from device."""
+ if self._direction == IN:
+ return await self._device.async_get_total_bytes_received()
+
+ return await self._device.async_get_total_bytes_sent()
+
+ @property
+ def state(self) -> str:
+ """Return the state of the device."""
+ if self._state is None:
+ return None
+
+ return format(float(self._state / KBYTE), '.1f')
+
+
+class PacketsPerSecondUPnPIGDSensor(PerSecondUPnPIGDSensor):
+ """Representation of a Packets Sent/Received per second sensor."""
+
+ @property
+ def unit(self) -> str:
+ """Get unit we are measuring in."""
+ return 'packets'
+
+ async def _async_fetch_value(self) -> float:
+ """Fetch value from device."""
+ if self._direction == IN:
+ return await self._device.async_get_total_packets_received()
+
+ return await self._device.async_get_total_packets_sent()
+
+ @property
+ def state(self) -> str:
+ """Return the state of the device."""
+ if self._state is None:
+ return None
+
+ return format(float(self._state), '.1f')
diff --git a/homeassistant/components/sensor/viaggiatreno.py b/homeassistant/components/sensor/viaggiatreno.py
index 1dd8523eb4b..82068c456b6 100644
--- a/homeassistant/components/sensor/viaggiatreno.py
+++ b/homeassistant/components/sensor/viaggiatreno.py
@@ -56,9 +56,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the ViaggiaTreno platform."""
train_id = config.get(CONF_TRAIN_ID)
station_id = config.get(CONF_STATION_ID)
@@ -68,16 +67,15 @@ def async_setup_platform(hass, config, async_add_entities,
async_add_entities([ViaggiaTrenoSensor(train_id, station_id, name)])
-@asyncio.coroutine
-def async_http_request(hass, uri):
+async def async_http_request(hass, uri):
"""Perform actual request."""
try:
session = hass.helpers.aiohttp_client.async_get_clientsession(hass)
with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop):
- req = yield from session.get(uri)
+ req = await session.get(uri)
if req.status != 200:
return {'error': req.status}
- json_response = yield from req.json()
+ json_response = await req.json()
return json_response
except (asyncio.TimeoutError, aiohttp.ClientError) as exc:
_LOGGER.error("Cannot connect to ViaggiaTreno API endpoint: %s", exc)
@@ -152,11 +150,10 @@ class ViaggiaTrenoSensor(Entity):
return True
return False
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update state."""
uri = self.uri
- res = yield from async_http_request(self.hass, uri)
+ res = await async_http_request(self.hass, uri)
if res.get('error', ''):
if res['error'] == 204:
self._state = NO_INFORMATION_STRING
diff --git a/homeassistant/components/sensor/waqi.py b/homeassistant/components/sensor/waqi.py
index 9f90f465fb2..d6b8d278fb1 100644
--- a/homeassistant/components/sensor/waqi.py
+++ b/homeassistant/components/sensor/waqi.py
@@ -59,9 +59,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the requested World Air Quality Index locations."""
import waqiasync
@@ -74,7 +73,7 @@ def async_setup_platform(hass, config, async_add_entities,
dev = []
try:
for location_name in locations:
- stations = yield from client.search(location_name)
+ stations = await client.search(location_name)
_LOGGER.debug("The following stations were returned: %s", stations)
for station in stations:
waqi_sensor = WaqiSensor(client, station)
@@ -161,13 +160,12 @@ class WaqiSensor(Entity):
except (IndexError, KeyError):
return {ATTR_ATTRIBUTION: ATTRIBUTION}
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the latest data and updates the states."""
if self.uid:
- result = yield from self._client.get_station_by_number(self.uid)
+ result = await self._client.get_station_by_number(self.uid)
elif self.url:
- result = yield from self._client.get_station_by_name(self.url)
+ result = await self._client.get_station_by_name(self.url)
else:
result = None
self._data = result
diff --git a/homeassistant/components/sensor/waterfurnace.py b/homeassistant/components/sensor/waterfurnace.py
index 806b40551df..60da761cf75 100644
--- a/homeassistant/components/sensor/waterfurnace.py
+++ b/homeassistant/components/sensor/waterfurnace.py
@@ -4,7 +4,6 @@ Support for Waterfurnace.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.waterfurnace/
"""
-import asyncio
from homeassistant.components.sensor import ENTITY_ID_FORMAT
from homeassistant.components.waterfurnace import (
@@ -100,8 +99,7 @@ class WaterFurnaceSensor(Entity):
"""Return the polling state."""
return False
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.async_update_callback)
diff --git a/homeassistant/components/sensor/wink.py b/homeassistant/components/sensor/wink.py
index 8e11b054b24..8c2abc0f875 100644
--- a/homeassistant/components/sensor/wink.py
+++ b/homeassistant/components/sensor/wink.py
@@ -4,7 +4,6 @@ Support for Wink sensors.
For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/sensor.wink/
"""
-import asyncio
import logging
from homeassistant.components.wink import DOMAIN, WinkDevice
@@ -60,8 +59,7 @@ class WinkSensorDevice(WinkDevice, Entity):
else:
self._unit_of_measurement = self.wink.unit()
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['sensor'].append(self)
diff --git a/homeassistant/components/sensor/worxlandroid.py b/homeassistant/components/sensor/worxlandroid.py
index f6593f4b1c5..be5c8452d88 100644
--- a/homeassistant/components/sensor/worxlandroid.py
+++ b/homeassistant/components/sensor/worxlandroid.py
@@ -50,9 +50,8 @@ ERROR_STATE = [
]
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Worx Landroid sensors."""
for typ in ('battery', 'state'):
async_add_entities([WorxLandroidSensor(typ, config)])
@@ -88,8 +87,7 @@ class WorxLandroidSensor(Entity):
return '%'
return None
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update the sensor data from the mower."""
connection_error = False
@@ -97,7 +95,7 @@ class WorxLandroidSensor(Entity):
session = async_get_clientsession(self.hass)
with async_timeout.timeout(self.timeout, loop=self.hass.loop):
auth = aiohttp.helpers.BasicAuth('admin', self.pin)
- mower_response = yield from session.get(self.url, auth=auth)
+ mower_response = await session.get(self.url, auth=auth)
except (asyncio.TimeoutError, aiohttp.ClientError):
if self.allow_unreachable is False:
_LOGGER.error("Error connecting to mower at %s", self.url)
@@ -115,7 +113,7 @@ class WorxLandroidSensor(Entity):
elif connection_error is False:
# set the expected content type to be text/html
# since the mover incorrectly returns it...
- data = yield from mower_response.json(content_type='text/html')
+ data = await mower_response.json(content_type='text/html')
# sensor battery
if self.sensor == 'battery':
diff --git a/homeassistant/components/sensor/wunderground.py b/homeassistant/components/sensor/wunderground.py
index a14d4b94789..590a9d96b4d 100644
--- a/homeassistant/components/sensor/wunderground.py
+++ b/homeassistant/components/sensor/wunderground.py
@@ -741,10 +741,9 @@ class WUndergroundSensor(Entity):
"""Return the units of measurement."""
return self._unit_of_measurement
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update current conditions."""
- yield from self.rest.async_update()
+ await self.rest.async_update()
if not self.rest.data:
# no data, return
diff --git a/homeassistant/components/sensor/xiaomi_aqara.py b/homeassistant/components/sensor/xiaomi_aqara.py
index 8a3a11db051..31366fe0097 100644
--- a/homeassistant/components/sensor/xiaomi_aqara.py
+++ b/homeassistant/components/sensor/xiaomi_aqara.py
@@ -5,7 +5,7 @@ from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY,
XiaomiDevice)
from homeassistant.const import (
DEVICE_CLASS_HUMIDITY, DEVICE_CLASS_ILLUMINANCE, DEVICE_CLASS_TEMPERATURE,
- TEMP_CELSIUS)
+ TEMP_CELSIUS, DEVICE_CLASS_PRESSURE)
_LOGGER = logging.getLogger(__name__)
@@ -14,7 +14,7 @@ SENSOR_TYPES = {
'humidity': ['%', None, DEVICE_CLASS_HUMIDITY],
'illumination': ['lm', None, DEVICE_CLASS_ILLUMINANCE],
'lux': ['lx', None, DEVICE_CLASS_ILLUMINANCE],
- 'pressure': ['hPa', 'mdi:gauge', None]
+ 'pressure': ['hPa', None, DEVICE_CLASS_PRESSURE]
}
diff --git a/homeassistant/components/shell_command.py b/homeassistant/components/shell_command.py
index 10a6c350b7c..2a95dd5c144 100644
--- a/homeassistant/components/shell_command.py
+++ b/homeassistant/components/shell_command.py
@@ -27,15 +27,13 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
+async def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
"""Set up the shell_command component."""
conf = config.get(DOMAIN, {})
cache = {}
- @asyncio.coroutine
- def async_service_handler(service: ServiceCall) -> None:
+ async def async_service_handler(service: ServiceCall) -> None:
"""Execute a shell command service."""
cmd = conf[service.service]
@@ -85,8 +83,8 @@ def async_setup(hass: HomeAssistantType, config: ConfigType) -> bool:
stderr=asyncio.subprocess.PIPE,
)
- process = yield from create_process
- stdout_data, stderr_data = yield from process.communicate()
+ process = await create_process
+ stdout_data, stderr_data = await process.communicate()
if stdout_data:
_LOGGER.debug("Stdout of command: `%s`, return code: %s:\n%s",
diff --git a/homeassistant/components/sisyphus.py b/homeassistant/components/sisyphus.py
index dc9f9cc4c25..f875e3a91c7 100644
--- a/homeassistant/components/sisyphus.py
+++ b/homeassistant/components/sisyphus.py
@@ -54,12 +54,12 @@ async def async_setup(hass, config):
tables[name] = table
_LOGGER.debug("Connected to %s at %s", name, host)
- hass.async_add_job(async_load_platform(
+ hass.async_create_task(async_load_platform(
hass, 'light', DOMAIN, {
CONF_NAME: name,
}, config
))
- hass.async_add_job(async_load_platform(
+ hass.async_create_task(async_load_platform(
hass, 'media_player', DOMAIN, {
CONF_NAME: name,
CONF_HOST: host,
diff --git a/homeassistant/components/smhi/.translations/ca.json b/homeassistant/components/smhi/.translations/ca.json
new file mode 100644
index 00000000000..23b6a2934f0
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/ca.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "El nom ja existeix",
+ "wrong_location": "Ubicaci\u00f3 nom\u00e9s a Su\u00e8cia"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Latitud",
+ "longitude": "Longitud",
+ "name": "Nom"
+ },
+ "title": "Ubicaci\u00f3 a Su\u00e8cia"
+ }
+ },
+ "title": "Servei meteorol\u00f2gic suec (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/en.json b/homeassistant/components/smhi/.translations/en.json
new file mode 100644
index 00000000000..6aa256d87d4
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/en.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "Name already exists",
+ "wrong_location": "Location Sweden only"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Latitude",
+ "longitude": "Longitude",
+ "name": "Name"
+ },
+ "title": "Location in Sweden"
+ }
+ },
+ "title": "Swedish weather service (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/ko.json b/homeassistant/components/smhi/.translations/ko.json
new file mode 100644
index 00000000000..f307fa1ad23
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/ko.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4",
+ "wrong_location": "\uc2a4\uc6e8\ub374 \uc9c0\uc5ed \uc804\uc6a9\uc785\ub2c8\ub2e4"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "\uc704\ub3c4",
+ "longitude": "\uacbd\ub3c4",
+ "name": "\uc774\ub984"
+ },
+ "title": "\uc2a4\uc6e8\ub374 \uc9c0\uc5ed \uc704\uce58"
+ }
+ },
+ "title": "\uc2a4\uc6e8\ub374 \uae30\uc0c1 \uc11c\ube44\uc2a4 (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/lb.json b/homeassistant/components/smhi/.translations/lb.json
new file mode 100644
index 00000000000..46abfd2677f
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/lb.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "Numm g\u00ebtt et schonn",
+ "wrong_location": "N\u00ebmmen Uertschaften an Schweden"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Breedegrad",
+ "longitude": "L\u00e4ngegrad",
+ "name": "Numm"
+ },
+ "title": "Uertschaft an Schweden"
+ }
+ },
+ "title": "Schwedeschen Wieder D\u00e9ngscht (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/nl.json b/homeassistant/components/smhi/.translations/nl.json
new file mode 100644
index 00000000000..88edc116e74
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/nl.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "Naam bestaat al",
+ "wrong_location": "Locatie alleen Zweden"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Breedtegraad",
+ "longitude": "Lengtegraad",
+ "name": "Naam"
+ },
+ "title": "Locatie in Zweden"
+ }
+ },
+ "title": "Zweedse weerdienst (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/pl.json b/homeassistant/components/smhi/.translations/pl.json
new file mode 100644
index 00000000000..21973cd54b6
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/pl.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "Nazwa ju\u017c istnieje",
+ "wrong_location": "Lokalizacja w Szwecji"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Szeroko\u015b\u0107 geograficzna",
+ "longitude": "D\u0142ugo\u015b\u0107 geograficzna",
+ "name": "Nazwa"
+ },
+ "title": "Lokalizacja w Szwecji"
+ }
+ },
+ "title": "Szwedzka us\u0142uga pogodowa (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/ru.json b/homeassistant/components/smhi/.translations/ru.json
new file mode 100644
index 00000000000..012bb74c568
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/ru.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "\u0418\u043c\u044f \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442",
+ "wrong_location": "\u0422\u043e\u043b\u044c\u043a\u043e \u0434\u043b\u044f \u0428\u0432\u0435\u0446\u0438\u0438"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "\u0428\u0438\u0440\u043e\u0442\u0430",
+ "longitude": "\u0414\u043e\u043b\u0433\u043e\u0442\u0430",
+ "name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"
+ },
+ "title": "\u041c\u0435\u0441\u0442\u043e\u043d\u0430\u0445\u043e\u0436\u0434\u0435\u043d\u0438\u0435 \u0432 \u0428\u0432\u0435\u0446\u0438\u0438"
+ }
+ },
+ "title": "\u0428\u0432\u0435\u0434\u0441\u043a\u0430\u044f \u043c\u0435\u0442\u0435\u043e\u0440\u043e\u043b\u043e\u0433\u0438\u0447\u0435\u0441\u043a\u0430\u044f \u0441\u043b\u0443\u0436\u0431\u0430 (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/sl.json b/homeassistant/components/smhi/.translations/sl.json
new file mode 100644
index 00000000000..94c3750f06f
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/sl.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "Ime \u017ee obstaja",
+ "wrong_location": "Lokacija le na \u0160vedskem"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Zemljepisna \u0161irina",
+ "longitude": "Zemljepisna dol\u017eina",
+ "name": "Ime"
+ },
+ "title": "Lokacija na \u0160vedskem"
+ }
+ },
+ "title": "\u0160vedska vremenska slu\u017eba (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/sv.json b/homeassistant/components/smhi/.translations/sv.json
new file mode 100644
index 00000000000..69073a0eb73
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/sv.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "Namnet finns redan",
+ "wrong_location": "Plats i Sverige endast"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "Latitud",
+ "longitude": "Longitud",
+ "name": "Namn"
+ },
+ "title": "Plats i Sverige"
+ }
+ },
+ "title": "Svensk v\u00e4derservice (SMHI)"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/zh-Hans.json b/homeassistant/components/smhi/.translations/zh-Hans.json
new file mode 100644
index 00000000000..a70bb7a6722
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/zh-Hans.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "\u540d\u79f0\u5df2\u5b58\u5728",
+ "wrong_location": "\u4ec5\u9650\u745e\u5178\u7684\u4f4d\u7f6e"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "\u7eac\u5ea6",
+ "longitude": "\u7ecf\u5ea6",
+ "name": "\u540d\u79f0"
+ },
+ "title": "\u5728\u745e\u5178\u7684\u4f4d\u7f6e"
+ }
+ },
+ "title": "\u745e\u5178\u6c14\u8c61\u670d\u52a1\uff08SMHI\uff09"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/smhi/.translations/zh-Hant.json b/homeassistant/components/smhi/.translations/zh-Hant.json
new file mode 100644
index 00000000000..b982baac2f8
--- /dev/null
+++ b/homeassistant/components/smhi/.translations/zh-Hant.json
@@ -0,0 +1,19 @@
+{
+ "config": {
+ "error": {
+ "name_exists": "\u8a72\u540d\u7a31\u5df2\u5b58\u5728",
+ "wrong_location": "\u50c5\u9650\u745e\u5178\u5ea7\u6a19"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "latitude": "\u7def\u5ea6",
+ "longitude": "\u7d93\u5ea6",
+ "name": "\u540d\u7a31"
+ },
+ "title": "\u745e\u5178\u5ea7\u6a19"
+ }
+ },
+ "title": "\u745e\u5178\u6c23\u8c61\u670d\u52d9\uff08SMHI\uff09"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/sonos/.translations/ko.json b/homeassistant/components/sonos/.translations/ko.json
index 89933f57425..0b2e2a1875c 100644
--- a/homeassistant/components/sonos/.translations/ko.json
+++ b/homeassistant/components/sonos/.translations/ko.json
@@ -6,7 +6,7 @@
},
"step": {
"confirm": {
- "description": "Sonos\ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
+ "description": "Sonos \ub97c \uc124\uc815 \ud558\uc2dc\uaca0\uc2b5\ub2c8\uae4c?",
"title": "Sonos"
}
},
diff --git a/homeassistant/components/sonos/.translations/pl.json b/homeassistant/components/sonos/.translations/pl.json
index 2a0c526b9a6..a45cb4e9824 100644
--- a/homeassistant/components/sonos/.translations/pl.json
+++ b/homeassistant/components/sonos/.translations/pl.json
@@ -6,7 +6,7 @@
},
"step": {
"confirm": {
- "description": "Chcesz skonfigurowa\u0107 Sonos?",
+ "description": "Czy chcesz skonfigurowa\u0107 Sonos?",
"title": "Sonos"
}
},
diff --git a/homeassistant/components/sonos/.translations/ru.json b/homeassistant/components/sonos/.translations/ru.json
index 63b6bd87c20..1bff827d273 100644
--- a/homeassistant/components/sonos/.translations/ru.json
+++ b/homeassistant/components/sonos/.translations/ru.json
@@ -2,11 +2,11 @@
"config": {
"abort": {
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Sonos \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.",
- "single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Sonos."
+ "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."
},
"step": {
"confirm": {
- "description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Sonos?",
+ "description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Sonos?",
"title": "Sonos"
}
},
diff --git a/homeassistant/components/sonos/.translations/zh-Hant.json b/homeassistant/components/sonos/.translations/zh-Hant.json
index c6fb13c3605..520a29b7602 100644
--- a/homeassistant/components/sonos/.translations/zh-Hant.json
+++ b/homeassistant/components/sonos/.translations/zh-Hant.json
@@ -1,7 +1,7 @@
{
"config": {
"abort": {
- "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Sonos \u8a2d\u5099\u3002",
+ "no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Sonos \u88dd\u7f6e\u3002",
"single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Sonos \u5373\u53ef\u3002"
},
"step": {
diff --git a/homeassistant/components/sonos/__init__.py b/homeassistant/components/sonos/__init__.py
index b4565794844..b794fe607e6 100644
--- a/homeassistant/components/sonos/__init__.py
+++ b/homeassistant/components/sonos/__init__.py
@@ -4,7 +4,7 @@ from homeassistant.helpers import config_entry_flow
DOMAIN = 'sonos'
-REQUIREMENTS = ['pysonos==0.0.2']
+REQUIREMENTS = ['pysonos==0.0.3']
async def async_setup(hass, config):
diff --git a/homeassistant/components/spc.py b/homeassistant/components/spc.py
index b00a4aeed2c..5aa987bd0a8 100644
--- a/homeassistant/components/spc.py
+++ b/homeassistant/components/spc.py
@@ -16,9 +16,6 @@ REQUIREMENTS = ['pyspcwebgw==0.4.0']
_LOGGER = logging.getLogger(__name__)
-ATTR_DISCOVER_DEVICES = 'devices'
-ATTR_DISCOVER_AREAS = 'areas'
-
CONF_WS_URL = 'ws_url'
CONF_API_URL = 'api_url'
@@ -66,13 +63,11 @@ async def async_setup(hass, config):
# add sensor devices for each zone (typically motion/fire/door sensors)
hass.async_create_task(discovery.async_load_platform(
- hass, 'binary_sensor', DOMAIN,
- {ATTR_DISCOVER_DEVICES: spc.zones.values()}, config))
+ hass, 'binary_sensor', DOMAIN, {}))
# create a separate alarm panel for each area
hass.async_create_task(discovery.async_load_platform(
- hass, 'alarm_control_panel', DOMAIN,
- {ATTR_DISCOVER_AREAS: spc.areas.values()}, config))
+ hass, 'alarm_control_panel', DOMAIN, {}))
# start listening for incoming events over websocket
spc.start()
diff --git a/homeassistant/components/sun.py b/homeassistant/components/sun.py
index 90c7f69e64a..e2717047b0a 100644
--- a/homeassistant/components/sun.py
+++ b/homeassistant/components/sun.py
@@ -4,7 +4,6 @@ Support for functionality to keep track of the sun.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/sun/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -36,8 +35,7 @@ STATE_ATTR_NEXT_RISING = 'next_rising'
STATE_ATTR_NEXT_SETTING = 'next_setting'
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Track the state of the sun."""
if config.get(CONF_ELEVATION) is not None:
_LOGGER.warning(
diff --git a/homeassistant/components/switch/__init__.py b/homeassistant/components/switch/__init__.py
index c95c752435a..1adabe4b57e 100644
--- a/homeassistant/components/switch/__init__.py
+++ b/homeassistant/components/switch/__init__.py
@@ -9,7 +9,6 @@ import logging
import voluptuous as vol
-from homeassistant.core import callback
from homeassistant.loader import bind_hass
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.entity import ToggleEntity
@@ -56,42 +55,6 @@ def is_on(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, entity_id=None):
- """Turn all or specified switch on."""
- hass.add_job(async_turn_on, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_turn_on(hass, entity_id=None):
- """Turn all or specified switch on."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
-
-
-@bind_hass
-def turn_off(hass, entity_id=None):
- """Turn all or specified switch off."""
- hass.add_job(async_turn_off, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_turn_off(hass, entity_id=None):
- """Turn all or specified switch off."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.async_add_job(
- hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
-
-
-@bind_hass
-def toggle(hass, entity_id=None):
- """Toggle all or specified switch."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
-
-
async def async_setup(hass, config):
"""Track states and offer events for switches."""
component = hass.data[DOMAIN] = EntityComponent(
diff --git a/homeassistant/components/switch/ads.py b/homeassistant/components/switch/ads.py
index 8c13e9a8960..ecd1e7edc31 100644
--- a/homeassistant/components/switch/ads.py
+++ b/homeassistant/components/switch/ads.py
@@ -4,7 +4,6 @@ Support for ADS switch platform.
For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/switch.ads/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -47,8 +46,7 @@ class AdsSwitch(ToggleEntity):
self._name = name
self.ads_var = ads_var
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register device notification."""
def update(name, value):
"""Handle device notification."""
diff --git a/homeassistant/components/switch/amcrest.py b/homeassistant/components/switch/amcrest.py
index 0805793fe95..4eb20308850 100644
--- a/homeassistant/components/switch/amcrest.py
+++ b/homeassistant/components/switch/amcrest.py
@@ -4,7 +4,6 @@ Support for toggling Amcrest IP camera settings.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.amcrest/
"""
-import asyncio
import logging
from homeassistant.components.amcrest import DATA_AMCREST, SWITCHES
@@ -17,9 +16,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['amcrest']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the IP Amcrest camera switch platform."""
if discovery_info is None:
return
diff --git a/homeassistant/components/switch/android_ip_webcam.py b/homeassistant/components/switch/android_ip_webcam.py
index 92e52c21caa..f770b9d5ebf 100644
--- a/homeassistant/components/switch/android_ip_webcam.py
+++ b/homeassistant/components/switch/android_ip_webcam.py
@@ -4,7 +4,6 @@ Support for IP Webcam settings.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.android_ip_webcam/
"""
-import asyncio
from homeassistant.components.switch import SwitchDevice
from homeassistant.components.android_ip_webcam import (
@@ -14,9 +13,8 @@ from homeassistant.components.android_ip_webcam import (
DEPENDENCIES = ['android_ip_webcam']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the IP Webcam switch platform."""
if discovery_info is None:
return
@@ -51,8 +49,7 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice):
"""Return the name of the node."""
return self._name
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the updated status of the switch."""
self._state = bool(self._ipcam.current_settings.get(self._setting))
@@ -61,31 +58,29 @@ class IPWebcamSettingsSwitch(AndroidIPCamEntity, SwitchDevice):
"""Return the boolean response if the node is on."""
return self._state
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn device on."""
if self._setting == 'torch':
- yield from self._ipcam.torch(activate=True)
+ await self._ipcam.torch(activate=True)
elif self._setting == 'focus':
- yield from self._ipcam.focus(activate=True)
+ await self._ipcam.focus(activate=True)
elif self._setting == 'video_recording':
- yield from self._ipcam.record(record=True)
+ await self._ipcam.record(record=True)
else:
- yield from self._ipcam.change_setting(self._setting, True)
+ await self._ipcam.change_setting(self._setting, True)
self._state = True
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn device off."""
if self._setting == 'torch':
- yield from self._ipcam.torch(activate=False)
+ await self._ipcam.torch(activate=False)
elif self._setting == 'focus':
- yield from self._ipcam.focus(activate=False)
+ await self._ipcam.focus(activate=False)
elif self._setting == 'video_recording':
- yield from self._ipcam.record(record=False)
+ await self._ipcam.record(record=False)
else:
- yield from self._ipcam.change_setting(self._setting, False)
+ await self._ipcam.change_setting(self._setting, False)
self._state = False
self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/switch/aqualogic.py b/homeassistant/components/switch/aqualogic.py
new file mode 100644
index 00000000000..48c4702aca0
--- /dev/null
+++ b/homeassistant/components/switch/aqualogic.py
@@ -0,0 +1,114 @@
+"""
+Support for AquaLogic switches.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/switch.aqualogic/
+"""
+import logging
+
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.core import callback
+import homeassistant.components.aqualogic as aq
+from homeassistant.components.switch import SwitchDevice, PLATFORM_SCHEMA
+from homeassistant.const import (CONF_MONITORED_CONDITIONS)
+
+DEPENDENCIES = ['aqualogic']
+
+_LOGGER = logging.getLogger(__name__)
+
+SWITCH_TYPES = {
+ 'lights': 'Lights',
+ 'filter': 'Filter',
+ 'filter_low_speed': 'Filter Low Speed',
+ 'aux_1': 'Aux 1',
+ 'aux_2': 'Aux 2',
+ 'aux_3': 'Aux 3',
+ 'aux_4': 'Aux 4',
+ 'aux_5': 'Aux 5',
+ 'aux_6': 'Aux 6',
+ 'aux_7': 'Aux 7',
+}
+
+PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
+ vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SWITCH_TYPES)):
+ vol.All(cv.ensure_list, [vol.In(SWITCH_TYPES)]),
+})
+
+
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
+ """Set up the switch platform."""
+ switches = []
+
+ processor = hass.data[aq.DOMAIN]
+ for switch_type in config.get(CONF_MONITORED_CONDITIONS):
+ switches.append(AquaLogicSwitch(processor, switch_type))
+
+ async_add_entities(switches)
+
+
+class AquaLogicSwitch(SwitchDevice):
+ """Switch implementation for the AquaLogic component."""
+
+ def __init__(self, processor, switch_type):
+ """Initialize switch."""
+ from aqualogic.core import States
+ self._processor = processor
+ self._type = switch_type
+ self._state_name = {
+ 'lights': States.LIGHTS,
+ 'filter': States.FILTER,
+ 'filter_low_speed': States.FILTER_LOW_SPEED,
+ 'aux_1': States.AUX_1,
+ 'aux_2': States.AUX_2,
+ 'aux_3': States.AUX_3,
+ 'aux_4': States.AUX_4,
+ 'aux_5': States.AUX_5,
+ 'aux_6': States.AUX_6,
+ 'aux_7': States.AUX_7
+ }[switch_type]
+
+ @property
+ def name(self):
+ """Return the name of the switch."""
+ return "AquaLogic {}".format(SWITCH_TYPES[self._type])
+
+ @property
+ def should_poll(self):
+ """Return the polling state."""
+ return False
+
+ @property
+ def is_on(self):
+ """Return true if device is on."""
+ panel = self._processor.panel
+ if panel is None:
+ return False
+ state = panel.get_state(self._state_name)
+ return state
+
+ def turn_on(self, **kwargs):
+ """Turn the device on."""
+ panel = self._processor.panel
+ if panel is None:
+ return
+ panel.set_state(self._state_name, True)
+
+ def turn_off(self, **kwargs):
+ """Turn the device off."""
+ panel = self._processor.panel
+ if panel is None:
+ return
+ panel.set_state(self._state_name, False)
+
+ async def async_added_to_hass(self):
+ """Register callbacks."""
+ self.hass.helpers.dispatcher.async_dispatcher_connect(
+ aq.UPDATE_TOPIC, self.async_update_callback)
+
+ @callback
+ def async_update_callback(self):
+ """Update callback."""
+ self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/switch/broadlink.py b/homeassistant/components/switch/broadlink.py
index e6115872390..0562292acec 100644
--- a/homeassistant/components/switch/broadlink.py
+++ b/homeassistant/components/switch/broadlink.py
@@ -80,11 +80,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
config.get(CONF_MAC).encode().replace(b':', b''))
switch_type = config.get(CONF_TYPE)
- @asyncio.coroutine
- def _learn_command(call):
+ async def _learn_command(call):
"""Handle a learn command."""
try:
- auth = yield from hass.async_add_job(broadlink_device.auth)
+ auth = await hass.async_add_job(broadlink_device.auth)
except socket.timeout:
_LOGGER.error("Failed to connect to device, timeout")
return
@@ -92,12 +91,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
_LOGGER.error("Failed to connect to device")
return
- yield from hass.async_add_job(broadlink_device.enter_learning)
+ await hass.async_add_job(broadlink_device.enter_learning)
_LOGGER.info("Press the key you want Home Assistant to learn")
start_time = utcnow()
while (utcnow() - start_time) < timedelta(seconds=20):
- packet = yield from hass.async_add_job(
+ packet = await hass.async_add_job(
broadlink_device.check_data)
if packet:
log_msg = "Received packet is: {}".\
@@ -106,13 +105,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
hass.components.persistent_notification.async_create(
log_msg, title='Broadlink switch')
return
- yield from asyncio.sleep(1, loop=hass.loop)
+ await asyncio.sleep(1, loop=hass.loop)
_LOGGER.error("Did not received any signal")
hass.components.persistent_notification.async_create(
"Did not received any signal", title='Broadlink switch')
- @asyncio.coroutine
- def _send_packet(call):
+ async def _send_packet(call):
"""Send a packet."""
packets = call.data.get('packet', [])
for packet in packets:
@@ -122,12 +120,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
if extra > 0:
packet = packet + ('=' * (4 - extra))
payload = b64decode(packet)
- yield from hass.async_add_job(
+ await hass.async_add_job(
broadlink_device.send_data, payload)
break
except (socket.timeout, ValueError):
try:
- yield from hass.async_add_job(
+ await hass.async_add_job(
broadlink_device.auth)
except socket.timeout:
if retry == DEFAULT_RETRY-1:
diff --git a/homeassistant/components/switch/flux.py b/homeassistant/components/switch/flux.py
index 15fdee59eaf..05e0497155a 100644
--- a/homeassistant/components/switch/flux.py
+++ b/homeassistant/components/switch/flux.py
@@ -13,10 +13,12 @@ import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.light import (
- is_on, turn_on, VALID_TRANSITION, ATTR_TRANSITION)
+ is_on, ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_RGB_COLOR, ATTR_TRANSITION,
+ ATTR_WHITE_VALUE, ATTR_XY_COLOR, DOMAIN as LIGHT_DOMAIN, VALID_TRANSITION)
from homeassistant.components.switch import DOMAIN, SwitchDevice
from homeassistant.const import (
- CONF_NAME, CONF_PLATFORM, CONF_LIGHTS, CONF_MODE)
+ ATTR_ENTITY_ID, CONF_NAME, CONF_PLATFORM, CONF_LIGHTS, CONF_MODE,
+ SERVICE_TURN_ON)
from homeassistant.helpers.event import track_time_change
from homeassistant.helpers.sun import get_astral_event_date
from homeassistant.util import slugify
@@ -69,30 +71,44 @@ def set_lights_xy(hass, lights, x_val, y_val, brightness, transition):
"""Set color of array of lights."""
for light in lights:
if is_on(hass, light):
- turn_on(hass, light,
- xy_color=[x_val, y_val],
- brightness=brightness,
- transition=transition,
- white_value=brightness)
+ service_data = {ATTR_ENTITY_ID: light}
+ if x_val is not None and y_val is not None:
+ service_data[ATTR_XY_COLOR] = [x_val, y_val]
+ if brightness is not None:
+ service_data[ATTR_BRIGHTNESS] = brightness
+ service_data[ATTR_WHITE_VALUE] = brightness
+ if transition is not None:
+ service_data[ATTR_TRANSITION] = transition
+ hass.services.call(
+ LIGHT_DOMAIN, SERVICE_TURN_ON, service_data)
def set_lights_temp(hass, lights, mired, brightness, transition):
"""Set color of array of lights."""
for light in lights:
if is_on(hass, light):
- turn_on(hass, light,
- color_temp=int(mired),
- brightness=brightness,
- transition=transition)
+ service_data = {ATTR_ENTITY_ID: light}
+ if mired is not None:
+ service_data[ATTR_COLOR_TEMP] = int(mired)
+ if brightness is not None:
+ service_data[ATTR_BRIGHTNESS] = brightness
+ if transition is not None:
+ service_data[ATTR_TRANSITION] = transition
+ hass.services.call(
+ LIGHT_DOMAIN, SERVICE_TURN_ON, service_data)
def set_lights_rgb(hass, lights, rgb, transition):
"""Set color of array of lights."""
for light in lights:
if is_on(hass, light):
- turn_on(hass, light,
- rgb_color=rgb,
- transition=transition)
+ service_data = {ATTR_ENTITY_ID: light}
+ if rgb is not None:
+ service_data[ATTR_RGB_COLOR] = rgb
+ if transition is not None:
+ service_data[ATTR_TRANSITION] = transition
+ hass.services.call(
+ LIGHT_DOMAIN, SERVICE_TURN_ON, service_data)
def setup_platform(hass, config, add_entities, discovery_info=None):
diff --git a/homeassistant/components/switch/hook.py b/homeassistant/components/switch/hook.py
index 5a86346aa76..fdc13f59d28 100644
--- a/homeassistant/components/switch/hook.py
+++ b/homeassistant/components/switch/hook.py
@@ -31,9 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up Hook by getting the access token and list of actions."""
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
@@ -43,14 +42,14 @@ def async_setup_platform(hass, config, async_add_entities,
if username is not None and password is not None:
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
- response = yield from websession.post(
+ response = await websession.post(
'{}{}'.format(HOOK_ENDPOINT, 'user/login'),
data={
'username': username,
'password': password})
# The Hook API returns JSON but calls it 'text/html'. Setting
# content_type=None disables aiohttp's content-type validation.
- data = yield from response.json(content_type=None)
+ data = await response.json(content_type=None)
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed authentication API call: %s", error)
return False
@@ -63,10 +62,10 @@ def async_setup_platform(hass, config, async_add_entities,
try:
with async_timeout.timeout(TIMEOUT, loop=hass.loop):
- response = yield from websession.get(
+ response = await websession.get(
'{}{}'.format(HOOK_ENDPOINT, 'device'),
params={"token": token})
- data = yield from response.json(content_type=None)
+ data = await response.json(content_type=None)
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed getting devices: %s", error)
return False
@@ -104,16 +103,15 @@ class HookSmartHome(SwitchDevice):
"""Return true if device is on."""
return self._state
- @asyncio.coroutine
- def _send(self, url):
+ async def _send(self, url):
"""Send the url to the Hook API."""
try:
_LOGGER.debug("Sending: %s", url)
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(TIMEOUT, loop=self.hass.loop):
- response = yield from websession.get(
+ response = await websession.get(
url, params={"token": self._token})
- data = yield from response.json(content_type=None)
+ data = await response.json(content_type=None)
except (asyncio.TimeoutError, aiohttp.ClientError) as error:
_LOGGER.error("Failed setting state: %s", error)
@@ -122,21 +120,19 @@ class HookSmartHome(SwitchDevice):
_LOGGER.debug("Got: %s", data)
return data['return_value'] == '1'
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on asynchronously."""
_LOGGER.debug("Turning on: %s", self._name)
url = '{}{}{}{}'.format(
HOOK_ENDPOINT, 'device/trigger/', self._id, '/On')
- success = yield from self._send(url)
+ success = await self._send(url)
self._state = success
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the device off asynchronously."""
_LOGGER.debug("Turning off: %s", self._name)
url = '{}{}{}{}'.format(
HOOK_ENDPOINT, 'device/trigger/', self._id, '/Off')
- success = yield from self._send(url)
+ success = await self._send(url)
# If it wasn't successful, keep state as true
self._state = not success
diff --git a/homeassistant/components/switch/insteon.py b/homeassistant/components/switch/insteon.py
index 744d278d394..454b3ef39cb 100644
--- a/homeassistant/components/switch/insteon.py
+++ b/homeassistant/components/switch/insteon.py
@@ -4,7 +4,6 @@ Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/switch.insteon/
"""
-import asyncio
import logging
from homeassistant.components.insteon import InsteonEntity
@@ -15,9 +14,8 @@ DEPENDENCIES = ['insteon']
_LOGGER = logging.getLogger(__name__)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data['insteon'].get('modem')
@@ -48,13 +46,11 @@ class InsteonSwitchDevice(InsteonEntity, SwitchDevice):
"""Return the boolean response if the node is on."""
return bool(self._insteon_device_state.value)
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn device on."""
self._insteon_device_state.on()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn device off."""
self._insteon_device_state.off()
@@ -67,12 +63,10 @@ class InsteonOpenClosedDevice(InsteonEntity, SwitchDevice):
"""Return the boolean response if the node is on."""
return bool(self._insteon_device_state.value)
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn device on."""
self._insteon_device_state.open()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn device off."""
self._insteon_device_state.close()
diff --git a/homeassistant/components/switch/lutron_caseta.py b/homeassistant/components/switch/lutron_caseta.py
index 8587c78a5d5..f983050cffa 100644
--- a/homeassistant/components/switch/lutron_caseta.py
+++ b/homeassistant/components/switch/lutron_caseta.py
@@ -4,7 +4,6 @@ Support for Lutron Caseta switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sitch.lutron_caseta/
"""
-import asyncio
import logging
from homeassistant.components.lutron_caseta import (
@@ -16,9 +15,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['lutron_caseta']
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up Lutron switch."""
devs = []
bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE]
@@ -35,13 +33,11 @@ def async_setup_platform(hass, config, async_add_entities,
class LutronCasetaLight(LutronCasetaDevice, SwitchDevice):
"""Representation of a Lutron Caseta switch."""
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the switch on."""
self._smartbridge.turn_on(self._device_id)
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the switch off."""
self._smartbridge.turn_off(self._device_id)
@@ -50,8 +46,7 @@ class LutronCasetaLight(LutronCasetaDevice, SwitchDevice):
"""Return true if device is on."""
return self._state["current_state"] > 0
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update when forcing a refresh of the device."""
self._state = self._smartbridge.get_device_by_id(self._device_id)
_LOGGER.debug(self._state)
diff --git a/homeassistant/components/switch/mqtt.py b/homeassistant/components/switch/mqtt.py
index b79f8f12b87..bb57f179340 100644
--- a/homeassistant/components/switch/mqtt.py
+++ b/homeassistant/components/switch/mqtt.py
@@ -15,12 +15,15 @@ from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, MqttAvailability,
MqttDiscoveryUpdate)
+from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
CONF_NAME, CONF_OPTIMISTIC, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_OFF,
CONF_PAYLOAD_ON, CONF_ICON, STATE_ON)
-from homeassistant.components import mqtt
+from homeassistant.components import mqtt, switch
import homeassistant.helpers.config_validation as cv
+from homeassistant.helpers.dispatcher import async_dispatcher_connect
+from homeassistant.helpers.typing import HomeAssistantType, ConfigType
from homeassistant.helpers.restore_state import async_get_last_state
_LOGGER = logging.getLogger(__name__)
@@ -47,20 +50,33 @@ PLATFORM_SCHEMA = mqtt.MQTT_RW_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-async def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
- """Set up the MQTT switch."""
- if discovery_info is not None:
- config = PLATFORM_SCHEMA(discovery_info)
+async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
+ async_add_entities, discovery_info=None):
+ """Set up MQTT switch through configuration.yaml."""
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_info)
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Set up MQTT switch dynamically through MQTT discovery."""
+ async def async_discover(discovery_payload):
+ """Discover and add a MQTT switch."""
+ config = PLATFORM_SCHEMA(discovery_payload)
+ await _async_setup_entity(hass, config, async_add_entities,
+ discovery_payload[ATTR_DISCOVERY_HASH])
+
+ async_dispatcher_connect(
+ hass, MQTT_DISCOVERY_NEW.format(switch.DOMAIN, 'mqtt'),
+ async_discover)
+
+
+async def _async_setup_entity(hass, config, async_add_entities,
+ discovery_hash=None):
+ """Set up the MQTT switch."""
value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None:
value_template.hass = hass
- discovery_hash = None
- if discovery_info is not None and ATTR_DISCOVERY_HASH in discovery_info:
- discovery_hash = discovery_info[ATTR_DISCOVERY_HASH]
-
newswitch = MqttSwitch(
config.get(CONF_NAME),
config.get(CONF_ICON),
diff --git a/homeassistant/components/switch/netio.py b/homeassistant/components/switch/netio.py
index 2ccb4501d73..fc6086f9897 100644
--- a/homeassistant/components/switch/netio.py
+++ b/homeassistant/components/switch/netio.py
@@ -117,7 +117,7 @@ class NetioApiView(HomeAssistantView):
ndev.start_dates = start_dates
for dev in DEVICES[host].entities:
- hass.async_add_job(dev.async_update_ha_state())
+ hass.async_create_task(dev.async_update_ha_state())
return self.json(True)
diff --git a/homeassistant/components/switch/pilight.py b/homeassistant/components/switch/pilight.py
index 8ae8e64c2ff..16dfc075409 100644
--- a/homeassistant/components/switch/pilight.py
+++ b/homeassistant/components/switch/pilight.py
@@ -4,7 +4,6 @@ Support for switching devices via Pilight to on and off.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.pilight/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -122,10 +121,9 @@ class PilightSwitch(SwitchDevice):
if any(self._code_on_receive) or any(self._code_off_receive):
hass.bus.listen(pilight.EVENT, self._handle_code)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity about to be added to hass."""
- state = yield from async_get_last_state(self._hass, self.entity_id)
+ state = await async_get_last_state(self._hass, self.entity_id)
if state:
self._state = state.state == STATE_ON
diff --git a/homeassistant/components/switch/rachio.py b/homeassistant/components/switch/rachio.py
index 956befeeb9f..4797aae9a8c 100644
--- a/homeassistant/components/switch/rachio.py
+++ b/homeassistant/components/switch/rachio.py
@@ -7,10 +7,10 @@ https://home-assistant.io/components/switch.rachio/
from abc import abstractmethod
from datetime import timedelta
import logging
-import voluptuous as vol
-from homeassistant.components.switch import PLATFORM_SCHEMA, SwitchDevice
-from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO,
+from homeassistant.components.switch import SwitchDevice
+from homeassistant.components.rachio import (CONF_MANUAL_RUN_MINS,
+ DOMAIN as DOMAIN_RACHIO,
KEY_DEVICE_ID,
KEY_ENABLED,
KEY_ID,
@@ -27,29 +27,20 @@ from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO,
SUBTYPE_ZONE_COMPLETED,
SUBTYPE_SLEEP_MODE_ON,
SUBTYPE_SLEEP_MODE_OFF)
-import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_connect
DEPENDENCIES = ['rachio']
_LOGGER = logging.getLogger(__name__)
-# Manual run length
-CONF_MANUAL_RUN_MINS = 'manual_run_mins'
-DEFAULT_MANUAL_RUN_MINS = 10
-
-PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
- vol.Optional(CONF_MANUAL_RUN_MINS, default=DEFAULT_MANUAL_RUN_MINS):
- cv.positive_int,
-})
-
ATTR_ZONE_SUMMARY = 'Summary'
ATTR_ZONE_NUMBER = 'Zone number'
def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the Rachio switches."""
- manual_run_time = timedelta(minutes=config.get(CONF_MANUAL_RUN_MINS))
+ manual_run_time = timedelta(minutes=hass.data[DOMAIN_RACHIO].config.get(
+ CONF_MANUAL_RUN_MINS))
_LOGGER.info("Rachio run time is %s", str(manual_run_time))
# Add all zones from all controllers as switches
@@ -126,6 +117,11 @@ class RachioStandbySwitch(RachioSwitch):
"""Return the name of the standby switch."""
return "{} in standby mode".format(self._controller.name)
+ @property
+ def unique_id(self) -> str:
+ """Return a unique id by combinining controller id and purpose."""
+ return "{}-standby".format(self._controller.controller_id)
+
@property
def icon(self) -> str:
"""Return an icon for the standby switch."""
@@ -189,6 +185,12 @@ class RachioZone(RachioSwitch):
"""Return the friendly name of the zone."""
return self._zone_name
+ @property
+ def unique_id(self) -> str:
+ """Return a unique id by combinining controller id and zone number."""
+ return "{}-zone-{}".format(self._controller.controller_id,
+ self.zone_id)
+
@property
def icon(self) -> str:
"""Return the icon to display."""
diff --git a/homeassistant/components/switch/rest.py b/homeassistant/components/switch/rest.py
index 0e00bfe7844..9b8f889a8ae 100644
--- a/homeassistant/components/switch/rest.py
+++ b/homeassistant/components/switch/rest.py
@@ -47,9 +47,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the RESTful switch."""
body_off = config.get(CONF_BODY_OFF)
body_on = config.get(CONF_BODY_ON)
@@ -77,7 +76,7 @@ def async_setup_platform(hass, config, async_add_entities,
switch = RestSwitch(name, resource, method, headers, auth, body_on,
body_off, is_on_template, timeout)
- req = yield from switch.get_device_state(hass)
+ req = await switch.get_device_state(hass)
if req.status >= 400:
_LOGGER.error("Got non-ok response from resource: %s", req.status)
else:
@@ -116,13 +115,12 @@ class RestSwitch(SwitchDevice):
"""Return true if device is on."""
return self._state
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the device on."""
body_on_t = self._body_on.async_render()
try:
- req = yield from self.set_device_state(body_on_t)
+ req = await self.set_device_state(body_on_t)
if req.status == 200:
self._state = True
@@ -131,15 +129,14 @@ class RestSwitch(SwitchDevice):
"Can't turn on %s. Is resource/endpoint offline?",
self._resource)
except (asyncio.TimeoutError, aiohttp.ClientError):
- _LOGGER.error("Error while turn on %s", self._resource)
+ _LOGGER.error("Error while switching on %s", self._resource)
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the device off."""
body_off_t = self._body_off.async_render()
try:
- req = yield from self.set_device_state(body_off_t)
+ req = await self.set_device_state(body_off_t)
if req.status == 200:
self._state = False
else:
@@ -147,35 +144,35 @@ class RestSwitch(SwitchDevice):
"Can't turn off %s. Is resource/endpoint offline?",
self._resource)
except (asyncio.TimeoutError, aiohttp.ClientError):
- _LOGGER.error("Error while turn off %s", self._resource)
+ _LOGGER.error("Error while switching off %s", self._resource)
- @asyncio.coroutine
- def set_device_state(self, body):
+ async def set_device_state(self, body):
"""Send a state update to the device."""
websession = async_get_clientsession(self.hass)
with async_timeout.timeout(self._timeout, loop=self.hass.loop):
- req = yield from getattr(websession, self._method)(
+ req = await getattr(websession, self._method)(
self._resource, auth=self._auth, data=bytes(body, 'utf-8'),
headers=self._headers)
return req
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Get the current state, catching errors."""
try:
- yield from self.get_device_state(self.hass)
- except (asyncio.TimeoutError, aiohttp.ClientError):
- _LOGGER.exception("Error while fetch data.")
+ await self.get_device_state(self.hass)
+ except asyncio.TimeoutError:
+ _LOGGER.exception("Timed out while fetching data")
+ except aiohttp.ClientError as err:
+ _LOGGER.exception("Error while fetching data: %s", err)
- @asyncio.coroutine
- def get_device_state(self, hass):
+ async def get_device_state(self, hass):
"""Get the latest data from REST API and update the state."""
websession = async_get_clientsession(hass)
with async_timeout.timeout(self._timeout, loop=hass.loop):
- req = yield from websession.get(self._resource, auth=self._auth)
- text = yield from req.text()
+ req = await websession.get(self._resource, auth=self._auth,
+ headers=self._headers)
+ text = await req.text()
if self._is_on_template is not None:
text = self._is_on_template.async_render_with_possible_json_value(
diff --git a/homeassistant/components/switch/rflink.py b/homeassistant/components/switch/rflink.py
index 370436b3184..2bbe3e3f03d 100644
--- a/homeassistant/components/switch/rflink.py
+++ b/homeassistant/components/switch/rflink.py
@@ -4,7 +4,6 @@ Support for Rflink switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.rflink/
"""
-import asyncio
import logging
from homeassistant.components.rflink import (
@@ -85,9 +84,8 @@ def devices_from_config(domain_config, hass=None):
return devices
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Rflink platform."""
async_add_entities(devices_from_config(config, hass))
diff --git a/homeassistant/components/switch/template.py b/homeassistant/components/switch/template.py
index 7461aa2a720..724fcbf6075 100644
--- a/homeassistant/components/switch/template.py
+++ b/homeassistant/components/switch/template.py
@@ -4,7 +4,6 @@ Support for switches which integrates with other components.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.template/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -43,9 +42,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Template switch."""
switches = []
@@ -103,8 +101,7 @@ class SwitchTemplate(SwitchDevice):
self._entity_picture = None
self._entities = entity_ids
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
@callback
def template_switch_state_listener(entity, old_state, new_state):
@@ -152,18 +149,15 @@ class SwitchTemplate(SwitchDevice):
"""Return the entity_picture to use in the frontend, if any."""
return self._entity_picture
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Fire the on action."""
- yield from self._on_script.async_run()
+ await self._on_script.async_run()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Fire the off action."""
- yield from self._off_script.async_run()
+ await self._off_script.async_run()
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Update the state from the template."""
try:
state = self._template.async_render().lower()
diff --git a/homeassistant/components/switch/tradfri.py b/homeassistant/components/switch/tradfri.py
new file mode 100644
index 00000000000..74997332b07
--- /dev/null
+++ b/homeassistant/components/switch/tradfri.py
@@ -0,0 +1,137 @@
+"""
+Support for the IKEA Tradfri platform.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/switch.tradfri/
+"""
+import logging
+
+from homeassistant.core import callback
+from homeassistant.components.switch import SwitchDevice
+from homeassistant.components.tradfri import (
+ KEY_GATEWAY, KEY_API, DOMAIN as TRADFRI_DOMAIN)
+from homeassistant.components.tradfri.const import (
+ CONF_GATEWAY_ID)
+
+_LOGGER = logging.getLogger(__name__)
+
+DEPENDENCIES = ['tradfri']
+IKEA = 'IKEA of Sweden'
+TRADFRI_SWITCH_MANAGER = 'Tradfri Switch Manager'
+
+
+async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Load Tradfri switches based on a config entry."""
+ gateway_id = config_entry.data[CONF_GATEWAY_ID]
+ api = hass.data[KEY_API][config_entry.entry_id]
+ gateway = hass.data[KEY_GATEWAY][config_entry.entry_id]
+
+ devices_commands = await api(gateway.get_devices())
+ devices = await api(devices_commands)
+ switches = [dev for dev in devices if dev.has_socket_control]
+ if switches:
+ async_add_entities(
+ TradfriSwitch(switch, api, gateway_id) for switch in switches)
+
+
+class TradfriSwitch(SwitchDevice):
+ """The platform class required by Home Assistant."""
+
+ def __init__(self, switch, api, gateway_id):
+ """Initialize a switch."""
+ self._api = api
+ self._unique_id = "{}-{}".format(gateway_id, switch.id)
+ self._switch = None
+ self._socket_control = None
+ self._switch_data = None
+ self._name = None
+ self._available = True
+ self._gateway_id = gateway_id
+
+ self._refresh(switch)
+
+ @property
+ def unique_id(self):
+ """Return unique ID for switch."""
+ return self._unique_id
+
+ @property
+ def device_info(self):
+ """Return the device info."""
+ info = self._switch.device_info
+
+ return {
+ 'identifiers': {
+ (TRADFRI_DOMAIN, self._switch.id)
+ },
+ 'name': self._name,
+ 'manufacturer': info.manufacturer,
+ 'model': info.model_number,
+ 'sw_version': info.firmware_version,
+ 'via_hub': (TRADFRI_DOMAIN, self._gateway_id),
+ }
+
+ async def async_added_to_hass(self):
+ """Start thread when added to hass."""
+ self._async_start_observe()
+
+ @property
+ def available(self):
+ """Return True if entity is available."""
+ return self._available
+
+ @property
+ def should_poll(self):
+ """No polling needed for tradfri switch."""
+ return False
+
+ @property
+ def name(self):
+ """Return the display name of this switch."""
+ return self._name
+
+ @property
+ def is_on(self):
+ """Return true if switch is on."""
+ return self._switch_data.state
+
+ async def async_turn_off(self, **kwargs):
+ """Instruct the switch to turn off."""
+ await self._api(self._socket_control.set_state(False))
+
+ async def async_turn_on(self, **kwargs):
+ """Instruct the switch to turn on."""
+ await self._api(self._socket_control.set_state(True))
+
+ @callback
+ def _async_start_observe(self, exc=None):
+ """Start observation of switch."""
+ from pytradfri.error import PytradfriError
+ if exc:
+ _LOGGER.warning("Observation failed for %s", self._name,
+ exc_info=exc)
+
+ try:
+ cmd = self._switch.observe(callback=self._observe_update,
+ err_callback=self._async_start_observe,
+ duration=0)
+ self.hass.async_create_task(self._api(cmd))
+ except PytradfriError as err:
+ _LOGGER.warning("Observation failed, trying again", exc_info=err)
+ self._async_start_observe()
+
+ def _refresh(self, switch):
+ """Refresh the switch data."""
+ self._switch = switch
+
+ # Caching of switchControl and switch object
+ self._available = switch.reachable
+ self._socket_control = switch.socket_control
+ self._switch_data = switch.socket_control.sockets[0]
+ self._name = switch.name
+
+ @callback
+ def _observe_update(self, tradfri_device):
+ """Receive new state data for this switch."""
+ self._refresh(tradfri_device)
+ self.async_schedule_update_ha_state()
diff --git a/homeassistant/components/switch/volvooncall.py b/homeassistant/components/switch/volvooncall.py
index 96091a725a1..42c753725ab 100644
--- a/homeassistant/components/switch/volvooncall.py
+++ b/homeassistant/components/switch/volvooncall.py
@@ -15,7 +15,7 @@ _LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_entities, discovery_info=None):
- """Set up Tellstick switches."""
+ """Set up a Volvo switch."""
if discovery_info is None:
return
add_entities([VolvoSwitch(hass, *discovery_info)])
diff --git a/homeassistant/components/switch/wink.py b/homeassistant/components/switch/wink.py
index 0df59d6b51c..9dea93488af 100644
--- a/homeassistant/components/switch/wink.py
+++ b/homeassistant/components/switch/wink.py
@@ -4,7 +4,6 @@ Support for Wink switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.wink/
"""
-import asyncio
import logging
from homeassistant.components.wink import DOMAIN, WinkDevice
@@ -40,8 +39,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class WinkToggleDevice(WinkDevice, ToggleEntity):
"""Representation of a Wink toggle device."""
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['switch'].append(self)
diff --git a/homeassistant/components/switch/xiaomi_aqara.py b/homeassistant/components/switch/xiaomi_aqara.py
index a29a3c74a2e..17265d5dfa2 100644
--- a/homeassistant/components/switch/xiaomi_aqara.py
+++ b/homeassistant/components/switch/xiaomi_aqara.py
@@ -99,6 +99,11 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchDevice):
attrs.update(super().device_state_attributes)
return attrs
+ @property
+ def should_poll(self):
+ """Return the polling state. Polling needed for zigbee plug only."""
+ return self._supports_power_consumption
+
def turn_on(self, **kwargs):
"""Turn the switch on."""
if self._write_to_hub(self._sid, **{self._data_key: 'on'}):
@@ -131,3 +136,8 @@ class XiaomiGenericSwitch(XiaomiDevice, SwitchDevice):
return False
self._state = state
return True
+
+ def update(self):
+ """Get data from hub."""
+ _LOGGER.debug("Update data from hub: %s", self._name)
+ self._get_from_hub(self._sid)
diff --git a/homeassistant/components/system_log/__init__.py b/homeassistant/components/system_log/__init__.py
index 2a2a19aa2f5..8ab6bd752ef 100644
--- a/homeassistant/components/system_log/__init__.py
+++ b/homeassistant/components/system_log/__init__.py
@@ -4,7 +4,6 @@ Support for system log.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/system_log/
"""
-import asyncio
from collections import deque
from io import StringIO
import logging
@@ -134,8 +133,7 @@ class LogErrorHandler(logging.Handler):
self.hass.bus.fire(EVENT_SYSTEM_LOG, entry)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the logger component."""
conf = config.get(DOMAIN)
if conf is None:
@@ -147,8 +145,7 @@ def async_setup(hass, config):
hass.http.register_view(AllErrorsView(handler))
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Handle logger services."""
if service.service == 'clear':
handler.records.clear()
@@ -159,8 +156,7 @@ def async_setup(hass, config):
level = service.data[CONF_LEVEL]
getattr(logger, level)(service.data[CONF_MESSAGE])
- @asyncio.coroutine
- def async_shutdown_handler(event):
+ async def async_shutdown_handler(event):
"""Remove logging handler when Home Assistant is shutdown."""
# This is needed as older logger instances will remain
logging.getLogger().removeHandler(handler)
@@ -188,8 +184,7 @@ class AllErrorsView(HomeAssistantView):
"""Initialize a new AllErrorsView."""
self.handler = handler
- @asyncio.coroutine
- def get(self, request):
+ async def get(self, request):
"""Get all errors and warnings."""
# deque is not serializable (it's just "list-like") so it must be
# converted to a list before it can be serialized to json
diff --git a/homeassistant/components/telegram_bot/__init__.py b/homeassistant/components/telegram_bot/__init__.py
index 8e24716ab57..40724a1ee86 100644
--- a/homeassistant/components/telegram_bot/__init__.py
+++ b/homeassistant/components/telegram_bot/__init__.py
@@ -4,7 +4,6 @@ Component to send and receive Telegram messages.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/telegram_bot/
"""
-import asyncio
import io
from functools import partial
import logging
@@ -210,8 +209,7 @@ def load_data(hass, url=None, filepath=None, username=None, password=None,
return None
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the Telegram bot component."""
if not config[DOMAIN]:
return False
@@ -220,7 +218,7 @@ def async_setup(hass, config):
p_type = p_config.get(CONF_PLATFORM)
- platform = yield from async_prepare_setup_platform(
+ platform = await async_prepare_setup_platform(
hass, config, DOMAIN, p_type)
if platform is None:
@@ -228,7 +226,7 @@ def async_setup(hass, config):
_LOGGER.info("Setting up %s.%s", DOMAIN, p_type)
try:
- receiver_service = yield from \
+ receiver_service = await \
platform.async_setup_platform(hass, p_config)
if receiver_service is False:
_LOGGER.error(
@@ -247,8 +245,7 @@ def async_setup(hass, config):
p_config.get(ATTR_PARSER)
)
- @asyncio.coroutine
- def async_send_telegram_message(service):
+ async def async_send_telegram_message(service):
"""Handle sending Telegram Bot message service calls."""
def _render_template_attr(data, attribute):
attribute_templ = data.get(attribute)
@@ -274,23 +271,23 @@ def async_setup(hass, config):
_LOGGER.debug("New telegram message %s: %s", msgtype, kwargs)
if msgtype == SERVICE_SEND_MESSAGE:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(notify_service.send_message, **kwargs))
elif msgtype in [SERVICE_SEND_PHOTO, SERVICE_SEND_STICKER,
SERVICE_SEND_VIDEO, SERVICE_SEND_DOCUMENT]:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(notify_service.send_file, msgtype, **kwargs))
elif msgtype == SERVICE_SEND_LOCATION:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(notify_service.send_location, **kwargs))
elif msgtype == SERVICE_ANSWER_CALLBACK_QUERY:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(notify_service.answer_callback_query, **kwargs))
elif msgtype == SERVICE_DELETE_MESSAGE:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(notify_service.delete_message, **kwargs))
else:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(notify_service.edit_message, msgtype, **kwargs))
# Register notification services
@@ -311,10 +308,11 @@ def initialize_bot(p_config):
proxy_url = p_config.get(CONF_PROXY_URL)
proxy_params = p_config.get(CONF_PROXY_PARAMS)
- request = None
if proxy_url is not None:
- request = Request(proxy_url=proxy_url,
+ request = Request(con_pool_size=4, proxy_url=proxy_url,
urllib3_proxy_kwargs=proxy_params)
+ else:
+ request = Request(con_pool_size=4)
return Bot(token=api_key, request=request)
diff --git a/homeassistant/components/telegram_bot/broadcast.py b/homeassistant/components/telegram_bot/broadcast.py
index 7e157fdb0c7..7cfcc272a33 100644
--- a/homeassistant/components/telegram_bot/broadcast.py
+++ b/homeassistant/components/telegram_bot/broadcast.py
@@ -4,7 +4,6 @@ Telegram bot implementation to send messages only.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/telegram_bot.broadcast/
"""
-import asyncio
import logging
from homeassistant.components.telegram_bot import (
@@ -16,14 +15,13 @@ _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = TELEGRAM_PLATFORM_SCHEMA
-@asyncio.coroutine
-def async_setup_platform(hass, config):
+async def async_setup_platform(hass, config):
"""Set up the Telegram broadcast platform."""
# Check the API key works
bot = initialize_bot(config)
- bot_config = yield from hass.async_add_job(bot.getMe)
+ bot_config = await hass.async_add_job(bot.getMe)
_LOGGER.debug("Telegram broadcast platform setup with bot %s",
bot_config['username'])
return True
diff --git a/homeassistant/components/telegram_bot/polling.py b/homeassistant/components/telegram_bot/polling.py
index 6ee42b32504..d1dea051985 100644
--- a/homeassistant/components/telegram_bot/polling.py
+++ b/homeassistant/components/telegram_bot/polling.py
@@ -4,14 +4,8 @@ Telegram bot polling implementation.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/telegram_bot.polling/
"""
-import asyncio
-from asyncio.futures import CancelledError
import logging
-import async_timeout
-from aiohttp.client_exceptions import ClientError
-from aiohttp.hdrs import CONNECTION, KEEP_ALIVE
-
from homeassistant.components.telegram_bot import (
initialize_bot,
CONF_ALLOWED_CHAT_IDS, BaseTelegramBotEntity,
@@ -19,22 +13,13 @@ from homeassistant.components.telegram_bot import (
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
from homeassistant.core import callback
-from homeassistant.helpers.aiohttp_client import async_get_clientsession
_LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = TELEGRAM_PLATFORM_SCHEMA
-RETRY_SLEEP = 10
-class WrongHttpStatus(Exception):
- """Thrown when a wrong http status is received."""
-
- pass
-
-
-@asyncio.coroutine
-def async_setup_platform(hass, config):
+async def async_setup_platform(hass, config):
"""Set up the Telegram polling platform."""
bot = initialize_bot(config)
pol = TelegramPoll(bot, hass, config[CONF_ALLOWED_CHAT_IDS])
@@ -55,73 +40,67 @@ def async_setup_platform(hass, config):
return True
+def process_error(bot, update, error):
+ """Telegram bot error handler."""
+ from telegram.error import (
+ TelegramError, TimedOut, NetworkError, RetryAfter)
+
+ try:
+ raise error
+ except (TimedOut, NetworkError, RetryAfter):
+ # Long polling timeout or connection problem. Nothing serious.
+ pass
+ except TelegramError:
+ _LOGGER.error('Update "%s" caused error "%s"', update, error)
+
+
+def message_handler(handler):
+ """Create messages handler."""
+ from telegram import Update
+ from telegram.ext import Handler
+
+ class MessageHandler(Handler):
+ """Telegram bot message handler."""
+
+ def __init__(self):
+ """Initialize the messages handler instance."""
+ super().__init__(handler)
+
+ def check_update(self, update):
+ """Check is update valid."""
+ return isinstance(update, Update)
+
+ def handle_update(self, update, dispatcher):
+ """Handle update."""
+ optional_args = self.collect_optional_args(dispatcher, update)
+ return self.callback(dispatcher.bot, update, **optional_args)
+
+ return MessageHandler()
+
+
class TelegramPoll(BaseTelegramBotEntity):
"""Asyncio telegram incoming message handler."""
def __init__(self, bot, hass, allowed_chat_ids):
"""Initialize the polling instance."""
+ from telegram.ext import Updater
+
BaseTelegramBotEntity.__init__(self, hass, allowed_chat_ids)
- self.update_id = 0
- self.websession = async_get_clientsession(hass)
- self.update_url = '{0}/getUpdates'.format(bot.base_url)
- self.polling_task = None # The actual polling task.
- self.timeout = 15 # async post timeout
- # Polling timeout should always be less than async post timeout.
- self.post_data = {'timeout': self.timeout - 5}
+
+ self.updater = Updater(bot=bot, workers=4)
+ self.dispatcher = self.updater.dispatcher
+
+ self.dispatcher.add_handler(message_handler(self.process_update))
+ self.dispatcher.add_error_handler(process_error)
def start_polling(self):
"""Start the polling task."""
- self.polling_task = self.hass.async_add_job(self.check_incoming())
+ self.updater.start_polling()
def stop_polling(self):
"""Stop the polling task."""
- self.polling_task.cancel()
+ self.updater.stop()
- @asyncio.coroutine
- def get_updates(self, offset):
- """Bypass the default long polling method to enable asyncio."""
- resp = None
- if offset:
- self.post_data['offset'] = offset
- try:
- with async_timeout.timeout(self.timeout, loop=self.hass.loop):
- resp = yield from self.websession.post(
- self.update_url, data=self.post_data,
- headers={CONNECTION: KEEP_ALIVE}
- )
- if resp.status == 200:
- _json = yield from resp.json()
- return _json
- raise WrongHttpStatus('wrong status {}'.format(resp.status))
- finally:
- if resp is not None:
- yield from resp.release()
-
- @asyncio.coroutine
- def check_incoming(self):
- """Continuously check for incoming telegram messages."""
- try:
- while True:
- try:
- _updates = yield from self.get_updates(self.update_id)
- except (WrongHttpStatus, ClientError) as err:
- # WrongHttpStatus: Non-200 status code.
- # Occurs at times (mainly 502) and recovers
- # automatically. Pause for a while before retrying.
- _LOGGER.error(err)
- yield from asyncio.sleep(RETRY_SLEEP)
- except (asyncio.TimeoutError, ValueError):
- # Long polling timeout. Nothing serious.
- # Json error. Just retry for the next message.
- pass
- else:
- # no exception raised. update received data.
- _updates = _updates.get('result')
- if _updates is None:
- _LOGGER.error("Incorrect result received.")
- else:
- for update in _updates:
- self.update_id = update['update_id'] + 1
- self.process_message(update)
- except CancelledError:
- _LOGGER.debug("Stopping Telegram polling bot")
+ def process_update(self, bot, update):
+ """Process incoming message."""
+ self.process_message(update.to_dict())
diff --git a/homeassistant/components/telegram_bot/webhooks.py b/homeassistant/components/telegram_bot/webhooks.py
index b7dd7ab8269..72e8c557fe5 100644
--- a/homeassistant/components/telegram_bot/webhooks.py
+++ b/homeassistant/components/telegram_bot/webhooks.py
@@ -4,7 +4,6 @@ Allows utilizing telegram webhooks.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/telegram_bot.webhooks/
"""
-import asyncio
import datetime as dt
from ipaddress import ip_network
import logging
@@ -47,13 +46,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config):
+async def async_setup_platform(hass, config):
"""Set up the Telegram webhooks platform."""
import telegram
bot = initialize_bot(config)
- current_status = yield from hass.async_add_job(bot.getWebhookInfo)
+ current_status = await hass.async_add_job(bot.getWebhookInfo)
base_url = config.get(CONF_URL, hass.config.api.base_url)
# Some logging of Bot current status:
@@ -81,7 +79,7 @@ def async_setup_platform(hass, config):
retry_num)
if current_status and current_status['url'] != handler_url:
- result = yield from hass.async_add_job(_try_to_set_webhook)
+ result = await hass.async_add_job(_try_to_set_webhook)
if result:
_LOGGER.info("Set new telegram webhook %s", handler_url)
else:
@@ -108,8 +106,7 @@ class BotPushReceiver(HomeAssistantView, BaseTelegramBotEntity):
BaseTelegramBotEntity.__init__(self, hass, allowed_chat_ids)
self.trusted_networks = trusted_networks
- @asyncio.coroutine
- def post(self, request):
+ async def post(self, request):
"""Accept the POST from telegram."""
real_ip = request[KEY_REAL_IP]
if not any(real_ip in net for net in self.trusted_networks):
@@ -117,7 +114,7 @@ class BotPushReceiver(HomeAssistantView, BaseTelegramBotEntity):
return self.json_message('Access denied', HTTP_UNAUTHORIZED)
try:
- data = yield from request.json()
+ data = await request.json()
except ValueError:
return self.json_message('Invalid JSON', HTTP_BAD_REQUEST)
diff --git a/homeassistant/components/tellstick.py b/homeassistant/components/tellstick.py
index 0eef2c4ece1..8f1c45d7312 100644
--- a/homeassistant/components/tellstick.py
+++ b/homeassistant/components/tellstick.py
@@ -4,7 +4,6 @@ Tellstick Component.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/tellstick/
"""
-import asyncio
import logging
import threading
@@ -158,8 +157,7 @@ class TellstickDevice(Entity):
self._tellcore_device = tellcore_device
self._name = tellcore_device.name
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_TELLCORE_CALLBACK,
diff --git a/homeassistant/components/thethingsnetwork.py b/homeassistant/components/thethingsnetwork.py
index 08715c74d1f..61f9843be45 100644
--- a/homeassistant/components/thethingsnetwork.py
+++ b/homeassistant/components/thethingsnetwork.py
@@ -4,7 +4,6 @@ Support for The Things network.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/thethingsnetwork/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -32,8 +31,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Initialize of The Things Network component."""
conf = config[DOMAIN]
app_id = conf.get(CONF_APP_ID)
diff --git a/homeassistant/components/tibber/__init__.py b/homeassistant/components/tibber/__init__.py
new file mode 100644
index 00000000000..8022902c580
--- /dev/null
+++ b/homeassistant/components/tibber/__init__.py
@@ -0,0 +1,55 @@
+"""
+Support for Tibber.
+
+For more details about this platform, please refer to the documentation at
+https://home-assistant.io/components/tibber/
+"""
+import asyncio
+import logging
+
+import aiohttp
+import voluptuous as vol
+
+import homeassistant.helpers.config_validation as cv
+from homeassistant.const import (EVENT_HOMEASSISTANT_STOP, CONF_ACCESS_TOKEN,
+ CONF_NAME)
+from homeassistant.helpers import discovery
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+
+REQUIREMENTS = ['pyTibber==0.7.2']
+
+DOMAIN = 'tibber'
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Required(CONF_ACCESS_TOKEN): cv.string,
+ })
+}, extra=vol.ALLOW_EXTRA)
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def async_setup(hass, config):
+ """Set up the Tibber component."""
+ conf = config.get(DOMAIN)
+
+ import tibber
+ tibber_connection = tibber.Tibber(conf[CONF_ACCESS_TOKEN],
+ websession=async_get_clientsession(hass))
+ hass.data[DOMAIN] = tibber_connection
+
+ async def _close(event):
+ await tibber_connection.rt_disconnect()
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, _close)
+
+ try:
+ await tibber_connection.update_info()
+ except (asyncio.TimeoutError, aiohttp.ClientError):
+ return False
+
+ for component in ['sensor', 'notify']:
+ discovery.load_platform(hass, component, DOMAIN,
+ {CONF_NAME: DOMAIN}, config)
+
+ return True
diff --git a/homeassistant/components/timer/__init__.py b/homeassistant/components/timer/__init__.py
index 8406b3ff5ec..c29df9db858 100644
--- a/homeassistant/components/timer/__init__.py
+++ b/homeassistant/components/timer/__init__.py
@@ -12,12 +12,10 @@ import voluptuous as vol
import homeassistant.util.dt as dt_util
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (ATTR_ENTITY_ID, CONF_ICON, CONF_NAME)
-from homeassistant.core import callback
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.event import async_track_point_in_utc_time
-from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__)
@@ -63,64 +61,6 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA)
-@bind_hass
-def start(hass, entity_id, duration):
- """Start a timer."""
- hass.add_job(async_start, hass, entity_id, {ATTR_ENTITY_ID: entity_id,
- ATTR_DURATION: duration})
-
-
-@callback
-@bind_hass
-def async_start(hass, entity_id, duration):
- """Start a timer."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_START, {ATTR_ENTITY_ID: entity_id,
- ATTR_DURATION: duration}))
-
-
-@bind_hass
-def pause(hass, entity_id):
- """Pause a timer."""
- hass.add_job(async_pause, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_pause(hass, entity_id):
- """Pause a timer."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_PAUSE, {ATTR_ENTITY_ID: entity_id}))
-
-
-@bind_hass
-def cancel(hass, entity_id):
- """Cancel a timer."""
- hass.add_job(async_cancel, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_cancel(hass, entity_id):
- """Cancel a timer."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_CANCEL, {ATTR_ENTITY_ID: entity_id}))
-
-
-@bind_hass
-def finish(hass, entity_id):
- """Finish a timer."""
- hass.add_job(async_cancel, hass, entity_id)
-
-
-@callback
-@bind_hass
-def async_finish(hass, entity_id):
- """Finish a timer."""
- hass.async_add_job(hass.services.async_call(
- DOMAIN, SERVICE_FINISH, {ATTR_ENTITY_ID: entity_id}))
-
-
async def async_setup(hass, config):
"""Set up a timer."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
diff --git a/homeassistant/components/tradfri/.translations/de.json b/homeassistant/components/tradfri/.translations/de.json
index 5284ae18b6d..4a19972774d 100644
--- a/homeassistant/components/tradfri/.translations/de.json
+++ b/homeassistant/components/tradfri/.translations/de.json
@@ -11,6 +11,7 @@
"step": {
"auth": {
"data": {
+ "host": "Host",
"security_code": "Sicherheitscode"
},
"description": "Du findest den Sicherheitscode auf der R\u00fcckseite deines Gateways.",
diff --git a/homeassistant/components/tradfri/.translations/hu.json b/homeassistant/components/tradfri/.translations/hu.json
index 0844e6d7095..dc7c033d41d 100644
--- a/homeassistant/components/tradfri/.translations/hu.json
+++ b/homeassistant/components/tradfri/.translations/hu.json
@@ -1,11 +1,20 @@
{
"config": {
+ "abort": {
+ "already_configured": "A bridge m\u00e1r konfigur\u00e1lva van"
+ },
+ "error": {
+ "cannot_connect": "Nem siker\u00fclt csatlakozni a gatewayhez.",
+ "timeout": "Id\u0151t\u00fall\u00e9p\u00e9s a k\u00f3d \u00e9rv\u00e9nyes\u00edt\u00e9se sor\u00e1n."
+ },
"step": {
"auth": {
"data": {
"host": "Hoszt",
"security_code": "Biztons\u00e1gi K\u00f3d"
- }
+ },
+ "description": "A biztons\u00e1gi k\u00f3dot a Gatewayed h\u00e1toldal\u00e1n tal\u00e1lod.",
+ "title": "Add meg a biztons\u00e1gi k\u00f3dot"
}
},
"title": "IKEA TR\u00c5DFRI"
diff --git a/homeassistant/components/tradfri/.translations/pl.json b/homeassistant/components/tradfri/.translations/pl.json
index ec253447ef4..4fd71567afe 100644
--- a/homeassistant/components/tradfri/.translations/pl.json
+++ b/homeassistant/components/tradfri/.translations/pl.json
@@ -17,6 +17,7 @@
"description": "Mo\u017cesz znale\u017a\u0107 kod bezpiecze\u0144stwa z ty\u0142u bramy.",
"title": "Wprowad\u017a kod bezpiecze\u0144stwa"
}
- }
+ },
+ "title": "IKEA TR\u00c5DFRI"
}
}
\ No newline at end of file
diff --git a/homeassistant/components/tradfri/.translations/sv.json b/homeassistant/components/tradfri/.translations/sv.json
index ffe8bff22b4..34799050539 100644
--- a/homeassistant/components/tradfri/.translations/sv.json
+++ b/homeassistant/components/tradfri/.translations/sv.json
@@ -4,11 +4,14 @@
"already_configured": "Bryggan \u00e4r redan konfigurerad"
},
"error": {
- "cannot_connect": "Det gick inte att ansluta till gatewayen."
+ "cannot_connect": "Det gick inte att ansluta till gatewayen.",
+ "invalid_key": "Misslyckades med att registrera den angivna nyckeln. Om det h\u00e4r h\u00e4nder, f\u00f6rs\u00f6k starta om gatewayen igen.",
+ "timeout": "Timeout vid valididering av kod"
},
"step": {
"auth": {
"data": {
+ "host": "V\u00e4rd",
"security_code": "S\u00e4kerhetskod"
},
"description": "Du kan hitta s\u00e4kerhetskoden p\u00e5 baksidan av din gateway.",
diff --git a/homeassistant/components/tradfri/__init__.py b/homeassistant/components/tradfri/__init__.py
index 6e91ab338a3..ba13b8d511a 100644
--- a/homeassistant/components/tradfri/__init__.py
+++ b/homeassistant/components/tradfri/__init__.py
@@ -9,6 +9,7 @@ import logging
import voluptuous as vol
from homeassistant import config_entries
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
import homeassistant.helpers.config_validation as cv
from homeassistant.util.json import load_json
@@ -17,18 +18,18 @@ from .const import (
from . import config_flow # noqa pylint_disable=unused-import
-REQUIREMENTS = ['pytradfri[async]==5.5.1']
+REQUIREMENTS = ['pytradfri[async]==6.0.1']
DOMAIN = 'tradfri'
CONFIG_FILE = '.tradfri_psk.conf'
KEY_GATEWAY = 'tradfri_gateway'
KEY_API = 'tradfri_api'
CONF_ALLOW_TRADFRI_GROUPS = 'allow_tradfri_groups'
-DEFAULT_ALLOW_TRADFRI_GROUPS = True
+DEFAULT_ALLOW_TRADFRI_GROUPS = False
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
- vol.Inclusive(CONF_HOST, 'gateway'): cv.string,
+ vol.Optional(CONF_HOST): cv.string,
vol.Optional(CONF_ALLOW_TRADFRI_GROUPS,
default=DEFAULT_ALLOW_TRADFRI_GROUPS): cv.boolean,
})
@@ -63,13 +64,14 @@ async def async_setup(hass, config):
))
host = conf.get(CONF_HOST)
+ import_groups = conf[CONF_ALLOW_TRADFRI_GROUPS]
if host is None or host in configured_hosts or host in legacy_hosts:
return True
hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, context={'source': config_entries.SOURCE_IMPORT},
- data={'host': host}
+ data={CONF_HOST: host, CONF_IMPORT_GROUPS: import_groups}
))
return True
@@ -87,6 +89,13 @@ async def async_setup_entry(hass, entry):
psk=entry.data[CONF_KEY],
loop=hass.loop
)
+
+ async def on_hass_stop(event):
+ """Close connection when hass stops."""
+ await factory.shutdown()
+
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop)
+
api = factory.request
gateway = Gateway()
@@ -119,5 +128,8 @@ async def async_setup_entry(hass, entry):
hass.async_create_task(hass.config_entries.async_forward_entry_setup(
entry, 'sensor'
))
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ entry, 'switch'
+ ))
return True
diff --git a/homeassistant/components/tradfri/config_flow.py b/homeassistant/components/tradfri/config_flow.py
index 29aa768dbb5..2e24fde8294 100644
--- a/homeassistant/components/tradfri/config_flow.py
+++ b/homeassistant/components/tradfri/config_flow.py
@@ -34,6 +34,7 @@ class FlowHandler(config_entries.ConfigFlow):
def __init__(self):
"""Initialize flow."""
self._host = None
+ self._import_groups = False
async def async_step_user(self, user_input=None):
"""Handle a flow initialized by the user."""
@@ -52,7 +53,8 @@ class FlowHandler(config_entries.ConfigFlow):
# We don't ask for import group anymore as group state
# is not reliable, don't want to show that to the user.
- auth[CONF_IMPORT_GROUPS] = False
+ # But we still allow specifying import group via config yaml.
+ auth[CONF_IMPORT_GROUPS] = self._import_groups
return await self._entry_from_data(auth)
@@ -97,6 +99,7 @@ class FlowHandler(config_entries.ConfigFlow):
# Happens if user has host directly in configuration.yaml
if 'key' not in user_input:
self._host = user_input['host']
+ self._import_groups = user_input[CONF_IMPORT_GROUPS]
return await self.async_step_auth()
try:
@@ -166,10 +169,15 @@ async def get_gateway_info(hass, host, identity, key):
psk=key,
loop=hass.loop
)
+
api = factory.request
gateway = Gateway()
gateway_info_result = await api(gateway.get_gateway_info())
- except RequestError:
+
+ await factory.shutdown()
+ except (OSError, RequestError):
+ # We're also catching OSError as PyTradfri doesn't catch that one yet
+ # Upstream PR: https://github.com/ggravlingen/pytradfri/pull/189
raise AuthError('cannot_connect')
return {
diff --git a/homeassistant/components/tts/__init__.py b/homeassistant/components/tts/__init__.py
index e5eef0e6135..86667782d09 100644
--- a/homeassistant/components/tts/__init__.py
+++ b/homeassistant/components/tts/__init__.py
@@ -299,7 +299,7 @@ class SpeechManager:
# Is file store in file cache
elif use_cache and key in self.file_cache:
filename = self.file_cache[key]
- self.hass.async_add_job(self.async_file_to_mem(key))
+ self.hass.async_create_task(self.async_file_to_mem(key))
# Load speech from provider into memory
else:
filename = await self.async_get_tts_audio(
@@ -331,7 +331,7 @@ class SpeechManager:
self._async_store_to_memcache(key, filename, data)
if cache:
- self.hass.async_add_job(
+ self.hass.async_create_task(
self.async_save_tts_audio(key, filename, data))
return filename
diff --git a/homeassistant/components/tts/marytts.py b/homeassistant/components/tts/marytts.py
index 072ea0e76e7..61f01a9b292 100644
--- a/homeassistant/components/tts/marytts.py
+++ b/homeassistant/components/tts/marytts.py
@@ -45,8 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_get_engine(hass, config):
+async def async_get_engine(hass, config):
"""Set up MaryTTS speech component."""
return MaryTTSProvider(hass, config)
@@ -74,8 +73,7 @@ class MaryTTSProvider(Provider):
"""Return list of supported languages."""
return SUPPORT_LANGUAGES
- @asyncio.coroutine
- def async_get_tts_audio(self, message, language, options=None):
+ async def async_get_tts_audio(self, message, language, options=None):
"""Load TTS from MaryTTS."""
websession = async_get_clientsession(self.hass)
@@ -98,13 +96,13 @@ class MaryTTSProvider(Provider):
'LOCALE': actual_language
}
- request = yield from websession.get(url, params=url_param)
+ request = await websession.get(url, params=url_param)
if request.status != 200:
_LOGGER.error("Error %d on load url %s",
request.status, request.url)
return (None, None)
- data = yield from request.read()
+ data = await request.read()
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for MaryTTS API")
diff --git a/homeassistant/components/tts/voicerss.py b/homeassistant/components/tts/voicerss.py
index 38f6e2290b5..22eba69e510 100644
--- a/homeassistant/components/tts/voicerss.py
+++ b/homeassistant/components/tts/voicerss.py
@@ -80,8 +80,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_get_engine(hass, config):
+async def async_get_engine(hass, config):
"""Set up VoiceRSS TTS component."""
return VoiceRSSProvider(hass, config)
@@ -113,8 +112,7 @@ class VoiceRSSProvider(Provider):
"""Return list of supported languages."""
return SUPPORT_LANGUAGES
- @asyncio.coroutine
- def async_get_tts_audio(self, message, language, options=None):
+ async def async_get_tts_audio(self, message, language, options=None):
"""Load TTS from VoiceRSS."""
websession = async_get_clientsession(self.hass)
form_data = self._form_data.copy()
@@ -124,7 +122,7 @@ class VoiceRSSProvider(Provider):
try:
with async_timeout.timeout(10, loop=self.hass.loop):
- request = yield from websession.post(
+ request = await websession.post(
VOICERSS_API_URL, data=form_data
)
@@ -132,7 +130,7 @@ class VoiceRSSProvider(Provider):
_LOGGER.error("Error %d on load url %s.",
request.status, request.url)
return (None, None)
- data = yield from request.read()
+ data = await request.read()
if data in ERROR_MSG:
_LOGGER.error(
diff --git a/homeassistant/components/tts/yandextts.py b/homeassistant/components/tts/yandextts.py
index b5e965a5b50..d0ec8c74a96 100644
--- a/homeassistant/components/tts/yandextts.py
+++ b/homeassistant/components/tts/yandextts.py
@@ -71,8 +71,7 @@ SUPPORTED_OPTIONS = [
]
-@asyncio.coroutine
-def async_get_engine(hass, config):
+async def async_get_engine(hass, config):
"""Set up VoiceRSS speech component."""
return YandexSpeechKitProvider(hass, config)
@@ -106,8 +105,7 @@ class YandexSpeechKitProvider(Provider):
"""Return list of supported options."""
return SUPPORTED_OPTIONS
- @asyncio.coroutine
- def async_get_tts_audio(self, message, language, options=None):
+ async def async_get_tts_audio(self, message, language, options=None):
"""Load TTS from yandex."""
websession = async_get_clientsession(self.hass)
actual_language = language
@@ -125,14 +123,14 @@ class YandexSpeechKitProvider(Provider):
'speed': options.get(CONF_SPEED, self._speed)
}
- request = yield from websession.get(
+ request = await websession.get(
YANDEX_API_URL, params=url_param)
if request.status != 200:
_LOGGER.error("Error %d on load URL %s",
request.status, request.url)
return (None, None)
- data = yield from request.read()
+ data = await request.read()
except (asyncio.TimeoutError, aiohttp.ClientError):
_LOGGER.error("Timeout for yandex speech kit API")
diff --git a/homeassistant/components/tuya.py b/homeassistant/components/tuya.py
index 33f34164b02..22a82dec8e2 100644
--- a/homeassistant/components/tuya.py
+++ b/homeassistant/components/tuya.py
@@ -159,7 +159,7 @@ class TuyaDevice(Entity):
def _delete_callback(self, dev_id):
"""Remove this entity."""
if dev_id == self.object_id:
- self.hass.async_add_job(self.async_remove())
+ self.hass.async_create_task(self.async_remove())
@callback
def _update_callback(self):
diff --git a/homeassistant/components/upcloud.py b/homeassistant/components/upcloud.py
index 0f503dcdc39..a0b61f86e56 100644
--- a/homeassistant/components/upcloud.py
+++ b/homeassistant/components/upcloud.py
@@ -4,7 +4,6 @@ Support for UpCloud.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/upcloud/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -129,8 +128,7 @@ class UpCloudServerEntity(Entity):
except (AttributeError, KeyError, TypeError):
return DEFAULT_COMPONENT_NAME.format(self.uuid)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
async_dispatcher_connect(
self.hass, SIGNAL_UPDATE_UPCLOUD, self._update_callback)
diff --git a/homeassistant/components/upnp.py b/homeassistant/components/upnp.py
deleted file mode 100644
index 2bf0572d498..00000000000
--- a/homeassistant/components/upnp.py
+++ /dev/null
@@ -1,144 +0,0 @@
-"""
-Will open a port in your router for Home Assistant and provide statistics.
-
-For more details about this component, please refer to the documentation at
-https://home-assistant.io/components/upnp/
-"""
-from ipaddress import ip_address
-import logging
-import asyncio
-
-import voluptuous as vol
-
-from homeassistant.const import (EVENT_HOMEASSISTANT_STOP)
-from homeassistant.helpers import config_validation as cv
-from homeassistant.helpers import discovery
-from homeassistant.util import get_local_ip
-
-REQUIREMENTS = ['pyupnp-async==0.1.1.1']
-DEPENDENCIES = ['http']
-
-_LOGGER = logging.getLogger(__name__)
-
-DEPENDENCIES = ['api']
-DOMAIN = 'upnp'
-
-DATA_UPNP = 'upnp_device'
-
-CONF_LOCAL_IP = 'local_ip'
-CONF_ENABLE_PORT_MAPPING = 'port_mapping'
-CONF_PORTS = 'ports'
-CONF_UNITS = 'unit'
-CONF_HASS = 'hass'
-
-NOTIFICATION_ID = 'upnp_notification'
-NOTIFICATION_TITLE = 'UPnP Setup'
-
-IGD_DEVICE = 'urn:schemas-upnp-org:device:InternetGatewayDevice:1'
-PPP_SERVICE = 'urn:schemas-upnp-org:service:WANPPPConnection:1'
-IP_SERVICE = 'urn:schemas-upnp-org:service:WANIPConnection:1'
-IP_SERVICE2 = 'urn:schemas-upnp-org:service:WANIPConnection:2'
-CIC_SERVICE = 'urn:schemas-upnp-org:service:WANCommonInterfaceConfig:1'
-
-UNITS = {
- "Bytes": 1,
- "KBytes": 1024,
- "MBytes": 1024**2,
- "GBytes": 1024**3,
-}
-
-CONFIG_SCHEMA = vol.Schema({
- DOMAIN: vol.Schema({
- vol.Optional(CONF_ENABLE_PORT_MAPPING, default=False): cv.boolean,
- vol.Optional(CONF_UNITS, default="MBytes"): vol.In(UNITS),
- vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string),
- vol.Optional(CONF_PORTS):
- vol.Schema({vol.Any(CONF_HASS, cv.positive_int): cv.positive_int})
- }),
-}, extra=vol.ALLOW_EXTRA)
-
-
-async def async_setup(hass, config):
- """Register a port mapping for Home Assistant via UPnP."""
- config = config[DOMAIN]
- host = config.get(CONF_LOCAL_IP)
-
- if host is None:
- host = get_local_ip()
-
- if host == '127.0.0.1':
- _LOGGER.error(
- 'Unable to determine local IP. Add it to your configuration.')
- return False
-
- import pyupnp_async
- from pyupnp_async.error import UpnpSoapError
-
- service = None
- resp = await pyupnp_async.msearch_first(search_target=IGD_DEVICE)
- if not resp:
- return False
-
- try:
- device = await resp.get_device()
- hass.data[DATA_UPNP] = device
- for _service in device.services:
- if _service['serviceType'] == PPP_SERVICE:
- service = device.find_first_service(PPP_SERVICE)
- if _service['serviceType'] == IP_SERVICE:
- service = device.find_first_service(IP_SERVICE)
- if _service['serviceType'] == IP_SERVICE2:
- service = device.find_first_service(IP_SERVICE2)
- if _service['serviceType'] == CIC_SERVICE:
- unit = config[CONF_UNITS]
- hass.async_create_task(discovery.async_load_platform(
- hass, 'sensor', DOMAIN, {'unit': unit}, config))
- except UpnpSoapError as error:
- _LOGGER.error(error)
- return False
-
- if not service:
- _LOGGER.warning("Could not find any UPnP IGD")
- return False
-
- port_mapping = config[CONF_ENABLE_PORT_MAPPING]
- if not port_mapping:
- return True
-
- internal_port = hass.http.server_port
-
- ports = config.get(CONF_PORTS)
- if ports is None:
- ports = {CONF_HASS: internal_port}
-
- registered = []
- for internal, external in ports.items():
- if internal == CONF_HASS:
- internal = internal_port
- try:
- await service.add_port_mapping(internal, external, host, 'TCP',
- desc='Home Assistant')
- registered.append(external)
- _LOGGER.debug("Mapping external TCP port %s -> %s @ %s",
- external, internal, host)
- except UpnpSoapError as error:
- _LOGGER.error(error)
- hass.components.persistent_notification.create(
- 'ERROR: tcp port {} is already mapped in your router.'
- '
Please disable port_mapping in the upnp '
- 'configuration section.
'
- 'You will need to restart hass after fixing.'
- ''.format(external),
- title=NOTIFICATION_TITLE,
- notification_id=NOTIFICATION_ID)
-
- async def deregister_port(event):
- """De-register the UPnP port mapping."""
- tasks = [service.delete_port_mapping(external, 'TCP')
- for external in registered]
- if tasks:
- await asyncio.wait(tasks)
-
- hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, deregister_port)
-
- return True
diff --git a/homeassistant/components/upnp/.translations/ca.json b/homeassistant/components/upnp/.translations/ca.json
new file mode 100644
index 00000000000..5dba9d1e16e
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/ca.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD ja est\u00e0 configurat",
+ "no_devices_discovered": "No s'ha trobat cap UPnP/IGD",
+ "no_sensors_or_port_mapping": "Activa, com a m\u00ednim, els sensors o l'assignaci\u00f3 de ports"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Activa l'assignaci\u00f3 de ports per a Home Assistant",
+ "enable_sensors": "Afegiu sensors de tr\u00e0nsit",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Opcions de configuraci\u00f3 per a UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/de.json b/homeassistant/components/upnp/.translations/de.json
new file mode 100644
index 00000000000..675d1eb7d0c
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/de.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD ist bereits konfiguriert",
+ "no_devices_discovered": "Keine UPnP/IGDs entdeckt",
+ "no_sensors_or_port_mapping": "Aktiviere mindestens Sensoren oder Port-Mapping"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Aktiviere Port-Mapping f\u00fcr Home Assistant",
+ "enable_sensors": "Verkehrssensoren hinzuf\u00fcgen",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Konfigurationsoptionen f\u00fcr UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/en.json b/homeassistant/components/upnp/.translations/en.json
new file mode 100644
index 00000000000..93e1db62f8e
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/en.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD is already configured",
+ "no_devices_discovered": "No UPnP/IGDs discovered",
+ "no_sensors_or_port_mapping": "Enable at least sensors or port mapping"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Enable port mapping for Home Assistant",
+ "enable_sensors": "Add traffic sensors",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Configuration options for the UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/fr.json b/homeassistant/components/upnp/.translations/fr.json
new file mode 100644
index 00000000000..3eac9577890
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/fr.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP / IGD est d\u00e9j\u00e0 configur\u00e9",
+ "no_devices_discovered": "Aucun UPnP / IGD d\u00e9couvert",
+ "no_sensors_or_port_mapping": "Activer au moins les capteurs ou la cartographie des ports"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP / IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Activer le mappage de port pour Home Assistant",
+ "enable_sensors": "Ajouter des capteurs de trafic",
+ "igd": "UPnP / IGD"
+ },
+ "title": "Options de configuration pour UPnP / IGD"
+ }
+ },
+ "title": "UPnP / IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/ko.json b/homeassistant/components/upnp/.translations/ko.json
new file mode 100644
index 00000000000..0dd7a16de0b
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/ko.json
@@ -0,0 +1,26 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD \uac00 \uc774\ubbf8 \uc124\uc815\ub41c \uc0c1\ud0dc\uc785\ub2c8\ub2e4",
+ "no_devices_discovered": "\ubc1c\uacac\ub41c UPnP/IGD \uac00 \uc5c6\uc2b5\ub2c8\ub2e4",
+ "no_sensors_or_port_mapping": "\ucd5c\uc18c\ud55c \uc13c\uc11c \ud639\uc740 \ud3ec\ud2b8 \ub9e4\ud551\uc744 \ud65c\uc131\ud654 \ud574\uc57c \ud569\ub2c8\ub2e4"
+ },
+ "error": {
+ "other": "\ub2e4\ub978"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Home Assistant \ud3ec\ud2b8 \ub9e4\ud551 \ud65c\uc131\ud654",
+ "enable_sensors": "\ud2b8\ub798\ud53d \uc13c\uc11c \ucd94\uac00",
+ "igd": "UPnP/IGD"
+ },
+ "title": "UPnP/IGD \uc758 \uad6c\uc131 \uc635\uc158"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/lb.json b/homeassistant/components/upnp/.translations/lb.json
new file mode 100644
index 00000000000..1d13492a487
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/lb.json
@@ -0,0 +1,27 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD ass scho konfigur\u00e9iert",
+ "no_devices_discovered": "Keng UPnP/IGDs entdeckt",
+ "no_sensors_or_port_mapping": "Aktiv\u00e9ier op mannst Sensoren oder Port Mapping"
+ },
+ "error": {
+ "one": "Een",
+ "other": "Aaner"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Port Mapping fir Home Assistant aktiv\u00e9ieren",
+ "enable_sensors": "Trafic Sensoren dob\u00e4isetzen",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Konfiguratiouns Optiounen fir UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/nl.json b/homeassistant/components/upnp/.translations/nl.json
new file mode 100644
index 00000000000..647eb647f24
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/nl.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP / IGD is al geconfigureerd",
+ "no_devices_discovered": "Geen UPnP / IGD's ontdekt",
+ "no_sensors_or_port_mapping": "Schakel ten minste sensoren of poorttoewijzing in"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Poorttoewijzing voor Home Assistant inschakelen",
+ "enable_sensors": "Voeg verkeerssensoren toe",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Configuratiemogelijkheden voor de UPnP/IGD"
+ }
+ },
+ "title": "UPnP / IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/no.json b/homeassistant/components/upnp/.translations/no.json
new file mode 100644
index 00000000000..fbb1b4afc75
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/no.json
@@ -0,0 +1,29 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP / IGD er allerede konfigurert",
+ "no_devices_discovered": "Ingen UPnP / IGDs oppdaget"
+ },
+ "error": {
+ "few": "f\u00e5",
+ "many": "mange",
+ "one": "en",
+ "other": "andre",
+ "two": "to",
+ "zero": "ingen"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP / IGD"
+ },
+ "user": {
+ "data": {
+ "enable_sensors": "Legg til trafikk sensorer",
+ "igd": "UPnP / IGD"
+ },
+ "title": "Konfigurasjonsalternativer for UPnP / IGD"
+ }
+ },
+ "title": "UPnP / IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/pl.json b/homeassistant/components/upnp/.translations/pl.json
new file mode 100644
index 00000000000..e47a25b9d93
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/pl.json
@@ -0,0 +1,29 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD jest ju\u017c skonfigurowane",
+ "no_devices_discovered": "Nie wykryto urz\u0105dze\u0144 UPnP/IGD",
+ "no_sensors_or_port_mapping": "W\u0142\u0105cz przynajmniej sensory lub mapowanie port\u00f3w"
+ },
+ "error": {
+ "few": "kilka",
+ "many": "wiele",
+ "one": "jeden",
+ "other": "inne"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "W\u0142\u0105cz mapowanie port\u00f3w dla Home Assistant'a",
+ "enable_sensors": "Dodaj sensor ruchu sieciowego",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Opcje konfiguracji dla UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/ru.json b/homeassistant/components/upnp/.translations/ru.json
new file mode 100644
index 00000000000..9b7d358da0a
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/ru.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "\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.",
+ "no_devices_discovered": "\u041d\u0435 \u043e\u0431\u043d\u0430\u0440\u0443\u0436\u0435\u043d\u043e UPnP / IGD",
+ "no_sensors_or_port_mapping": "\u0418\u0441\u043f\u043e\u043b\u044c\u0437\u0443\u0439\u0442\u0435 \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0438\u043b\u0438 \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432 "
+ },
+ "step": {
+ "init": {
+ "title": "UPnP / IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "\u0412\u043a\u043b\u044e\u0447\u0438\u0442\u044c \u043f\u0440\u0435\u043e\u0431\u0440\u0430\u0437\u043e\u0432\u0430\u043d\u0438\u0435 \u043f\u043e\u0440\u0442\u043e\u0432 \u0434\u043b\u044f Home Assistant",
+ "enable_sensors": "\u0414\u043e\u0431\u0430\u0432\u0438\u0442\u044c \u0434\u0430\u0442\u0447\u0438\u043a\u0438 \u0441\u0435\u0442\u0435\u0432\u043e\u0433\u043e \u0442\u0440\u0430\u0444\u0438\u043a\u0430",
+ "igd": "UPnP / IGD"
+ },
+ "title": "\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0434\u043b\u044f UPnP / IGD"
+ }
+ },
+ "title": "UPnP / IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/sl.json b/homeassistant/components/upnp/.translations/sl.json
new file mode 100644
index 00000000000..20debe7f09a
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/sl.json
@@ -0,0 +1,29 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD je \u017ee konfiguriran",
+ "no_devices_discovered": "Ni odkritih UPnP/IGD naprav",
+ "no_sensors_or_port_mapping": "Omogo\u010dite vsaj senzorje ali preslikavo vrat (port mapping)"
+ },
+ "error": {
+ "few": "nekaj",
+ "one": "ena",
+ "other": "ve\u010d",
+ "two": "dve"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Omogo\u010dajo preslikavo vrat (port mapping) za Home Assistent-a",
+ "enable_sensors": "Dodaj prometne senzorje",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Mo\u017enosti konfiguracije za UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/sv.json b/homeassistant/components/upnp/.translations/sv.json
new file mode 100644
index 00000000000..63c63781845
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/sv.json
@@ -0,0 +1,27 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD \u00e4r redan konfigurerad",
+ "no_devices_discovered": "Inga UPnP/IGDs uppt\u00e4cktes",
+ "no_sensors_or_port_mapping": "Aktivera minst sensorer eller portmappning"
+ },
+ "error": {
+ "one": "En",
+ "other": "Andra"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "Aktivera portmappning f\u00f6r Home Assistant",
+ "enable_sensors": "L\u00e4gg till trafiksensorer",
+ "igd": "UPnP/IGD"
+ },
+ "title": "Konfigurationsalternativ f\u00f6r UPnP/IGD"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/zh-Hans.json b/homeassistant/components/upnp/.translations/zh-Hans.json
new file mode 100644
index 00000000000..c4962ba1c4b
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/zh-Hans.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD \u5df2\u914d\u7f6e\u5b8c\u6210",
+ "no_devices_discovered": "\u672a\u53d1\u73b0 UPnP/IGD",
+ "no_sensors_or_port_mapping": "\u81f3\u5c11\u542f\u7528\u4f20\u611f\u5668\u6216\u7aef\u53e3\u6620\u5c04"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "\u4e3a Home Assistant \u542f\u7528\u7aef\u53e3\u6620\u5c04",
+ "enable_sensors": "\u6dfb\u52a0\u6d41\u91cf\u4f20\u611f\u5668",
+ "igd": "UPnP/IGD"
+ },
+ "title": "UPnP/IGD \u7684\u914d\u7f6e\u9009\u9879"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/.translations/zh-Hant.json b/homeassistant/components/upnp/.translations/zh-Hant.json
new file mode 100644
index 00000000000..ca8171265ae
--- /dev/null
+++ b/homeassistant/components/upnp/.translations/zh-Hant.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "UPnP/IGD \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
+ "no_devices_discovered": "\u672a\u641c\u5c0b\u5230 UPnP/IGD",
+ "no_sensors_or_port_mapping": "\u81f3\u5c11\u958b\u555f\u611f\u61c9\u5668\u6216\u901a\u8a0a\u57e0\u8f49\u767c"
+ },
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "data": {
+ "enable_port_mapping": "\u958b\u555f Home Assistant \u901a\u8a0a\u57e0\u8f49\u767c",
+ "enable_sensors": "\u65b0\u589e\u4ea4\u901a\u611f\u61c9\u5668",
+ "igd": "UPnP/IGD"
+ },
+ "title": "UPnP/IGD \u8a2d\u5b9a\u9078\u9805"
+ }
+ },
+ "title": "UPnP/IGD"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/upnp/__init__.py b/homeassistant/components/upnp/__init__.py
new file mode 100644
index 00000000000..f70fbcc4d20
--- /dev/null
+++ b/homeassistant/components/upnp/__init__.py
@@ -0,0 +1,169 @@
+"""
+Will open a port in your router for Home Assistant and provide statistics.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/upnp/
+"""
+import asyncio
+from ipaddress import ip_address
+
+import aiohttp
+import voluptuous as vol
+
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
+from homeassistant.helpers import config_validation as cv
+from homeassistant.helpers import dispatcher
+from homeassistant.helpers.typing import ConfigType
+from homeassistant.helpers.typing import HomeAssistantType
+from homeassistant.components.discovery import DOMAIN as DISCOVERY_DOMAIN
+
+from .const import (
+ CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS,
+ CONF_HASS, CONF_LOCAL_IP, CONF_PORTS,
+ CONF_UDN, CONF_SSDP_DESCRIPTION,
+ SIGNAL_REMOVE_SENSOR,
+)
+from .const import DOMAIN
+from .const import LOGGER as _LOGGER
+from .config_flow import ensure_domain_data
+from .device import Device
+
+
+REQUIREMENTS = ['async-upnp-client==0.12.4']
+DEPENDENCIES = ['http']
+
+NOTIFICATION_ID = 'upnp_notification'
+NOTIFICATION_TITLE = 'UPnP/IGD Setup'
+
+CONFIG_SCHEMA = vol.Schema({
+ DOMAIN: vol.Schema({
+ vol.Optional(CONF_ENABLE_PORT_MAPPING, default=False): cv.boolean,
+ vol.Optional(CONF_ENABLE_SENSORS, default=True): cv.boolean,
+ vol.Optional(CONF_LOCAL_IP): vol.All(ip_address, cv.string),
+ vol.Optional(CONF_PORTS):
+ vol.Schema({
+ vol.Any(CONF_HASS, cv.positive_int):
+ vol.Any(CONF_HASS, cv.positive_int)
+ })
+ }),
+}, extra=vol.ALLOW_EXTRA)
+
+
+def _substitute_hass_ports(ports, hass_port):
+ """Substitute 'hass' for the hass_port."""
+ ports = ports.copy()
+
+ # substitute 'hass' for hass_port, both keys and values
+ if CONF_HASS in ports:
+ ports[hass_port] = ports[CONF_HASS]
+ del ports[CONF_HASS]
+
+ for port in ports:
+ if ports[port] == CONF_HASS:
+ ports[port] = hass_port
+
+ return ports
+
+
+# config
+async def async_setup(hass: HomeAssistantType, config: ConfigType):
+ """Register a port mapping for Home Assistant via UPnP."""
+ ensure_domain_data(hass)
+
+ # ensure sane config
+ if DOMAIN not in config:
+ return True
+
+ if DISCOVERY_DOMAIN not in config:
+ _LOGGER.warning('UPNP needs discovery, please enable it')
+ return False
+
+ # overridden local ip
+ upnp_config = config[DOMAIN]
+ if CONF_LOCAL_IP in upnp_config:
+ hass.data[DOMAIN]['local_ip'] = upnp_config[CONF_LOCAL_IP]
+
+ # determine ports
+ ports = {CONF_HASS: CONF_HASS} # default, port_mapping disabled by default
+ if CONF_PORTS in upnp_config:
+ # copy from config
+ ports = upnp_config[CONF_PORTS]
+
+ hass.data[DOMAIN]['auto_config'] = {
+ 'active': True,
+ 'enable_sensors': upnp_config[CONF_ENABLE_SENSORS],
+ 'enable_port_mapping': upnp_config[CONF_ENABLE_PORT_MAPPING],
+ 'ports': ports,
+ }
+
+ return True
+
+
+# config flow
+async def async_setup_entry(hass: HomeAssistantType,
+ config_entry: ConfigEntry):
+ """Set up UPnP/IGD-device from a config entry."""
+ ensure_domain_data(hass)
+ data = config_entry.data
+
+ # build UPnP/IGD device
+ ssdp_description = data[CONF_SSDP_DESCRIPTION]
+ try:
+ device = await Device.async_create_device(hass, ssdp_description)
+ except (asyncio.TimeoutError, aiohttp.ClientError):
+ _LOGGER.error('Unable to create upnp-device')
+ return False
+
+ hass.data[DOMAIN]['devices'][device.udn] = device
+
+ # port mapping
+ if data.get(CONF_ENABLE_PORT_MAPPING):
+ local_ip = hass.data[DOMAIN].get('local_ip')
+ ports = hass.data[DOMAIN]['auto_config']['ports']
+ _LOGGER.debug('Enabling port mappings: %s', ports)
+
+ hass_port = hass.http.server_port
+ ports = _substitute_hass_ports(ports, hass_port)
+ await device.async_add_port_mappings(ports, local_ip=local_ip)
+
+ # sensors
+ if data.get(CONF_ENABLE_SENSORS):
+ _LOGGER.debug('Enabling sensors')
+
+ # register sensor setup handlers
+ hass.async_create_task(hass.config_entries.async_forward_entry_setup(
+ config_entry, 'sensor'))
+
+ async def unload_entry(event):
+ """Unload entry on quit."""
+ await async_unload_entry(hass, config_entry)
+ hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unload_entry)
+
+ return True
+
+
+async def async_unload_entry(hass: HomeAssistantType,
+ config_entry: ConfigEntry):
+ """Unload a config entry."""
+ data = config_entry.data
+ udn = data[CONF_UDN]
+
+ if udn not in hass.data[DOMAIN]['devices']:
+ return True
+ device = hass.data[DOMAIN]['devices'][udn]
+
+ # port mapping
+ if data.get(CONF_ENABLE_PORT_MAPPING):
+ _LOGGER.debug('Deleting port mappings')
+ await device.async_delete_port_mappings()
+
+ # sensors
+ if data.get(CONF_ENABLE_SENSORS):
+ _LOGGER.debug('Deleting sensors')
+ dispatcher.async_dispatcher_send(hass, SIGNAL_REMOVE_SENSOR, device)
+
+ # clear stored device
+ del hass.data[DOMAIN]['devices'][udn]
+
+ return True
diff --git a/homeassistant/components/upnp/config_flow.py b/homeassistant/components/upnp/config_flow.py
new file mode 100644
index 00000000000..f695e3ada75
--- /dev/null
+++ b/homeassistant/components/upnp/config_flow.py
@@ -0,0 +1,160 @@
+"""Config flow for UPNP."""
+from collections import OrderedDict
+
+import voluptuous as vol
+
+from homeassistant import config_entries
+from homeassistant import data_entry_flow
+
+from .const import (
+ CONF_ENABLE_PORT_MAPPING, CONF_ENABLE_SENSORS,
+ CONF_SSDP_DESCRIPTION, CONF_UDN
+)
+from .const import DOMAIN
+
+
+def ensure_domain_data(hass):
+ """Ensure hass.data is filled properly."""
+ hass.data[DOMAIN] = hass.data.get(DOMAIN, {})
+ hass.data[DOMAIN]['devices'] = hass.data[DOMAIN].get('devices', {})
+ hass.data[DOMAIN]['discovered'] = hass.data[DOMAIN].get('discovered', {})
+ hass.data[DOMAIN]['auto_config'] = hass.data[DOMAIN].get('auto_config', {
+ 'active': False,
+ 'enable_sensors': False,
+ 'enable_port_mapping': False,
+ 'ports': {'hass': 'hass'},
+ })
+
+
+@config_entries.HANDLERS.register(DOMAIN)
+class UpnpFlowHandler(data_entry_flow.FlowHandler):
+ """Handle a UPnP/IGD config flow."""
+
+ VERSION = 1
+ CONNECTION_CLASS = config_entries.CONN_CLASS_LOCAL_POLL
+
+ @property
+ def _configured_upnp_igds(self):
+ """Get all configured IGDs."""
+ return {
+ entry.data[CONF_UDN]: {
+ 'udn': entry.data[CONF_UDN],
+ }
+ for entry in self.hass.config_entries.async_entries(DOMAIN)
+ }
+
+ @property
+ def _discovered_upnp_igds(self):
+ """Get all discovered entries."""
+ return self.hass.data[DOMAIN]['discovered']
+
+ def _store_discovery_info(self, discovery_info):
+ """Add discovery info."""
+ udn = discovery_info['udn']
+ self.hass.data[DOMAIN]['discovered'][udn] = discovery_info
+
+ def _auto_config_settings(self):
+ """Check if auto_config has been enabled."""
+ return self.hass.data[DOMAIN]['auto_config']
+
+ async def async_step_discovery(self, discovery_info):
+ """
+ Handle a discovered UPnP/IGD.
+
+ This flow is triggered by the discovery component. It will check if the
+ host is already configured and delegate to the import step if not.
+ """
+ ensure_domain_data(self.hass)
+
+ # store discovered device
+ discovery_info['friendly_name'] = \
+ '{} ({})'.format(discovery_info['host'], discovery_info['name'])
+ self._store_discovery_info(discovery_info)
+
+ # ensure not already discovered/configured
+ udn = discovery_info['udn']
+ if udn in self._configured_upnp_igds:
+ return self.async_abort(reason='already_configured')
+
+ # auto config?
+ auto_config = self._auto_config_settings()
+ if auto_config['active']:
+ import_info = {
+ 'name': discovery_info['friendly_name'],
+ 'enable_sensors': auto_config['enable_sensors'],
+ 'enable_port_mapping': auto_config['enable_port_mapping'],
+ }
+
+ return await self._async_save_entry(import_info)
+
+ return await self.async_step_user()
+
+ async def async_step_user(self, user_input=None):
+ """Manual set up."""
+ ensure_domain_data(self.hass)
+
+ # if user input given, handle it
+ user_input = user_input or {}
+ if 'name' in user_input:
+ if not user_input['enable_sensors'] and \
+ not user_input['enable_port_mapping']:
+ return self.async_abort(reason='no_sensors_or_port_mapping')
+
+ # ensure not already configured
+ configured_names = [
+ entry['friendly_name']
+ for udn, entry in self._discovered_upnp_igds.items()
+ if udn in self._configured_upnp_igds
+ ]
+ if user_input['name'] in configured_names:
+ return self.async_abort(reason='already_configured')
+
+ return await self._async_save_entry(user_input)
+
+ # let user choose from all discovered, non-configured, UPnP/IGDs
+ names = [
+ entry['friendly_name']
+ for udn, entry in self._discovered_upnp_igds.items()
+ if udn not in self._configured_upnp_igds
+ ]
+ if not names:
+ return self.async_abort(reason='no_devices_discovered')
+
+ return self.async_show_form(
+ step_id='user',
+ data_schema=vol.Schema(
+ OrderedDict([
+ (vol.Required('name'), vol.In(names)),
+ (vol.Optional('enable_sensors', default=False), bool),
+ (vol.Optional('enable_port_mapping', default=False), bool),
+ ])
+ ))
+
+ async def async_step_import(self, import_info):
+ """Import a new UPnP/IGD as a config entry."""
+ ensure_domain_data(self.hass)
+
+ return await self._async_save_entry(import_info)
+
+ async def _async_save_entry(self, import_info):
+ """Store UPNP/IGD as new entry."""
+ ensure_domain_data(self.hass)
+
+ # ensure we know the host
+ name = import_info['name']
+ discovery_infos = [info
+ for info in self._discovered_upnp_igds.values()
+ if info['friendly_name'] == name]
+ if not discovery_infos:
+ return self.async_abort(reason='host_not_found')
+ discovery_info = discovery_infos[0]
+
+ return self.async_create_entry(
+ title=discovery_info['name'],
+ data={
+ CONF_SSDP_DESCRIPTION: discovery_info['ssdp_description'],
+ CONF_UDN: discovery_info['udn'],
+ CONF_ENABLE_SENSORS: import_info['enable_sensors'],
+ CONF_ENABLE_PORT_MAPPING: import_info['enable_port_mapping'],
+ },
+ )
diff --git a/homeassistant/components/upnp/const.py b/homeassistant/components/upnp/const.py
new file mode 100644
index 00000000000..7a906ae02be
--- /dev/null
+++ b/homeassistant/components/upnp/const.py
@@ -0,0 +1,14 @@
+"""Constants for the IGD component."""
+import logging
+
+
+CONF_ENABLE_PORT_MAPPING = 'port_mapping'
+CONF_ENABLE_SENSORS = 'sensors'
+CONF_HASS = 'hass'
+CONF_LOCAL_IP = 'local_ip'
+CONF_PORTS = 'ports'
+CONF_SSDP_DESCRIPTION = 'ssdp_description'
+CONF_UDN = 'udn'
+DOMAIN = 'upnp'
+LOGGER = logging.getLogger('homeassistant.components.upnp')
+SIGNAL_REMOVE_SENSOR = 'upnp_remove_sensor'
diff --git a/homeassistant/components/upnp/device.py b/homeassistant/components/upnp/device.py
new file mode 100644
index 00000000000..4a444aa3087
--- /dev/null
+++ b/homeassistant/components/upnp/device.py
@@ -0,0 +1,131 @@
+"""Hass representation of an UPnP/IGD."""
+import asyncio
+from ipaddress import IPv4Address
+
+import aiohttp
+
+from homeassistant.helpers.aiohttp_client import async_get_clientsession
+from homeassistant.helpers.typing import HomeAssistantType
+from homeassistant.util import get_local_ip
+
+from .const import LOGGER as _LOGGER
+
+
+class Device:
+ """Hass representation of an UPnP/IGD."""
+
+ def __init__(self, igd_device):
+ """Initializer."""
+ self._igd_device = igd_device
+ self._mapped_ports = []
+
+ @classmethod
+ async def async_create_device(cls,
+ hass: HomeAssistantType,
+ ssdp_description: str):
+ """Create UPnP/IGD device."""
+ # build async_upnp_client requester
+ from async_upnp_client.aiohttp import AiohttpSessionRequester
+ session = async_get_clientsession(hass)
+ requester = AiohttpSessionRequester(session, True)
+
+ # create async_upnp_client device
+ from async_upnp_client import UpnpFactory
+ factory = UpnpFactory(requester,
+ disable_state_variable_validation=True)
+ upnp_device = await factory.async_create_device(ssdp_description)
+
+ # wrap with async_upnp_client IgdDevice
+ from async_upnp_client.igd import IgdDevice
+ igd_device = IgdDevice(upnp_device, None)
+
+ return cls(igd_device)
+
+ @property
+ def udn(self):
+ """Get the UDN."""
+ return self._igd_device.udn
+
+ @property
+ def name(self):
+ """Get the name."""
+ return self._igd_device.name
+
+ async def async_add_port_mappings(self, ports, local_ip=None):
+ """Add port mappings."""
+ # determine local ip, ensure sane IP
+ if local_ip is None:
+ local_ip = get_local_ip()
+
+ if local_ip == '127.0.0.1':
+ _LOGGER.error(
+ 'Could not create port mapping, our IP is 127.0.0.1')
+ local_ip = IPv4Address(local_ip)
+
+ # create port mappings
+ for external_port, internal_port in ports.items():
+ await self._async_add_port_mapping(external_port,
+ local_ip,
+ internal_port)
+ self._mapped_ports.append(external_port)
+
+ async def _async_add_port_mapping(self,
+ external_port,
+ local_ip,
+ internal_port):
+ """Add a port mapping."""
+ # create port mapping
+ from async_upnp_client import UpnpError
+ _LOGGER.info('Creating port mapping %s:%s:%s (TCP)',
+ external_port, local_ip, internal_port)
+ try:
+ await self._igd_device.async_add_port_mapping(
+ remote_host=None,
+ external_port=external_port,
+ protocol='TCP',
+ internal_port=internal_port,
+ internal_client=local_ip,
+ enabled=True,
+ description="Home Assistant",
+ lease_duration=None)
+
+ self._mapped_ports.append(external_port)
+ except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError):
+ _LOGGER.error('Could not add port mapping: %s:%s:%s',
+ external_port, local_ip, internal_port)
+
+ async def async_delete_port_mappings(self):
+ """Remove a port mapping."""
+ for port in self._mapped_ports:
+ await self._async_delete_port_mapping(port)
+
+ async def _async_delete_port_mapping(self, external_port):
+ """Remove a port mapping."""
+ from async_upnp_client import UpnpError
+ _LOGGER.info('Deleting port mapping %s (TCP)', external_port)
+ try:
+ await self._igd_device.async_delete_port_mapping(
+ remote_host=None,
+ external_port=external_port,
+ protocol='TCP')
+
+ self._mapped_ports.remove(external_port)
+ except (asyncio.TimeoutError, aiohttp.ClientError, UpnpError):
+ _LOGGER.error('Could not delete port mapping')
+
+ async def async_get_total_bytes_received(self):
+ """Get total bytes received."""
+ return await self._igd_device.async_get_total_bytes_received()
+
+ async def async_get_total_bytes_sent(self):
+ """Get total bytes sent."""
+ return await self._igd_device.async_get_total_bytes_sent()
+
+ async def async_get_total_packets_received(self):
+ """Get total packets received."""
+ # pylint: disable=invalid-name
+ return await self._igd_device.async_get_total_packets_received()
+
+ async def async_get_total_packets_sent(self):
+ """Get total packets sent."""
+ return await self._igd_device.async_get_total_packets_sent()
diff --git a/homeassistant/components/upnp/strings.json b/homeassistant/components/upnp/strings.json
new file mode 100644
index 00000000000..9dd4c3f5ad0
--- /dev/null
+++ b/homeassistant/components/upnp/strings.json
@@ -0,0 +1,23 @@
+{
+ "config": {
+ "title": "UPnP/IGD",
+ "step": {
+ "init": {
+ "title": "UPnP/IGD"
+ },
+ "user": {
+ "title": "Configuration options for the UPnP/IGD",
+ "data":{
+ "igd": "UPnP/IGD",
+ "enable_sensors": "Add traffic sensors",
+ "enable_port_mapping": "Enable port mapping for Home Assistant"
+ }
+ }
+ },
+ "abort": {
+ "no_devices_discovered": "No UPnP/IGDs discovered",
+ "already_configured": "UPnP/IGD is already configured",
+ "no_sensors_or_port_mapping": "Enable at least sensors or port mapping"
+ }
+ }
+}
diff --git a/homeassistant/components/vacuum/__init__.py b/homeassistant/components/vacuum/__init__.py
index 1808737d281..212e6bd648f 100644
--- a/homeassistant/components/vacuum/__init__.py
+++ b/homeassistant/components/vacuum/__init__.py
@@ -4,7 +4,6 @@ Support for vacuum cleaner robots (botvacs).
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/vacuum/
"""
-import asyncio
from datetime import timedelta
from functools import partial
import logging
@@ -94,101 +93,12 @@ def is_on(hass, entity_id=None):
return hass.states.is_state(entity_id, STATE_ON)
-@bind_hass
-def turn_on(hass, entity_id=None):
- """Turn all or specified vacuum on."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
-
-
-@bind_hass
-def turn_off(hass, entity_id=None):
- """Turn all or specified vacuum off."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
-
-
-@bind_hass
-def toggle(hass, entity_id=None):
- """Toggle all or specified vacuum."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
-
-
-@bind_hass
-def locate(hass, entity_id=None):
- """Locate all or specified vacuum."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_LOCATE, data)
-
-
-@bind_hass
-def clean_spot(hass, entity_id=None):
- """Tell all or specified vacuum to perform a spot clean-up."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_CLEAN_SPOT, data)
-
-
-@bind_hass
-def return_to_base(hass, entity_id=None):
- """Tell all or specified vacuum to return to base."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_RETURN_TO_BASE, data)
-
-
-@bind_hass
-def start_pause(hass, entity_id=None):
- """Tell all or specified vacuum to start or pause the current task."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_START_PAUSE, data)
-
-
-@bind_hass
-def start(hass, entity_id=None):
- """Tell all or specified vacuum to start or resume the current task."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_START, data)
-
-
-@bind_hass
-def pause(hass, entity_id=None):
- """Tell all or the specified vacuum to pause the current task."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_PAUSE, data)
-
-
-@bind_hass
-def stop(hass, entity_id=None):
- """Stop all or specified vacuum."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
- hass.services.call(DOMAIN, SERVICE_STOP, data)
-
-
-@bind_hass
-def set_fan_speed(hass, fan_speed, entity_id=None):
- """Set fan speed for all or specified vacuum."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- data[ATTR_FAN_SPEED] = fan_speed
- hass.services.call(DOMAIN, SERVICE_SET_FAN_SPEED, data)
-
-
-@bind_hass
-def send_command(hass, command, params=None, entity_id=None):
- """Send command to all or specified vacuum."""
- data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
- data[ATTR_COMMAND] = command
- if params is not None:
- data[ATTR_PARAMS] = params
- hass.services.call(DOMAIN, SERVICE_SEND_COMMAND, data)
-
-
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the vacuum component."""
component = EntityComponent(
_LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_VACUUMS)
- yield from component.async_setup(config)
+ await component.async_setup(config)
component.async_register_entity_service(
SERVICE_TURN_ON, VACUUM_SERVICE_SCHEMA,
diff --git a/homeassistant/components/vacuum/dyson.py b/homeassistant/components/vacuum/dyson.py
index 943b97f6360..3d6e23c20c8 100644
--- a/homeassistant/components/vacuum/dyson.py
+++ b/homeassistant/components/vacuum/dyson.py
@@ -4,7 +4,6 @@ Support for the Dyson 360 eye vacuum cleaner robot.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/vacuum.dyson/
"""
-import asyncio
import logging
from homeassistant.components.dyson import DYSON_DEVICES
@@ -55,8 +54,7 @@ class Dyson360EyeDevice(VacuumDevice):
_LOGGER.debug("Creating device %s", device.name)
self._device = device
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.async_add_job(
self._device.add_message_listener, self.on_message)
diff --git a/homeassistant/components/vacuum/mqtt.py b/homeassistant/components/vacuum/mqtt.py
index 47617277773..fcb77e10732 100644
--- a/homeassistant/components/vacuum/mqtt.py
+++ b/homeassistant/components/vacuum/mqtt.py
@@ -4,7 +4,6 @@ Support for a generic MQTT vacuum.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/vacuum.mqtt/
"""
-import asyncio
import logging
import voluptuous as vol
@@ -139,9 +138,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the vacuum."""
name = config.get(CONF_NAME)
supported_feature_strings = config.get(CONF_SUPPORTED_FEATURES)
@@ -265,10 +263,9 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._battery_level = 0
self._fan_speed = 'unknown'
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Subscribe MQTT events."""
- yield from super().async_added_to_hass()
+ await super().async_added_to_hass()
@callback
def message_received(topic, payload, qos):
@@ -332,7 +329,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._docked_topic,
self._fan_speed_topic) if topic]
for topic in set(topics_list):
- yield from self.hass.components.mqtt.async_subscribe(
+ await self.hass.components.mqtt.async_subscribe(
topic, message_received, self._qos)
@property
@@ -395,8 +392,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
"""Flag supported features."""
return self._supported_features
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the vacuum on."""
if self.supported_features & SUPPORT_TURN_ON == 0:
return
@@ -406,8 +402,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = 'Cleaning'
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the vacuum off."""
if self.supported_features & SUPPORT_TURN_OFF == 0:
return
@@ -417,8 +412,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = 'Turning Off'
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_stop(self, **kwargs):
+ async def async_stop(self, **kwargs):
"""Stop the vacuum."""
if self.supported_features & SUPPORT_STOP == 0:
return
@@ -428,8 +422,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = 'Stopping the current task'
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_clean_spot(self, **kwargs):
+ async def async_clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
if self.supported_features & SUPPORT_CLEAN_SPOT == 0:
return
@@ -439,8 +432,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = "Cleaning spot"
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_locate(self, **kwargs):
+ async def async_locate(self, **kwargs):
"""Locate the vacuum (usually by playing a song)."""
if self.supported_features & SUPPORT_LOCATE == 0:
return
@@ -450,8 +442,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = "Hi, I'm over here!"
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_start_pause(self, **kwargs):
+ async def async_start_pause(self, **kwargs):
"""Start, pause or resume the cleaning task."""
if self.supported_features & SUPPORT_PAUSE == 0:
return
@@ -461,8 +452,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = 'Pausing/Resuming cleaning...'
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_return_to_base(self, **kwargs):
+ async def async_return_to_base(self, **kwargs):
"""Tell the vacuum to return to its dock."""
if self.supported_features & SUPPORT_RETURN_HOME == 0:
return
@@ -473,8 +463,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = 'Returning home...'
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_set_fan_speed(self, fan_speed, **kwargs):
+ async def async_set_fan_speed(self, fan_speed, **kwargs):
"""Set fan speed."""
if self.supported_features & SUPPORT_FAN_SPEED == 0:
return
@@ -487,8 +476,7 @@ class MqttVacuum(MqttAvailability, VacuumDevice):
self._status = "Setting fan to {}...".format(fan_speed)
self.async_schedule_update_ha_state()
- @asyncio.coroutine
- def async_send_command(self, command, params=None, **kwargs):
+ async def async_send_command(self, command, params=None, **kwargs):
"""Send a command to a vacuum cleaner."""
if self.supported_features & SUPPORT_SEND_COMMAND == 0:
return
diff --git a/homeassistant/components/vacuum/roomba.py b/homeassistant/components/vacuum/roomba.py
index 487fd573f37..72d564909a8 100644
--- a/homeassistant/components/vacuum/roomba.py
+++ b/homeassistant/components/vacuum/roomba.py
@@ -68,9 +68,8 @@ SUPPORT_ROOMBA = SUPPORT_BATTERY | SUPPORT_PAUSE | SUPPORT_RETURN_HOME | \
SUPPORT_ROOMBA_CARPET_BOOST = SUPPORT_ROOMBA | SUPPORT_FAN_SPEED
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the iRobot Roomba vacuum cleaner platform."""
from roomba import Roomba
if PLATFORM not in hass.data:
@@ -96,7 +95,7 @@ def async_setup_platform(hass, config, async_add_entities,
try:
with async_timeout.timeout(9):
- yield from hass.async_add_job(roomba.connect)
+ await hass.async_add_job(roomba.connect)
except asyncio.TimeoutError:
raise PlatformNotReady
@@ -170,54 +169,46 @@ class RoombaVacuum(VacuumDevice):
"""Return the state attributes of the device."""
return self._state_attrs
- @asyncio.coroutine
- def async_turn_on(self, **kwargs):
+ async def async_turn_on(self, **kwargs):
"""Turn the vacuum on."""
- yield from self.hass.async_add_job(self.vacuum.send_command, 'start')
+ await self.hass.async_add_job(self.vacuum.send_command, 'start')
self._is_on = True
- @asyncio.coroutine
- def async_turn_off(self, **kwargs):
+ async def async_turn_off(self, **kwargs):
"""Turn the vacuum off and return to home."""
- yield from self.async_stop()
- yield from self.async_return_to_base()
+ await self.async_stop()
+ await self.async_return_to_base()
- @asyncio.coroutine
- def async_stop(self, **kwargs):
+ async def async_stop(self, **kwargs):
"""Stop the vacuum cleaner."""
- yield from self.hass.async_add_job(self.vacuum.send_command, 'stop')
+ await self.hass.async_add_job(self.vacuum.send_command, 'stop')
self._is_on = False
- @asyncio.coroutine
- def async_resume(self, **kwargs):
+ async def async_resume(self, **kwargs):
"""Resume the cleaning cycle."""
- yield from self.hass.async_add_job(self.vacuum.send_command, 'resume')
+ await self.hass.async_add_job(self.vacuum.send_command, 'resume')
self._is_on = True
- @asyncio.coroutine
- def async_pause(self, **kwargs):
+ async def async_pause(self, **kwargs):
"""Pause the cleaning cycle."""
- yield from self.hass.async_add_job(self.vacuum.send_command, 'pause')
+ await self.hass.async_add_job(self.vacuum.send_command, 'pause')
self._is_on = False
- @asyncio.coroutine
- def async_start_pause(self, **kwargs):
+ async def async_start_pause(self, **kwargs):
"""Pause the cleaning task or resume it."""
if self.vacuum_state and self.is_on: # vacuum is running
- yield from self.async_pause()
+ await self.async_pause()
elif self._status == 'Stopped': # vacuum is stopped
- yield from self.async_resume()
+ await self.async_resume()
else: # vacuum is off
- yield from self.async_turn_on()
+ await self.async_turn_on()
- @asyncio.coroutine
- def async_return_to_base(self, **kwargs):
+ async def async_return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock."""
- yield from self.hass.async_add_job(self.vacuum.send_command, 'dock')
+ await self.hass.async_add_job(self.vacuum.send_command, 'dock')
self._is_on = False
- @asyncio.coroutine
- def async_set_fan_speed(self, fan_speed, **kwargs):
+ async def async_set_fan_speed(self, fan_speed, **kwargs):
"""Set fan speed."""
if fan_speed.capitalize() in FAN_SPEEDS:
fan_speed = fan_speed.capitalize()
@@ -240,22 +231,20 @@ class RoombaVacuum(VacuumDevice):
_LOGGER.error("No such fan speed available: %s", fan_speed)
return
# The set_preference method does only accept string values
- yield from self.hass.async_add_job(
+ await self.hass.async_add_job(
self.vacuum.set_preference, 'carpetBoost', str(carpet_boost))
- yield from self.hass.async_add_job(
+ await self.hass.async_add_job(
self.vacuum.set_preference, 'vacHigh', str(high_perf))
- @asyncio.coroutine
- def async_send_command(self, command, params=None, **kwargs):
+ async def async_send_command(self, command, params=None, **kwargs):
"""Send raw command."""
_LOGGER.debug("async_send_command %s (%s), %s",
command, params, kwargs)
- yield from self.hass.async_add_job(
+ await self.hass.async_add_job(
self.vacuum.send_command, command, params)
return True
- @asyncio.coroutine
- def async_update(self):
+ async def async_update(self):
"""Fetch state from the device."""
# No data, no update
if not self.vacuum.master_state:
diff --git a/homeassistant/components/vacuum/xiaomi_miio.py b/homeassistant/components/vacuum/xiaomi_miio.py
index 290c3417149..d2da4f3b6ac 100644
--- a/homeassistant/components/vacuum/xiaomi_miio.py
+++ b/homeassistant/components/vacuum/xiaomi_miio.py
@@ -92,6 +92,7 @@ STATE_CODE_TO_STATE = {
3: STATE_IDLE,
5: STATE_CLEANING,
6: STATE_RETURNING,
+ 7: STATE_CLEANING,
8: STATE_DOCKED,
9: STATE_ERROR,
10: STATE_PAUSED,
@@ -103,9 +104,8 @@ STATE_CODE_TO_STATE = {
}
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the Xiaomi vacuum cleaner robot platform."""
from miio import Vacuum
if DATA_KEY not in hass.data:
@@ -124,8 +124,7 @@ def async_setup_platform(hass, config, async_add_entities,
async_add_entities([mirobo], update_before_add=True)
- @asyncio.coroutine
- def async_service_handler(service):
+ async def async_service_handler(service):
"""Map services to methods on MiroboVacuum."""
method = SERVICE_TO_METHOD.get(service.service)
params = {key: value for key, value in service.data.items()
@@ -139,14 +138,14 @@ def async_setup_platform(hass, config, async_add_entities,
update_tasks = []
for vacuum in target_vacuums:
- yield from getattr(vacuum, method['method'])(**params)
+ await getattr(vacuum, method['method'])(**params)
for vacuum in target_vacuums:
update_coro = vacuum.async_update_ha_state(True)
update_tasks.append(update_coro)
if update_tasks:
- yield from asyncio.wait(update_tasks, loop=hass.loop)
+ await asyncio.wait(update_tasks, loop=hass.loop)
for vacuum_service in SERVICE_TO_METHOD:
schema = SERVICE_TO_METHOD[vacuum_service].get(
@@ -259,12 +258,11 @@ class MiroboVacuum(StateVacuumDevice):
"""Flag vacuum cleaner robot features that are supported."""
return SUPPORT_XIAOMI
- @asyncio.coroutine
- def _try_command(self, mask_error, func, *args, **kwargs):
+ async def _try_command(self, mask_error, func, *args, **kwargs):
"""Call a vacuum command handling error messages."""
from miio import DeviceException
try:
- yield from self.hass.async_add_job(partial(func, *args, **kwargs))
+ await self.hass.async_add_job(partial(func, *args, **kwargs))
return True
except DeviceException as exc:
_LOGGER.error(mask_error, exc)
@@ -281,14 +279,12 @@ class MiroboVacuum(StateVacuumDevice):
await self._try_command(
"Unable to set start/pause: %s", self._vacuum.pause)
- @asyncio.coroutine
- def async_stop(self, **kwargs):
+ async def async_stop(self, **kwargs):
"""Stop the vacuum cleaner."""
- yield from self._try_command(
+ await self._try_command(
"Unable to stop: %s", self._vacuum.stop)
- @asyncio.coroutine
- def async_set_fan_speed(self, fan_speed, **kwargs):
+ async def async_set_fan_speed(self, fan_speed, **kwargs):
"""Set fan speed."""
if fan_speed.capitalize() in FAN_SPEEDS:
fan_speed = FAN_SPEEDS[fan_speed.capitalize()]
@@ -300,68 +296,60 @@ class MiroboVacuum(StateVacuumDevice):
"Valid speeds are: %s", exc,
self.fan_speed_list)
return
- yield from self._try_command(
+ await self._try_command(
"Unable to set fan speed: %s",
self._vacuum.set_fan_speed, fan_speed)
- @asyncio.coroutine
- def async_return_to_base(self, **kwargs):
+ async def async_return_to_base(self, **kwargs):
"""Set the vacuum cleaner to return to the dock."""
- yield from self._try_command(
+ await self._try_command(
"Unable to return home: %s", self._vacuum.home)
- @asyncio.coroutine
- def async_clean_spot(self, **kwargs):
+ async def async_clean_spot(self, **kwargs):
"""Perform a spot clean-up."""
- yield from self._try_command(
+ await self._try_command(
"Unable to start the vacuum for a spot clean-up: %s",
self._vacuum.spot)
- @asyncio.coroutine
- def async_locate(self, **kwargs):
+ async def async_locate(self, **kwargs):
"""Locate the vacuum cleaner."""
- yield from self._try_command(
+ await self._try_command(
"Unable to locate the botvac: %s", self._vacuum.find)
- @asyncio.coroutine
- def async_send_command(self, command, params=None, **kwargs):
+ async def async_send_command(self, command, params=None, **kwargs):
"""Send raw command."""
- yield from self._try_command(
+ await self._try_command(
"Unable to send command to the vacuum: %s",
self._vacuum.raw_command, command, params)
- @asyncio.coroutine
- def async_remote_control_start(self):
+ async def async_remote_control_start(self):
"""Start remote control mode."""
- yield from self._try_command(
+ await self._try_command(
"Unable to start remote control the vacuum: %s",
self._vacuum.manual_start)
- @asyncio.coroutine
- def async_remote_control_stop(self):
+ async def async_remote_control_stop(self):
"""Stop remote control mode."""
- yield from self._try_command(
+ await self._try_command(
"Unable to stop remote control the vacuum: %s",
self._vacuum.manual_stop)
- @asyncio.coroutine
- def async_remote_control_move(self,
- rotation: int = 0,
- velocity: float = 0.3,
- duration: int = 1500):
+ async def async_remote_control_move(self,
+ rotation: int = 0,
+ velocity: float = 0.3,
+ duration: int = 1500):
"""Move vacuum with remote control mode."""
- yield from self._try_command(
+ await self._try_command(
"Unable to move with remote control the vacuum: %s",
self._vacuum.manual_control,
velocity=velocity, rotation=rotation, duration=duration)
- @asyncio.coroutine
- def async_remote_control_move_step(self,
- rotation: int = 0,
- velocity: float = 0.2,
- duration: int = 1500):
+ async def async_remote_control_move_step(self,
+ rotation: int = 0,
+ velocity: float = 0.2,
+ duration: int = 1500):
"""Move vacuum one step with remote control mode."""
- yield from self._try_command(
+ await self._try_command(
"Unable to remote control the vacuum: %s",
self._vacuum.manual_control_once,
velocity=velocity, rotation=rotation, duration=duration)
diff --git a/homeassistant/components/verisure.py b/homeassistant/components/verisure.py
index 1f26ab639d6..016547697b9 100644
--- a/homeassistant/components/verisure.py
+++ b/homeassistant/components/verisure.py
@@ -10,8 +10,8 @@ from datetime import timedelta
import voluptuous as vol
-from homeassistant.const import (CONF_PASSWORD, CONF_USERNAME,
- EVENT_HOMEASSISTANT_STOP)
+from homeassistant.const import (CONF_PASSWORD, CONF_SCAN_INTERVAL,
+ CONF_USERNAME, EVENT_HOMEASSISTANT_STOP)
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
@@ -35,6 +35,9 @@ CONF_SMARTCAM = 'smartcam'
DOMAIN = 'verisure'
+MIN_SCAN_INTERVAL = timedelta(minutes=1)
+DEFAULT_SCAN_INTERVAL = timedelta(minutes=1)
+
SERVICE_CAPTURE_SMARTCAM = 'capture_smartcam'
HUB = None
@@ -53,6 +56,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_SMARTPLUGS, default=True): cv.boolean,
vol.Optional(CONF_THERMOMETERS, default=True): cv.boolean,
vol.Optional(CONF_SMARTCAM, default=True): cv.boolean,
+ vol.Optional(CONF_SCAN_INTERVAL, default=DEFAULT_SCAN_INTERVAL): (
+ vol.All(cv.time_period, vol.Clamp(min=MIN_SCAN_INTERVAL))),
}),
}, extra=vol.ALLOW_EXTRA)
@@ -66,6 +71,8 @@ def setup(hass, config):
import verisure
global HUB
HUB = VerisureHub(config[DOMAIN], verisure)
+ HUB.update_overview = Throttle(
+ config[DOMAIN][CONF_SCAN_INTERVAL])(HUB.update_overview)
if not HUB.login():
return False
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
@@ -140,7 +147,6 @@ class VerisureHub:
return False
return True
- @Throttle(timedelta(seconds=60))
def update_overview(self):
"""Update the overview."""
try:
diff --git a/homeassistant/components/wake_on_lan.py b/homeassistant/components/wake_on_lan.py
index 5bcb0d4dd79..dba99bf7e3d 100644
--- a/homeassistant/components/wake_on_lan.py
+++ b/homeassistant/components/wake_on_lan.py
@@ -4,7 +4,6 @@ Component to wake up devices sending Wake-On-LAN magic packets.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/wake_on_lan/
"""
-import asyncio
from functools import partial
import logging
@@ -29,24 +28,22 @@ WAKE_ON_LAN_SEND_MAGIC_PACKET_SCHEMA = vol.Schema({
})
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the wake on LAN component."""
import wakeonlan
- @asyncio.coroutine
- def send_magic_packet(call):
+ async def send_magic_packet(call):
"""Send magic packet to wake up a device."""
mac_address = call.data.get(CONF_MAC)
broadcast_address = call.data.get(CONF_BROADCAST_ADDRESS)
_LOGGER.info("Send magic packet to mac %s (broadcast: %s)",
mac_address, broadcast_address)
if broadcast_address is not None:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(wakeonlan.send_magic_packet, mac_address,
ip_address=broadcast_address))
else:
- yield from hass.async_add_job(
+ await hass.async_add_job(
partial(wakeonlan.send_magic_packet, mac_address))
hass.services.async_register(
diff --git a/homeassistant/components/weather/__init__.py b/homeassistant/components/weather/__init__.py
index a43999f2276..f9a8f1fbbe4 100644
--- a/homeassistant/components/weather/__init__.py
+++ b/homeassistant/components/weather/__init__.py
@@ -4,7 +4,6 @@ Weather component that handles meteorological data for your location.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/weather/
"""
-import asyncio
import logging
from homeassistant.helpers.entity_component import EntityComponent
@@ -27,6 +26,8 @@ ATTR_FORECAST_PRECIPITATION = 'precipitation'
ATTR_FORECAST_TEMP = 'temperature'
ATTR_FORECAST_TEMP_LOW = 'templow'
ATTR_FORECAST_TIME = 'datetime'
+ATTR_FORECAST_WIND_SPEED = 'wind_speed'
+ATTR_FORECAST_WIND_BEARING = 'wind_bearing'
ATTR_WEATHER_ATTRIBUTION = 'attribution'
ATTR_WEATHER_HUMIDITY = 'humidity'
ATTR_WEATHER_OZONE = 'ozone'
@@ -37,12 +38,11 @@ ATTR_WEATHER_WIND_BEARING = 'wind_bearing'
ATTR_WEATHER_WIND_SPEED = 'wind_speed'
-@asyncio.coroutine
-def async_setup(hass, config):
+async def async_setup(hass, config):
"""Set up the weather component."""
component = EntityComponent(_LOGGER, DOMAIN, hass)
- yield from component.async_setup(config)
+ await component.async_setup(config)
return True
diff --git a/homeassistant/components/weather/buienradar.py b/homeassistant/components/weather/buienradar.py
index 6b92eb97c9e..1ec3fc513e9 100644
--- a/homeassistant/components/weather/buienradar.py
+++ b/homeassistant/components/weather/buienradar.py
@@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/weather.buienradar/
"""
import logging
-import asyncio
import voluptuous as vol
@@ -55,9 +54,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
})
-@asyncio.coroutine
-def async_setup_platform(hass, config, async_add_entities,
- discovery_info=None):
+async def async_setup_platform(hass, config, async_add_entities,
+ discovery_info=None):
"""Set up the buienradar platform."""
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
@@ -86,7 +84,7 @@ def async_setup_platform(hass, config, async_add_entities,
async_add_entities([BrWeather(data, config)])
# schedule the first update in 1 minute from now:
- yield from data.schedule_update(1)
+ await data.schedule_update(1)
class BrWeather(WeatherEntity):
diff --git a/homeassistant/components/weather/darksky.py b/homeassistant/components/weather/darksky.py
index 34a6fd3d6f6..c753c0249ca 100644
--- a/homeassistant/components/weather/darksky.py
+++ b/homeassistant/components/weather/darksky.py
@@ -13,10 +13,12 @@ import voluptuous as vol
from homeassistant.components.weather import (
ATTR_FORECAST_TEMP, ATTR_FORECAST_TIME, ATTR_FORECAST_CONDITION,
+ ATTR_FORECAST_WIND_SPEED, ATTR_FORECAST_WIND_BEARING,
+ ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_PRECIPITATION,
PLATFORM_SCHEMA, WeatherEntity)
from homeassistant.const import (
CONF_API_KEY, CONF_LATITUDE, CONF_LONGITUDE, CONF_NAME, TEMP_CELSIUS,
- TEMP_FAHRENHEIT)
+ CONF_MODE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
from homeassistant.util import Throttle
@@ -26,6 +28,8 @@ _LOGGER = logging.getLogger(__name__)
ATTRIBUTION = "Powered by Dark Sky"
+FORECAST_MODE = ['hourly', 'daily']
+
MAP_CONDITION = {
'clear-day': 'sunny',
'clear-night': 'clear-night',
@@ -50,6 +54,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_API_KEY): cv.string,
vol.Optional(CONF_LATITUDE): cv.latitude,
vol.Optional(CONF_LONGITUDE): cv.longitude,
+ vol.Optional(CONF_MODE, default='hourly'): vol.In(FORECAST_MODE),
vol.Optional(CONF_UNITS): vol.In(['auto', 'si', 'us', 'ca', 'uk', 'uk2']),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
@@ -62,6 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
latitude = config.get(CONF_LATITUDE, hass.config.latitude)
longitude = config.get(CONF_LONGITUDE, hass.config.longitude)
name = config.get(CONF_NAME)
+ mode = config.get(CONF_MODE)
units = config.get(CONF_UNITS)
if not units:
@@ -70,16 +76,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
dark_sky = DarkSkyData(
config.get(CONF_API_KEY), latitude, longitude, units)
- add_entities([DarkSkyWeather(name, dark_sky)], True)
+ add_entities([DarkSkyWeather(name, dark_sky, mode)], True)
class DarkSkyWeather(WeatherEntity):
"""Representation of a weather condition."""
- def __init__(self, name, dark_sky):
+ def __init__(self, name, dark_sky, mode):
"""Initialize Dark Sky weather."""
self._name = name
self._dark_sky = dark_sky
+ self._mode = mode
self._ds_data = None
self._ds_currently = None
@@ -117,11 +124,26 @@ class DarkSkyWeather(WeatherEntity):
"""Return the wind speed."""
return self._ds_currently.get('windSpeed')
+ @property
+ def wind_bearing(self):
+ """Return the wind bearing."""
+ return self._ds_currently.get('windBearing')
+
+ @property
+ def ozone(self):
+ """Return the ozone level."""
+ return self._ds_currently.get('ozone')
+
@property
def pressure(self):
"""Return the pressure."""
return self._ds_currently.get('pressure')
+ @property
+ def visibility(self):
+ """Return the visibility."""
+ return self._ds_currently.get('visibility')
+
@property
def condition(self):
"""Return the weather condition."""
@@ -130,14 +152,47 @@ class DarkSkyWeather(WeatherEntity):
@property
def forecast(self):
"""Return the forecast array."""
- return [{
- ATTR_FORECAST_TIME:
- datetime.fromtimestamp(entry.d.get('time')).isoformat(),
- ATTR_FORECAST_TEMP:
- entry.d.get('temperature'),
- ATTR_FORECAST_CONDITION:
- MAP_CONDITION.get(entry.d.get('icon'))
- } for entry in self._ds_hourly.data]
+ # Per conversation with Joshua Reyes of Dark Sky, to get the total
+ # forecasted precipitation, you have to multiple the intensity by
+ # the hours for the forecast interval
+ def calc_precipitation(intensity, hours):
+ amount = None
+ if intensity is not None:
+ amount = round((intensity * hours), 1)
+ return amount if amount > 0 else None
+
+ data = None
+
+ if self._mode == 'daily':
+ data = [{
+ ATTR_FORECAST_TIME:
+ datetime.fromtimestamp(entry.d.get('time')).isoformat(),
+ ATTR_FORECAST_TEMP:
+ entry.d.get('temperatureHigh'),
+ ATTR_FORECAST_TEMP_LOW:
+ entry.d.get('temperatureLow'),
+ ATTR_FORECAST_PRECIPITATION:
+ calc_precipitation(entry.d.get('precipIntensity'), 24),
+ ATTR_FORECAST_WIND_SPEED:
+ entry.d.get('windSpeed'),
+ ATTR_FORECAST_WIND_BEARING:
+ entry.d.get('windBearing'),
+ ATTR_FORECAST_CONDITION:
+ MAP_CONDITION.get(entry.d.get('icon'))
+ } for entry in self._ds_daily.data]
+ else:
+ data = [{
+ ATTR_FORECAST_TIME:
+ datetime.fromtimestamp(entry.d.get('time')).isoformat(),
+ ATTR_FORECAST_TEMP:
+ entry.d.get('temperature'),
+ ATTR_FORECAST_PRECIPITATION:
+ calc_precipitation(entry.d.get('precipIntensity'), 1),
+ ATTR_FORECAST_CONDITION:
+ MAP_CONDITION.get(entry.d.get('icon'))
+ } for entry in self._ds_hourly.data]
+
+ return data
def update(self):
"""Get the latest data from Dark Sky."""
diff --git a/homeassistant/components/weather/openweathermap.py b/homeassistant/components/weather/openweathermap.py
index b300fcbcbec..b70413b9565 100644
--- a/homeassistant/components/weather/openweathermap.py
+++ b/homeassistant/components/weather/openweathermap.py
@@ -11,7 +11,9 @@ import voluptuous as vol
from homeassistant.components.weather import (
ATTR_FORECAST_CONDITION, ATTR_FORECAST_PRECIPITATION, ATTR_FORECAST_TEMP,
- ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, PLATFORM_SCHEMA, WeatherEntity)
+ ATTR_FORECAST_TEMP_LOW, ATTR_FORECAST_TIME, ATTR_FORECAST_WIND_SPEED,
+ ATTR_FORECAST_WIND_BEARING,
+ PLATFORM_SCHEMA, WeatherEntity)
from homeassistant.const import (
CONF_API_KEY, TEMP_CELSIUS, CONF_LATITUDE, CONF_LONGITUDE, CONF_MODE,
CONF_NAME, STATE_UNKNOWN)
@@ -22,9 +24,6 @@ REQUIREMENTS = ['pyowm==2.9.0']
_LOGGER = logging.getLogger(__name__)
-ATTR_FORECAST_WIND_SPEED = 'wind_speed'
-ATTR_FORECAST_WIND_BEARING = 'wind_bearing'
-
ATTRIBUTION = 'Data provided by OpenWeatherMap'
FORECAST_MODE = ['hourly', 'daily']
@@ -131,6 +130,9 @@ class OpenWeatherMapWeather(WeatherEntity):
@property
def wind_speed(self):
"""Return the wind speed."""
+ if self.hass.config.units.name == 'imperial':
+ return round(self.data.get_wind().get('speed') * 2.24, 2)
+
return round(self.data.get_wind().get('speed') * 3.6, 2)
@property
diff --git a/homeassistant/components/webhook.py b/homeassistant/components/webhook.py
new file mode 100644
index 00000000000..2a4c3f973f2
--- /dev/null
+++ b/homeassistant/components/webhook.py
@@ -0,0 +1,85 @@
+"""Webhooks for Home Assistant.
+
+For more details about this component, please refer to the documentation at
+https://home-assistant.io/components/webhook/
+"""
+import logging
+
+from aiohttp.web import Response
+
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+from homeassistant.auth.util import generate_secret
+from homeassistant.components.http.view import HomeAssistantView
+
+DOMAIN = 'webhook'
+DEPENDENCIES = ['http']
+_LOGGER = logging.getLogger(__name__)
+
+
+@callback
+@bind_hass
+def async_register(hass, webhook_id, handler):
+ """Register a webhook."""
+ handlers = hass.data.setdefault(DOMAIN, {})
+
+ if webhook_id in handlers:
+ raise ValueError('Handler is already defined!')
+
+ handlers[webhook_id] = handler
+
+
+@callback
+@bind_hass
+def async_unregister(hass, webhook_id):
+ """Remove a webhook."""
+ handlers = hass.data.setdefault(DOMAIN, {})
+ handlers.pop(webhook_id, None)
+
+
+@callback
+def async_generate_id():
+ """Generate a webhook_id."""
+ return generate_secret(entropy=32)
+
+
+@callback
+@bind_hass
+def async_generate_url(hass, webhook_id):
+ """Generate a webhook_id."""
+ return "{}/api/webhook/{}".format(hass.config.api.base_url, webhook_id)
+
+
+async def async_setup(hass, config):
+ """Initialize the webhook component."""
+ hass.http.register_view(WebhookView)
+ return True
+
+
+class WebhookView(HomeAssistantView):
+ """Handle incoming webhook requests."""
+
+ url = "/api/webhook/{webhook_id}"
+ name = "api:webhook"
+ requires_auth = False
+
+ async def post(self, request, webhook_id):
+ """Handle webhook call."""
+ hass = request.app['hass']
+ handlers = hass.data.setdefault(DOMAIN, {})
+ handler = handlers.get(webhook_id)
+
+ # Always respond successfully to not give away if a hook exists or not.
+ if handler is None:
+ _LOGGER.warning(
+ 'Received message for unregistered webhook %s', webhook_id)
+ return Response(status=200)
+
+ try:
+ response = await handler(hass, webhook_id, request)
+ if response is None:
+ response = Response(status=200)
+ return response
+ except Exception: # pylint: disable=broad-except
+ _LOGGER.exception("Error processing webhook %s", webhook_id)
+ return Response(status=200)
diff --git a/homeassistant/components/websocket_api.py b/homeassistant/components/websocket_api.py
deleted file mode 100644
index 4e7c186facc..00000000000
--- a/homeassistant/components/websocket_api.py
+++ /dev/null
@@ -1,654 +0,0 @@
-"""
-Websocket based API for Home Assistant.
-
-For more details about this component, please refer to the documentation at
-https://developers.home-assistant.io/docs/external_api_websocket.html
-"""
-import asyncio
-from concurrent import futures
-from contextlib import suppress
-from functools import partial, wraps
-import json
-import logging
-
-from aiohttp import web
-import voluptuous as vol
-from voluptuous.humanize import humanize_error
-
-from homeassistant.const import (
- MATCH_ALL, EVENT_TIME_CHANGED, EVENT_HOMEASSISTANT_STOP,
- __version__)
-from homeassistant.core import Context, callback, HomeAssistant
-from homeassistant.loader import bind_hass
-from homeassistant.helpers.json import JSONEncoder
-from homeassistant.helpers import config_validation as cv
-from homeassistant.helpers.service import async_get_all_descriptions
-from homeassistant.components.http import HomeAssistantView
-from homeassistant.components.http.auth import validate_password
-from homeassistant.components.http.const import KEY_AUTHENTICATED
-from homeassistant.components.http.ban import process_wrong_login, \
- process_success_login
-
-DOMAIN = 'websocket_api'
-
-URL = '/api/websocket'
-DEPENDENCIES = ('http',)
-
-MAX_PENDING_MSG = 512
-
-ERR_ID_REUSE = 1
-ERR_INVALID_FORMAT = 2
-ERR_NOT_FOUND = 3
-ERR_UNKNOWN_COMMAND = 4
-ERR_UNKNOWN_ERROR = 5
-
-TYPE_AUTH = 'auth'
-TYPE_AUTH_INVALID = 'auth_invalid'
-TYPE_AUTH_OK = 'auth_ok'
-TYPE_AUTH_REQUIRED = 'auth_required'
-TYPE_CALL_SERVICE = 'call_service'
-TYPE_EVENT = 'event'
-TYPE_GET_CONFIG = 'get_config'
-TYPE_GET_SERVICES = 'get_services'
-TYPE_GET_STATES = 'get_states'
-TYPE_PING = 'ping'
-TYPE_PONG = 'pong'
-TYPE_RESULT = 'result'
-TYPE_SUBSCRIBE_EVENTS = 'subscribe_events'
-TYPE_UNSUBSCRIBE_EVENTS = 'unsubscribe_events'
-
-_LOGGER = logging.getLogger(__name__)
-
-JSON_DUMP = partial(json.dumps, cls=JSONEncoder)
-
-AUTH_MESSAGE_SCHEMA = vol.Schema({
- vol.Required('type'): TYPE_AUTH,
- vol.Exclusive('api_password', 'auth'): str,
- vol.Exclusive('access_token', 'auth'): str,
-})
-
-# Minimal requirements of a message
-MINIMAL_MESSAGE_SCHEMA = vol.Schema({
- vol.Required('id'): cv.positive_int,
- vol.Required('type'): cv.string,
-}, extra=vol.ALLOW_EXTRA)
-# Base schema to extend by message handlers
-BASE_COMMAND_MESSAGE_SCHEMA = vol.Schema({
- vol.Required('id'): cv.positive_int,
-})
-
-
-SCHEMA_SUBSCRIBE_EVENTS = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_SUBSCRIBE_EVENTS,
- vol.Optional('event_type', default=MATCH_ALL): str,
-})
-
-
-SCHEMA_UNSUBSCRIBE_EVENTS = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_UNSUBSCRIBE_EVENTS,
- vol.Required('subscription'): cv.positive_int,
-})
-
-
-SCHEMA_CALL_SERVICE = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_CALL_SERVICE,
- vol.Required('domain'): str,
- vol.Required('service'): str,
- vol.Optional('service_data'): dict
-})
-
-
-SCHEMA_GET_STATES = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_GET_STATES,
-})
-
-
-SCHEMA_GET_SERVICES = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_GET_SERVICES,
-})
-
-
-SCHEMA_GET_CONFIG = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_GET_CONFIG,
-})
-
-
-SCHEMA_PING = BASE_COMMAND_MESSAGE_SCHEMA.extend({
- vol.Required('type'): TYPE_PING,
-})
-
-
-# Define the possible errors that occur when connections are cancelled.
-# Originally, this was just asyncio.CancelledError, but issue #9546 showed
-# that futures.CancelledErrors can also occur in some situations.
-CANCELLATION_ERRORS = (asyncio.CancelledError, futures.CancelledError)
-
-
-def auth_ok_message():
- """Return an auth_ok message."""
- return {
- 'type': TYPE_AUTH_OK,
- 'ha_version': __version__,
- }
-
-
-def auth_required_message():
- """Return an auth_required message."""
- return {
- 'type': TYPE_AUTH_REQUIRED,
- 'ha_version': __version__,
- }
-
-
-def auth_invalid_message(message):
- """Return an auth_invalid message."""
- return {
- 'type': TYPE_AUTH_INVALID,
- 'message': message,
- }
-
-
-def event_message(iden, event):
- """Return an event message."""
- return {
- 'id': iden,
- 'type': TYPE_EVENT,
- 'event': event.as_dict(),
- }
-
-
-def error_message(iden, code, message):
- """Return an error result message."""
- return {
- 'id': iden,
- 'type': TYPE_RESULT,
- 'success': False,
- 'error': {
- 'code': code,
- 'message': message,
- },
- }
-
-
-def pong_message(iden):
- """Return a pong message."""
- return {
- 'id': iden,
- 'type': TYPE_PONG,
- }
-
-
-def result_message(iden, result=None):
- """Return a success result message."""
- return {
- 'id': iden,
- 'type': TYPE_RESULT,
- 'success': True,
- 'result': result,
- }
-
-
-@bind_hass
-@callback
-def async_register_command(hass, command, handler, schema):
- """Register a websocket command."""
- handlers = hass.data.get(DOMAIN)
- if handlers is None:
- handlers = hass.data[DOMAIN] = {}
- handlers[command] = (handler, schema)
-
-
-def require_owner(func):
- """Websocket decorator to require user to be an owner."""
- @wraps(func)
- def with_owner(hass, connection, msg):
- """Check owner and call function."""
- user = connection.request.get('hass_user')
-
- if user is None or not user.is_owner:
- connection.to_write.put_nowait(error_message(
- msg['id'], 'unauthorized', 'This command is for owners only.'))
- return
-
- func(hass, connection, msg)
-
- return with_owner
-
-
-async def async_setup(hass, config):
- """Initialize the websocket API."""
- hass.http.register_view(WebsocketAPIView)
-
- async_register_command(hass, TYPE_SUBSCRIBE_EVENTS,
- handle_subscribe_events, SCHEMA_SUBSCRIBE_EVENTS)
- async_register_command(hass, TYPE_UNSUBSCRIBE_EVENTS,
- handle_unsubscribe_events,
- SCHEMA_UNSUBSCRIBE_EVENTS)
- async_register_command(hass, TYPE_CALL_SERVICE,
- handle_call_service, SCHEMA_CALL_SERVICE)
- async_register_command(hass, TYPE_GET_STATES,
- handle_get_states, SCHEMA_GET_STATES)
- async_register_command(hass, TYPE_GET_SERVICES,
- handle_get_services, SCHEMA_GET_SERVICES)
- async_register_command(hass, TYPE_GET_CONFIG,
- handle_get_config, SCHEMA_GET_CONFIG)
- async_register_command(hass, TYPE_PING,
- handle_ping, SCHEMA_PING)
-
- return True
-
-
-class WebsocketAPIView(HomeAssistantView):
- """View to serve a websockets endpoint."""
-
- name = "websocketapi"
- url = URL
- requires_auth = False
-
- async def get(self, request):
- """Handle an incoming websocket connection."""
- return await ActiveConnection(request.app['hass'], request).handle()
-
-
-class ActiveConnection:
- """Handle an active websocket client connection."""
-
- def __init__(self, hass, request):
- """Initialize an active connection."""
- self.hass = hass
- self.request = request
- self.wsock = None
- self.event_listeners = {}
- self.to_write = asyncio.Queue(maxsize=MAX_PENDING_MSG, loop=hass.loop)
- self._handle_task = None
- self._writer_task = None
-
- @property
- def user(self):
- """Return the user associated with the connection."""
- return self.request.get('hass_user')
-
- def context(self, msg):
- """Return a context."""
- user = self.user
- if user is None:
- return Context()
- return Context(user_id=user.id)
-
- def debug(self, message1, message2=''):
- """Print a debug message."""
- _LOGGER.debug("WS %s: %s %s", id(self.wsock), message1, message2)
-
- def log_error(self, message1, message2=''):
- """Print an error message."""
- _LOGGER.error("WS %s: %s %s", id(self.wsock), message1, message2)
-
- async def _writer(self):
- """Write outgoing messages."""
- # Exceptions if Socket disconnected or cancelled by connection handler
- with suppress(RuntimeError, *CANCELLATION_ERRORS):
- while not self.wsock.closed:
- message = await self.to_write.get()
- if message is None:
- break
- self.debug("Sending", message)
- try:
- await self.wsock.send_json(message, dumps=JSON_DUMP)
- except TypeError as err:
- _LOGGER.error('Unable to serialize to JSON: %s\n%s',
- err, message)
-
- @callback
- def send_message_outside(self, message):
- """Send a message to the client.
-
- Closes connection if the client is not reading the messages.
-
- Async friendly.
- """
- try:
- self.to_write.put_nowait(message)
- except asyncio.QueueFull:
- self.log_error("Client exceeded max pending messages [2]:",
- MAX_PENDING_MSG)
- self.cancel()
-
- @callback
- def cancel(self):
- """Cancel the connection."""
- self._handle_task.cancel()
- self._writer_task.cancel()
-
- async def handle(self):
- """Handle the websocket connection."""
- request = self.request
- wsock = self.wsock = web.WebSocketResponse(heartbeat=55)
- await wsock.prepare(request)
- self.debug("Connected")
-
- self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)
-
- @callback
- def handle_hass_stop(event):
- """Cancel this connection."""
- self.cancel()
-
- unsub_stop = self.hass.bus.async_listen(
- EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
- self._writer_task = self.hass.async_add_job(self._writer())
- final_message = None
- msg = None
- authenticated = False
-
- try:
- if request[KEY_AUTHENTICATED]:
- authenticated = True
-
- # always request auth when auth is active
- # even request passed pre-authentication (trusted networks)
- # or when using legacy api_password
- if self.hass.auth.active or not authenticated:
- self.debug("Request auth")
- await self.wsock.send_json(auth_required_message())
- msg = await wsock.receive_json()
- msg = AUTH_MESSAGE_SCHEMA(msg)
-
- if self.hass.auth.active and 'access_token' in msg:
- self.debug("Received access_token")
- refresh_token = \
- await self.hass.auth.async_validate_access_token(
- msg['access_token'])
- authenticated = refresh_token is not None
- if authenticated:
- request['hass_user'] = refresh_token.user
- request['refresh_token_id'] = refresh_token.id
-
- elif ((not self.hass.auth.active or
- self.hass.auth.support_legacy) and
- 'api_password' in msg):
- self.debug("Received api_password")
- authenticated = validate_password(
- request, msg['api_password'])
-
- if not authenticated:
- self.debug("Authorization failed")
- await self.wsock.send_json(
- auth_invalid_message('Invalid access token or password'))
- await process_wrong_login(request)
- return wsock
-
- self.debug("Auth OK")
- await process_success_login(request)
- await self.wsock.send_json(auth_ok_message())
-
- # ---------- AUTH PHASE OVER ----------
-
- msg = await wsock.receive_json()
- last_id = 0
- handlers = self.hass.data[DOMAIN]
-
- while msg:
- self.debug("Received", msg)
- msg = MINIMAL_MESSAGE_SCHEMA(msg)
- cur_id = msg['id']
-
- if cur_id <= last_id:
- self.to_write.put_nowait(error_message(
- cur_id, ERR_ID_REUSE,
- 'Identifier values have to increase.'))
-
- elif msg['type'] not in handlers:
- self.log_error(
- 'Received invalid command: {}'.format(msg['type']))
- self.to_write.put_nowait(error_message(
- cur_id, ERR_UNKNOWN_COMMAND,
- 'Unknown command.'))
-
- else:
- handler, schema = handlers[msg['type']]
- try:
- handler(self.hass, self, schema(msg))
- except Exception: # pylint: disable=broad-except
- _LOGGER.exception('Error handling message: %s', msg)
- self.to_write.put_nowait(error_message(
- cur_id, ERR_UNKNOWN_ERROR,
- 'Unknown error.'))
-
- last_id = cur_id
- msg = await wsock.receive_json()
-
- except vol.Invalid as err:
- error_msg = "Message incorrectly formatted: "
- if msg:
- error_msg += humanize_error(msg, err)
- else:
- error_msg += str(err)
-
- self.log_error(error_msg)
-
- if not authenticated:
- final_message = auth_invalid_message(error_msg)
-
- else:
- if isinstance(msg, dict):
- iden = msg.get('id')
- else:
- iden = None
-
- final_message = error_message(
- iden, ERR_INVALID_FORMAT, error_msg)
-
- except TypeError as err:
- if wsock.closed:
- self.debug("Connection closed by client")
- else:
- _LOGGER.exception("Unexpected TypeError: %s", err)
-
- except ValueError as err:
- msg = "Received invalid JSON"
- value = getattr(err, 'doc', None) # Py3.5+ only
- if value:
- msg += ': {}'.format(value)
- self.log_error(msg)
- self._writer_task.cancel()
-
- except CANCELLATION_ERRORS:
- self.debug("Connection cancelled")
-
- except asyncio.QueueFull:
- self.log_error("Client exceeded max pending messages [1]:",
- MAX_PENDING_MSG)
- self._writer_task.cancel()
-
- except Exception: # pylint: disable=broad-except
- error = "Unexpected error inside websocket API. "
- if msg is not None:
- error += str(msg)
- _LOGGER.exception(error)
-
- finally:
- unsub_stop()
-
- for unsub in self.event_listeners.values():
- unsub()
-
- try:
- if final_message is not None:
- self.to_write.put_nowait(final_message)
- self.to_write.put_nowait(None)
- # Make sure all error messages are written before closing
- await self._writer_task
- except asyncio.QueueFull:
- self._writer_task.cancel()
-
- await wsock.close()
- self.debug("Closed connection")
-
- return wsock
-
-
-def async_response(func):
- """Decorate an async function to handle WebSocket API messages."""
- async def handle_msg_response(hass, connection, msg):
- """Create a response and handle exception."""
- try:
- await func(hass, connection, msg)
- except Exception: # pylint: disable=broad-except
- _LOGGER.exception("Unexpected exception")
- connection.send_message_outside(error_message(
- msg['id'], 'unknown', 'Unexpected error occurred'))
-
- @callback
- @wraps(func)
- def schedule_handler(hass, connection, msg):
- """Schedule the handler."""
- hass.async_create_task(handle_msg_response(hass, connection, msg))
-
- return schedule_handler
-
-
-@callback
-def handle_subscribe_events(hass, connection, msg):
- """Handle subscribe events command.
-
- Async friendly.
- """
- async def forward_events(event):
- """Forward events to websocket."""
- if event.event_type == EVENT_TIME_CHANGED:
- return
-
- connection.send_message_outside(event_message(msg['id'], event))
-
- connection.event_listeners[msg['id']] = hass.bus.async_listen(
- msg['event_type'], forward_events)
-
- connection.to_write.put_nowait(result_message(msg['id']))
-
-
-@callback
-def handle_unsubscribe_events(hass, connection, msg):
- """Handle unsubscribe events command.
-
- Async friendly.
- """
- subscription = msg['subscription']
-
- if subscription in connection.event_listeners:
- connection.event_listeners.pop(subscription)()
- connection.to_write.put_nowait(result_message(msg['id']))
- else:
- connection.to_write.put_nowait(error_message(
- msg['id'], ERR_NOT_FOUND, 'Subscription not found.'))
-
-
-@async_response
-async def handle_call_service(hass, connection, msg):
- """Handle call service command.
-
- Async friendly.
- """
- blocking = True
- if (msg['domain'] == 'homeassistant' and
- msg['service'] in ['restart', 'stop']):
- blocking = False
- await hass.services.async_call(
- msg['domain'], msg['service'], msg.get('service_data'), blocking,
- connection.context(msg))
- connection.send_message_outside(result_message(msg['id']))
-
-
-@callback
-def handle_get_states(hass, connection, msg):
- """Handle get states command.
-
- Async friendly.
- """
- connection.to_write.put_nowait(result_message(
- msg['id'], hass.states.async_all()))
-
-
-@async_response
-async def handle_get_services(hass, connection, msg):
- """Handle get services command.
-
- Async friendly.
- """
- descriptions = await async_get_all_descriptions(hass)
- connection.send_message_outside(
- result_message(msg['id'], descriptions))
-
-
-@callback
-def handle_get_config(hass, connection, msg):
- """Handle get config command.
-
- Async friendly.
- """
- connection.to_write.put_nowait(result_message(
- msg['id'], hass.config.as_dict()))
-
-
-@callback
-def handle_ping(hass, connection, msg):
- """Handle ping command.
-
- Async friendly.
- """
- connection.to_write.put_nowait(pong_message(msg['id']))
-
-
-def ws_require_user(
- only_owner=False, only_system_user=False, allow_system_user=True,
- only_active_user=True, only_inactive_user=False):
- """Decorate function validating login user exist in current WS connection.
-
- Will write out error message if not authenticated.
- """
- def validator(func):
- """Decorate func."""
- @wraps(func)
- def check_current_user(hass: HomeAssistant,
- connection: ActiveConnection,
- msg):
- """Check current user."""
- def output_error(message_id, message):
- """Output error message."""
- connection.send_message_outside(error_message(
- msg['id'], message_id, message))
-
- if connection.user is None:
- output_error('no_user', 'Not authenticated as a user')
- return
-
- if only_owner and not connection.user.is_owner:
- output_error('only_owner', 'Only allowed as owner')
- return
-
- if (only_system_user and
- not connection.user.system_generated):
- output_error('only_system_user',
- 'Only allowed as system user')
- return
-
- if (not allow_system_user
- and connection.user.system_generated):
- output_error('not_system_user', 'Not allowed as system user')
- return
-
- if (only_active_user and
- not connection.user.is_active):
- output_error('only_active_user',
- 'Only allowed as active user')
- return
-
- if only_inactive_user and connection.user.is_active:
- output_error('only_inactive_user',
- 'Not allowed as active user')
- return
-
- return func(hass, connection, msg)
-
- return check_current_user
-
- return validator
diff --git a/homeassistant/components/websocket_api/__init__.py b/homeassistant/components/websocket_api/__init__.py
new file mode 100644
index 00000000000..90c802423ce
--- /dev/null
+++ b/homeassistant/components/websocket_api/__init__.py
@@ -0,0 +1,42 @@
+"""
+Websocket based API for Home Assistant.
+
+For more details about this component, please refer to the documentation at
+https://developers.home-assistant.io/docs/external_api_websocket.html
+"""
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+from . import commands, connection, const, decorators, http, messages
+
+DOMAIN = const.DOMAIN
+
+DEPENDENCIES = ('http',)
+
+# Backwards compat / Make it easier to integrate
+# pylint: disable=invalid-name
+ActiveConnection = connection.ActiveConnection
+BASE_COMMAND_MESSAGE_SCHEMA = messages.BASE_COMMAND_MESSAGE_SCHEMA
+error_message = messages.error_message
+result_message = messages.result_message
+async_response = decorators.async_response
+require_owner = decorators.require_owner
+ws_require_user = decorators.ws_require_user
+# pylint: enable=invalid-name
+
+
+@bind_hass
+@callback
+def async_register_command(hass, command, handler, schema):
+ """Register a websocket command."""
+ handlers = hass.data.get(DOMAIN)
+ if handlers is None:
+ handlers = hass.data[DOMAIN] = {}
+ handlers[command] = (handler, schema)
+
+
+async def async_setup(hass, config):
+ """Initialize the websocket API."""
+ hass.http.register_view(http.WebsocketAPIView)
+ commands.async_register_commands(hass)
+ return True
diff --git a/homeassistant/components/websocket_api/auth.py b/homeassistant/components/websocket_api/auth.py
new file mode 100644
index 00000000000..db41f3df06d
--- /dev/null
+++ b/homeassistant/components/websocket_api/auth.py
@@ -0,0 +1,99 @@
+"""Handle the auth of a connection."""
+import voluptuous as vol
+from voluptuous.humanize import humanize_error
+
+from homeassistant.const import __version__
+from homeassistant.components.http.auth import validate_password
+from homeassistant.components.http.ban import process_wrong_login, \
+ process_success_login
+
+from .connection import ActiveConnection
+from .error import Disconnect
+
+TYPE_AUTH = 'auth'
+TYPE_AUTH_INVALID = 'auth_invalid'
+TYPE_AUTH_OK = 'auth_ok'
+TYPE_AUTH_REQUIRED = 'auth_required'
+
+AUTH_MESSAGE_SCHEMA = vol.Schema({
+ vol.Required('type'): TYPE_AUTH,
+ vol.Exclusive('api_password', 'auth'): str,
+ vol.Exclusive('access_token', 'auth'): str,
+})
+
+
+def auth_ok_message():
+ """Return an auth_ok message."""
+ return {
+ 'type': TYPE_AUTH_OK,
+ 'ha_version': __version__,
+ }
+
+
+def auth_required_message():
+ """Return an auth_required message."""
+ return {
+ 'type': TYPE_AUTH_REQUIRED,
+ 'ha_version': __version__,
+ }
+
+
+def auth_invalid_message(message):
+ """Return an auth_invalid message."""
+ return {
+ 'type': TYPE_AUTH_INVALID,
+ 'message': message,
+ }
+
+
+class AuthPhase:
+ """Connection that requires client to authenticate first."""
+
+ def __init__(self, logger, hass, send_message, request):
+ """Initialize the authentiated connection."""
+ self._hass = hass
+ self._send_message = send_message
+ self._logger = logger
+ self._request = request
+ self._authenticated = False
+ self._connection = None
+
+ async def async_handle(self, msg):
+ """Handle authentication."""
+ try:
+ msg = AUTH_MESSAGE_SCHEMA(msg)
+ except vol.Invalid as err:
+ error_msg = 'Auth message incorrectly formatted: {}'.format(
+ humanize_error(msg, err))
+ self._logger.warning(error_msg)
+ self._send_message(auth_invalid_message(error_msg))
+ raise Disconnect
+
+ if self._hass.auth.active and 'access_token' in msg:
+ self._logger.debug("Received access_token")
+ refresh_token = \
+ await self._hass.auth.async_validate_access_token(
+ msg['access_token'])
+ if refresh_token is not None:
+ return await self._async_finish_auth(
+ refresh_token.user, refresh_token)
+
+ elif ((not self._hass.auth.active or self._hass.auth.support_legacy)
+ and 'api_password' in msg):
+ self._logger.debug("Received api_password")
+ if validate_password(self._request, msg['api_password']):
+ return await self._async_finish_auth(None, None)
+
+ self._send_message(auth_invalid_message(
+ 'Invalid access token or password'))
+ await process_wrong_login(self._request)
+ raise Disconnect
+
+ async def _async_finish_auth(self, user, refresh_token) \
+ -> ActiveConnection:
+ """Create an active connection."""
+ self._logger.debug("Auth OK")
+ await process_success_login(self._request)
+ self._send_message(auth_ok_message())
+ return ActiveConnection(
+ self._logger, self._hass, self._send_message, user, refresh_token)
diff --git a/homeassistant/components/websocket_api/commands.py b/homeassistant/components/websocket_api/commands.py
new file mode 100644
index 00000000000..8e1dac4af8e
--- /dev/null
+++ b/homeassistant/components/websocket_api/commands.py
@@ -0,0 +1,183 @@
+"""Commands part of Websocket API."""
+import voluptuous as vol
+
+from homeassistant.const import MATCH_ALL, EVENT_TIME_CHANGED
+from homeassistant.core import callback
+from homeassistant.helpers import config_validation as cv
+from homeassistant.helpers.service import async_get_all_descriptions
+
+from . import const, decorators, messages
+
+
+TYPE_CALL_SERVICE = 'call_service'
+TYPE_EVENT = 'event'
+TYPE_GET_CONFIG = 'get_config'
+TYPE_GET_SERVICES = 'get_services'
+TYPE_GET_STATES = 'get_states'
+TYPE_PING = 'ping'
+TYPE_PONG = 'pong'
+TYPE_SUBSCRIBE_EVENTS = 'subscribe_events'
+TYPE_UNSUBSCRIBE_EVENTS = 'unsubscribe_events'
+
+
+@callback
+def async_register_commands(hass):
+ """Register commands."""
+ async_reg = hass.components.websocket_api.async_register_command
+ async_reg(TYPE_SUBSCRIBE_EVENTS, handle_subscribe_events,
+ SCHEMA_SUBSCRIBE_EVENTS)
+ async_reg(TYPE_UNSUBSCRIBE_EVENTS, handle_unsubscribe_events,
+ SCHEMA_UNSUBSCRIBE_EVENTS)
+ async_reg(TYPE_CALL_SERVICE, handle_call_service, SCHEMA_CALL_SERVICE)
+ async_reg(TYPE_GET_STATES, handle_get_states, SCHEMA_GET_STATES)
+ async_reg(TYPE_GET_SERVICES, handle_get_services, SCHEMA_GET_SERVICES)
+ async_reg(TYPE_GET_CONFIG, handle_get_config, SCHEMA_GET_CONFIG)
+ async_reg(TYPE_PING, handle_ping, SCHEMA_PING)
+
+
+SCHEMA_SUBSCRIBE_EVENTS = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_SUBSCRIBE_EVENTS,
+ vol.Optional('event_type', default=MATCH_ALL): str,
+})
+
+
+SCHEMA_UNSUBSCRIBE_EVENTS = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_UNSUBSCRIBE_EVENTS,
+ vol.Required('subscription'): cv.positive_int,
+})
+
+
+SCHEMA_CALL_SERVICE = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_CALL_SERVICE,
+ vol.Required('domain'): str,
+ vol.Required('service'): str,
+ vol.Optional('service_data'): dict
+})
+
+
+SCHEMA_GET_STATES = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_GET_STATES,
+})
+
+
+SCHEMA_GET_SERVICES = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_GET_SERVICES,
+})
+
+
+SCHEMA_GET_CONFIG = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_GET_CONFIG,
+})
+
+
+SCHEMA_PING = messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({
+ vol.Required('type'): TYPE_PING,
+})
+
+
+def event_message(iden, event):
+ """Return an event message."""
+ return {
+ 'id': iden,
+ 'type': TYPE_EVENT,
+ 'event': event.as_dict(),
+ }
+
+
+def pong_message(iden):
+ """Return a pong message."""
+ return {
+ 'id': iden,
+ 'type': TYPE_PONG,
+ }
+
+
+@callback
+def handle_subscribe_events(hass, connection, msg):
+ """Handle subscribe events command.
+
+ Async friendly.
+ """
+ async def forward_events(event):
+ """Forward events to websocket."""
+ if event.event_type == EVENT_TIME_CHANGED:
+ return
+
+ connection.send_message(event_message(msg['id'], event))
+
+ connection.event_listeners[msg['id']] = hass.bus.async_listen(
+ msg['event_type'], forward_events)
+
+ connection.send_message(messages.result_message(msg['id']))
+
+
+@callback
+def handle_unsubscribe_events(hass, connection, msg):
+ """Handle unsubscribe events command.
+
+ Async friendly.
+ """
+ subscription = msg['subscription']
+
+ if subscription in connection.event_listeners:
+ connection.event_listeners.pop(subscription)()
+ connection.send_message(messages.result_message(msg['id']))
+ else:
+ connection.send_message(messages.error_message(
+ msg['id'], const.ERR_NOT_FOUND, 'Subscription not found.'))
+
+
+@decorators.async_response
+async def handle_call_service(hass, connection, msg):
+ """Handle call service command.
+
+ Async friendly.
+ """
+ blocking = True
+ if (msg['domain'] == 'homeassistant' and
+ msg['service'] in ['restart', 'stop']):
+ blocking = False
+ await hass.services.async_call(
+ msg['domain'], msg['service'], msg.get('service_data'), blocking,
+ connection.context(msg))
+ connection.send_message(messages.result_message(msg['id']))
+
+
+@callback
+def handle_get_states(hass, connection, msg):
+ """Handle get states command.
+
+ Async friendly.
+ """
+ connection.send_message(messages.result_message(
+ msg['id'], hass.states.async_all()))
+
+
+@decorators.async_response
+async def handle_get_services(hass, connection, msg):
+ """Handle get services command.
+
+ Async friendly.
+ """
+ descriptions = await async_get_all_descriptions(hass)
+ connection.send_message(
+ messages.result_message(msg['id'], descriptions))
+
+
+@callback
+def handle_get_config(hass, connection, msg):
+ """Handle get config command.
+
+ Async friendly.
+ """
+ connection.send_message(messages.result_message(
+ msg['id'], hass.config.as_dict()))
+
+
+@callback
+def handle_ping(hass, connection, msg):
+ """Handle ping command.
+
+ Async friendly.
+ """
+ connection.send_message(pong_message(msg['id']))
diff --git a/homeassistant/components/websocket_api/connection.py b/homeassistant/components/websocket_api/connection.py
new file mode 100644
index 00000000000..1cb58591a0a
--- /dev/null
+++ b/homeassistant/components/websocket_api/connection.py
@@ -0,0 +1,78 @@
+"""Connection session."""
+import voluptuous as vol
+
+from homeassistant.core import callback, Context
+
+from . import const, messages
+
+
+class ActiveConnection:
+ """Handle an active websocket client connection."""
+
+ def __init__(self, logger, hass, send_message, user, refresh_token):
+ """Initialize an active connection."""
+ self.logger = logger
+ self.hass = hass
+ self.send_message = send_message
+ self.user = user
+ if refresh_token:
+ self.refresh_token_id = refresh_token.id
+ else:
+ self.refresh_token_id = None
+
+ self.event_listeners = {}
+ self.last_id = 0
+
+ def context(self, msg):
+ """Return a context."""
+ user = self.user
+ if user is None:
+ return Context()
+ return Context(user_id=user.id)
+
+ @callback
+ def async_handle(self, msg):
+ """Handle a single incoming message."""
+ handlers = self.hass.data[const.DOMAIN]
+
+ try:
+ msg = messages.MINIMAL_MESSAGE_SCHEMA(msg)
+ cur_id = msg['id']
+ except vol.Invalid:
+ self.logger.error('Received invalid command', msg)
+ self.send_message(messages.error_message(
+ msg.get('id'), const.ERR_INVALID_FORMAT,
+ 'Message incorrectly formatted.'))
+ return
+
+ if cur_id <= self.last_id:
+ self.send_message(messages.error_message(
+ cur_id, const.ERR_ID_REUSE,
+ 'Identifier values have to increase.'))
+ return
+
+ if msg['type'] not in handlers:
+ self.logger.error(
+ 'Received invalid command: {}'.format(msg['type']))
+ self.send_message(messages.error_message(
+ cur_id, const.ERR_UNKNOWN_COMMAND,
+ 'Unknown command.'))
+ return
+
+ handler, schema = handlers[msg['type']]
+
+ try:
+ handler(self.hass, self, schema(msg))
+ except Exception: # pylint: disable=broad-except
+ self.logger.exception('Error handling message: %s', msg)
+ self.send_message(messages.error_message(
+ cur_id, const.ERR_UNKNOWN_ERROR,
+ 'Unknown error.'))
+
+ self.last_id = cur_id
+
+ @callback
+ def async_close(self):
+ """Close down connection."""
+ for unsub in self.event_listeners.values():
+ unsub()
diff --git a/homeassistant/components/websocket_api/const.py b/homeassistant/components/websocket_api/const.py
new file mode 100644
index 00000000000..8d452959ca5
--- /dev/null
+++ b/homeassistant/components/websocket_api/const.py
@@ -0,0 +1,20 @@
+"""Websocket constants."""
+import asyncio
+from concurrent import futures
+
+DOMAIN = 'websocket_api'
+URL = '/api/websocket'
+MAX_PENDING_MSG = 512
+
+ERR_ID_REUSE = 1
+ERR_INVALID_FORMAT = 2
+ERR_NOT_FOUND = 3
+ERR_UNKNOWN_COMMAND = 4
+ERR_UNKNOWN_ERROR = 5
+
+TYPE_RESULT = 'result'
+
+# Define the possible errors that occur when connections are cancelled.
+# Originally, this was just asyncio.CancelledError, but issue #9546 showed
+# that futures.CancelledErrors can also occur in some situations.
+CANCELLATION_ERRORS = (asyncio.CancelledError, futures.CancelledError)
diff --git a/homeassistant/components/websocket_api/decorators.py b/homeassistant/components/websocket_api/decorators.py
new file mode 100644
index 00000000000..5f78790f5db
--- /dev/null
+++ b/homeassistant/components/websocket_api/decorators.py
@@ -0,0 +1,103 @@
+"""Decorators for the Websocket API."""
+from functools import wraps
+import logging
+
+from homeassistant.core import callback
+
+from . import messages
+
+
+_LOGGER = logging.getLogger(__name__)
+
+
+async def _handle_async_response(func, hass, connection, msg):
+ """Create a response and handle exception."""
+ try:
+ await func(hass, connection, msg)
+ except Exception: # pylint: disable=broad-except
+ _LOGGER.exception("Unexpected exception")
+ connection.send_message(messages.error_message(
+ msg['id'], 'unknown', 'Unexpected error occurred'))
+
+
+def async_response(func):
+ """Decorate an async function to handle WebSocket API messages."""
+ @callback
+ @wraps(func)
+ def schedule_handler(hass, connection, msg):
+ """Schedule the handler."""
+ hass.async_create_task(
+ _handle_async_response(func, hass, connection, msg))
+
+ return schedule_handler
+
+
+def require_owner(func):
+ """Websocket decorator to require user to be an owner."""
+ @wraps(func)
+ def with_owner(hass, connection, msg):
+ """Check owner and call function."""
+ user = connection.user
+
+ if user is None or not user.is_owner:
+ connection.send_message(messages.error_message(
+ msg['id'], 'unauthorized', 'This command is for owners only.'))
+ return
+
+ func(hass, connection, msg)
+
+ return with_owner
+
+
+def ws_require_user(
+ only_owner=False, only_system_user=False, allow_system_user=True,
+ only_active_user=True, only_inactive_user=False):
+ """Decorate function validating login user exist in current WS connection.
+
+ Will write out error message if not authenticated.
+ """
+ def validator(func):
+ """Decorate func."""
+ @wraps(func)
+ def check_current_user(hass, connection, msg):
+ """Check current user."""
+ def output_error(message_id, message):
+ """Output error message."""
+ connection.send_message(messages.error_message(
+ msg['id'], message_id, message))
+
+ if connection.user is None:
+ output_error('no_user', 'Not authenticated as a user')
+ return
+
+ if only_owner and not connection.user.is_owner:
+ output_error('only_owner', 'Only allowed as owner')
+ return
+
+ if (only_system_user and
+ not connection.user.system_generated):
+ output_error('only_system_user',
+ 'Only allowed as system user')
+ return
+
+ if (not allow_system_user
+ and connection.user.system_generated):
+ output_error('not_system_user', 'Not allowed as system user')
+ return
+
+ if (only_active_user and
+ not connection.user.is_active):
+ output_error('only_active_user',
+ 'Only allowed as active user')
+ return
+
+ if only_inactive_user and connection.user.is_active:
+ output_error('only_inactive_user',
+ 'Not allowed as active user')
+ return
+
+ return func(hass, connection, msg)
+
+ return check_current_user
+
+ return validator
diff --git a/homeassistant/components/websocket_api/error.py b/homeassistant/components/websocket_api/error.py
new file mode 100644
index 00000000000..c0b7ea04554
--- /dev/null
+++ b/homeassistant/components/websocket_api/error.py
@@ -0,0 +1,8 @@
+"""WebSocket API related errors."""
+from homeassistant.exceptions import HomeAssistantError
+
+
+class Disconnect(HomeAssistantError):
+ """Disconnect the current session."""
+
+ pass
diff --git a/homeassistant/components/websocket_api/http.py b/homeassistant/components/websocket_api/http.py
new file mode 100644
index 00000000000..87f25c9b3ef
--- /dev/null
+++ b/homeassistant/components/websocket_api/http.py
@@ -0,0 +1,189 @@
+"""View to accept incoming websocket connection."""
+import asyncio
+from contextlib import suppress
+from functools import partial
+import json
+import logging
+
+from aiohttp import web, WSMsgType
+import async_timeout
+
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
+from homeassistant.core import callback
+from homeassistant.components.http import HomeAssistantView
+from homeassistant.helpers.json import JSONEncoder
+
+from .const import MAX_PENDING_MSG, CANCELLATION_ERRORS, URL
+from .auth import AuthPhase, auth_required_message
+from .error import Disconnect
+
+JSON_DUMP = partial(json.dumps, cls=JSONEncoder)
+
+
+class WebsocketAPIView(HomeAssistantView):
+ """View to serve a websockets endpoint."""
+
+ name = "websocketapi"
+ url = URL
+ requires_auth = False
+
+ async def get(self, request):
+ """Handle an incoming websocket connection."""
+ return await WebSocketHandler(
+ request.app['hass'], request).async_handle()
+
+
+class WebSocketHandler:
+ """Handle an active websocket client connection."""
+
+ def __init__(self, hass, request):
+ """Initialize an active connection."""
+ self.hass = hass
+ self.request = request
+ self.wsock = None
+ self._to_write = asyncio.Queue(maxsize=MAX_PENDING_MSG, loop=hass.loop)
+ self._handle_task = None
+ self._writer_task = None
+ self._logger = logging.getLogger(
+ "{}.connection.{}".format(__name__, id(self)))
+
+ async def _writer(self):
+ """Write outgoing messages."""
+ # Exceptions if Socket disconnected or cancelled by connection handler
+ with suppress(RuntimeError, *CANCELLATION_ERRORS):
+ while not self.wsock.closed:
+ message = await self._to_write.get()
+ if message is None:
+ break
+ self._logger.debug("Sending %s", message)
+ try:
+ await self.wsock.send_json(message, dumps=JSON_DUMP)
+ except TypeError as err:
+ self._logger.error('Unable to serialize to JSON: %s\n%s',
+ err, message)
+
+ @callback
+ def _send_message(self, message):
+ """Send a message to the client.
+
+ Closes connection if the client is not reading the messages.
+
+ Async friendly.
+ """
+ try:
+ self._to_write.put_nowait(message)
+ except asyncio.QueueFull:
+ self._logger.error("Client exceeded max pending messages [2]: %s",
+ MAX_PENDING_MSG)
+ self._cancel()
+
+ @callback
+ def _cancel(self):
+ """Cancel the connection."""
+ self._handle_task.cancel()
+ self._writer_task.cancel()
+
+ async def async_handle(self):
+ """Handle a websocket response."""
+ request = self.request
+ wsock = self.wsock = web.WebSocketResponse(heartbeat=55)
+ await wsock.prepare(request)
+ self._logger.debug("Connected")
+
+ # Py3.7+
+ if hasattr(asyncio, 'current_task'):
+ # pylint: disable=no-member
+ self._handle_task = asyncio.current_task()
+ else:
+ self._handle_task = asyncio.Task.current_task(loop=self.hass.loop)
+
+ @callback
+ def handle_hass_stop(event):
+ """Cancel this connection."""
+ self._cancel()
+
+ unsub_stop = self.hass.bus.async_listen(
+ EVENT_HOMEASSISTANT_STOP, handle_hass_stop)
+
+ self._writer_task = self.hass.async_create_task(self._writer())
+
+ auth = AuthPhase(self._logger, self.hass, self._send_message, request)
+ connection = None
+ disconnect_warn = None
+
+ try:
+ self._send_message(auth_required_message())
+
+ # Auth Phase
+ try:
+ with async_timeout.timeout(10):
+ msg = await wsock.receive()
+ except asyncio.TimeoutError:
+ disconnect_warn = \
+ 'Did not receive auth message within 10 seconds'
+ raise Disconnect
+
+ if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING):
+ raise Disconnect
+
+ elif msg.type != WSMsgType.TEXT:
+ disconnect_warn = 'Received non-Text message.'
+ raise Disconnect
+
+ try:
+ msg = msg.json()
+ except ValueError:
+ disconnect_warn = 'Received invalid JSON.'
+ raise Disconnect
+
+ self._logger.debug("Received %s", msg)
+ connection = await auth.async_handle(msg)
+
+ # Command phase
+ while not wsock.closed:
+ msg = await wsock.receive()
+
+ if msg.type in (WSMsgType.CLOSE, WSMsgType.CLOSING):
+ break
+
+ elif msg.type != WSMsgType.TEXT:
+ disconnect_warn = 'Received non-Text message.'
+ break
+
+ try:
+ msg = msg.json()
+ except ValueError:
+ disconnect_warn = 'Received invalid JSON.'
+ break
+
+ self._logger.debug("Received %s", msg)
+ connection.async_handle(msg)
+
+ except asyncio.CancelledError:
+ self._logger.info("Connection closed by client")
+
+ except Disconnect:
+ pass
+
+ except Exception: # pylint: disable=broad-except
+ self._logger.exception("Unexpected error inside websocket API")
+
+ finally:
+ unsub_stop()
+
+ if connection is not None:
+ connection.async_close()
+
+ try:
+ self._to_write.put_nowait(None)
+ # Make sure all error messages are written before closing
+ await self._writer_task
+ except asyncio.QueueFull:
+ self._writer_task.cancel()
+
+ await wsock.close()
+
+ if disconnect_warn is None:
+ self._logger.debug("Disconnected")
+ else:
+ self._logger.warning("Disconnected: %s", disconnect_warn)
diff --git a/homeassistant/components/websocket_api/messages.py b/homeassistant/components/websocket_api/messages.py
new file mode 100644
index 00000000000..d616b6ad670
--- /dev/null
+++ b/homeassistant/components/websocket_api/messages.py
@@ -0,0 +1,42 @@
+"""Message templates for websocket commands."""
+
+import voluptuous as vol
+
+from homeassistant.helpers import config_validation as cv
+
+from . import const
+
+
+# Minimal requirements of a message
+MINIMAL_MESSAGE_SCHEMA = vol.Schema({
+ vol.Required('id'): cv.positive_int,
+ vol.Required('type'): cv.string,
+}, extra=vol.ALLOW_EXTRA)
+
+# Base schema to extend by message handlers
+BASE_COMMAND_MESSAGE_SCHEMA = vol.Schema({
+ vol.Required('id'): cv.positive_int,
+})
+
+
+def result_message(iden, result=None):
+ """Return a success result message."""
+ return {
+ 'id': iden,
+ 'type': const.TYPE_RESULT,
+ 'success': True,
+ 'result': result,
+ }
+
+
+def error_message(iden, code, message):
+ """Return an error result message."""
+ return {
+ 'id': iden,
+ 'type': const.TYPE_RESULT,
+ 'success': False,
+ 'error': {
+ 'code': code,
+ 'message': message,
+ },
+ }
diff --git a/homeassistant/components/wink/__init__.py b/homeassistant/components/wink/__init__.py
index 0399b25b278..d21ccc18c93 100644
--- a/homeassistant/components/wink/__init__.py
+++ b/homeassistant/components/wink/__init__.py
@@ -4,7 +4,6 @@ Support for Wink hubs.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/wink/
"""
-import asyncio
from datetime import timedelta
import json
import logging
@@ -763,8 +762,7 @@ class WinkDevice(Entity):
class WinkSirenDevice(WinkDevice):
"""Representation of a Wink siren device."""
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['switch'].append(self)
@@ -824,8 +822,7 @@ class WinkNimbusDialDevice(WinkDevice):
super().__init__(dial, hass)
self.parent = nimbus
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['sensor'].append(self)
diff --git a/homeassistant/components/xiaomi_aqara.py b/homeassistant/components/xiaomi_aqara.py
index f2d51d2fc2e..27414a64150 100644
--- a/homeassistant/components/xiaomi_aqara.py
+++ b/homeassistant/components/xiaomi_aqara.py
@@ -4,7 +4,6 @@ Support for Xiaomi Gateways.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/xiaomi_aqara/
"""
-import asyncio
import logging
from datetime import timedelta
@@ -23,7 +22,7 @@ from homeassistant.helpers.event import async_track_point_in_utc_time
from homeassistant.util.dt import utcnow
from homeassistant.util import slugify
-REQUIREMENTS = ['PyXiaomiGateway==0.10.0']
+REQUIREMENTS = ['PyXiaomiGateway==0.11.0']
_LOGGER = logging.getLogger(__name__)
@@ -113,8 +112,7 @@ def setup(hass, config):
interface = config[DOMAIN][CONF_INTERFACE]
discovery_retry = config[DOMAIN][CONF_DISCOVERY_RETRY]
- @asyncio.coroutine
- def xiaomi_gw_discovered(service, discovery_info):
+ async def xiaomi_gw_discovered(service, discovery_info):
"""Perform action when Xiaomi Gateway device(s) has been found."""
# We don't need to do anything here, the purpose of Home Assistant's
# discovery service is to just trigger loading of this
@@ -218,7 +216,7 @@ class XiaomiDevice(Entity):
self._get_from_hub = xiaomi_hub.get_from_hub
self._device_state_attributes = {}
self._remove_unavailability_tracker = None
- xiaomi_hub.callbacks[self._sid].append(self._add_push_data_job)
+ self._xiaomi_hub = xiaomi_hub
self.parse_data(device['data'], device['raw_data'])
self.parse_voltage(device['data'])
@@ -233,9 +231,9 @@ class XiaomiDevice(Entity):
def _add_push_data_job(self, *args):
self.hass.add_job(self.push_data, *args)
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Start unavailability tracking."""
+ self._xiaomi_hub.callbacks[self._sid].append(self._add_push_data_job)
self._async_track_unavailable()
@property
diff --git a/homeassistant/components/zha/__init__.py b/homeassistant/components/zha/__init__.py
index 7c48a577850..2e74e079d5f 100644
--- a/homeassistant/components/zha/__init__.py
+++ b/homeassistant/components/zha/__init__.py
@@ -109,7 +109,8 @@ async def async_setup(hass, config):
await APPLICATION_CONTROLLER.startup(auto_form=True)
for device in APPLICATION_CONTROLLER.devices.values():
- hass.async_add_job(listener.async_device_initialized(device, False))
+ hass.async_create_task(
+ listener.async_device_initialized(device, False))
async def permit(service):
"""Allow devices to join this network."""
@@ -161,7 +162,8 @@ class ApplicationListener:
def device_initialized(self, device):
"""Handle device joined and basic information discovered."""
- self._hass.async_add_job(self.async_device_initialized(device, True))
+ self._hass.async_create_task(
+ self.async_device_initialized(device, True))
def device_left(self, device):
"""Handle device leaving the network."""
@@ -170,7 +172,7 @@ class ApplicationListener:
def device_removed(self, device):
"""Handle device being removed from the network."""
for device_entity in self._device_registry[device.ieee]:
- self._hass.async_add_job(device_entity.async_remove())
+ self._hass.async_create_task(device_entity.async_remove())
async def async_device_initialized(self, device, join):
"""Handle device joined and basic information discovered (async)."""
diff --git a/homeassistant/components/zigbee.py b/homeassistant/components/zigbee.py
index 67bdf744251..4c294e51231 100644
--- a/homeassistant/components/zigbee.py
+++ b/homeassistant/components/zigbee.py
@@ -4,7 +4,6 @@ Support for ZigBee devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/zigbee/
"""
-import asyncio
import logging
from binascii import hexlify, unhexlify
@@ -277,8 +276,7 @@ class ZigBeeDigitalIn(Entity):
self._config = config
self._state = False
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
def handle_frame(frame):
"""Handle an incoming frame.
@@ -403,8 +401,7 @@ class ZigBeeAnalogIn(Entity):
self._config = config
self._value = None
- @asyncio.coroutine
- def async_added_to_hass(self):
+ async def async_added_to_hass(self):
"""Register callbacks."""
def handle_frame(frame):
"""Handle an incoming frame.
diff --git a/homeassistant/components/zone/__init__.py b/homeassistant/components/zone/__init__.py
index 3754bf5edbc..370b52d1360 100644
--- a/homeassistant/components/zone/__init__.py
+++ b/homeassistant/components/zone/__init__.py
@@ -55,7 +55,7 @@ async def async_setup(hass, config):
entry.get(CONF_ICON), entry.get(CONF_PASSIVE))
zone.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, entry[CONF_NAME], entities)
- hass.async_add_job(zone.async_update_ha_state())
+ hass.async_create_task(zone.async_update_ha_state())
entities.add(zone.entity_id)
if ENTITY_ID_HOME not in entities and HOME_ZONE not in zone_entries:
@@ -63,7 +63,7 @@ async def async_setup(hass, config):
hass.config.latitude, hass.config.longitude,
DEFAULT_RADIUS, ICON_HOME, False)
zone.entity_id = ENTITY_ID_HOME
- hass.async_add_job(zone.async_update_ha_state())
+ hass.async_create_task(zone.async_update_ha_state())
return True
@@ -77,7 +77,7 @@ async def async_setup_entry(hass, config_entry):
entry.get(CONF_PASSIVE, DEFAULT_PASSIVE))
zone.entity_id = async_generate_entity_id(
ENTITY_ID_FORMAT, name, None, hass)
- hass.async_add_job(zone.async_update_ha_state())
+ hass.async_create_task(zone.async_update_ha_state())
hass.data[DOMAIN][slugify(name)] = zone
return True
diff --git a/homeassistant/components/zoneminder.py b/homeassistant/components/zoneminder.py
index b7f43177200..3f6d8ba7fcf 100644
--- a/homeassistant/components/zoneminder.py
+++ b/homeassistant/components/zoneminder.py
@@ -15,7 +15,7 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
-REQUIREMENTS = ['zm-py==0.0.4']
+REQUIREMENTS = ['zm-py==0.0.5']
CONF_PATH_ZMS = 'path_zms'
diff --git a/homeassistant/components/zwave/.translations/ca.json b/homeassistant/components/zwave/.translations/ca.json
new file mode 100644
index 00000000000..b617a902374
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/ca.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave ja est\u00e0 configurat",
+ "one_instance_only": "El component nom\u00e9s admet una inst\u00e0ncia de Z-Wave"
+ },
+ "error": {
+ "option_error": "Ha fallat la validaci\u00f3 de Z-Wave. \u00c9s correcta la ruta al port on hi ha la mem\u00f2ria USB?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "Clau de xarxa (deixeu-ho en blanc per generar-la autom\u00e0ticament)",
+ "usb_path": "Ruta del port USB"
+ },
+ "description": "Consulteu https://www.home-assistant.io/docs/z-wave/installation/ per obtenir informaci\u00f3 sobre les variables de configuraci\u00f3",
+ "title": "Configureu Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/en.json b/homeassistant/components/zwave/.translations/en.json
new file mode 100644
index 00000000000..081d5c858cb
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/en.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave is already configured",
+ "one_instance_only": "Component only supports one Z-Wave instance"
+ },
+ "error": {
+ "option_error": "Z-Wave validation failed. Is the path to the USB stick correct?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "Network Key (leave blank to auto-generate)",
+ "usb_path": "USB Path"
+ },
+ "description": "See https://www.home-assistant.io/docs/z-wave/installation/ for information on the configuration variables",
+ "title": "Set up Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/ko.json b/homeassistant/components/zwave/.translations/ko.json
new file mode 100644
index 00000000000..43103de3d51
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/ko.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave \uac00 \uc774\ubbf8 \uad6c\uc131\ub418\uc5c8\uc2b5\ub2c8\ub2e4",
+ "one_instance_only": "\uad6c\uc131\uc694\uc18c\ub294 \ud558\ub098\uc758 Z-Wave \uc778\uc2a4\ud134\uc2a4\ub9cc \uc9c0\uc6d0\ud569\ub2c8\ub2e4"
+ },
+ "error": {
+ "option_error": "Z-Wave \uc720\ud6a8\uc131 \uac80\uc0ac\uc5d0 \uc2e4\ud328\ud588\uc2b5\ub2c8\ub2e4. USB \uc2a4\ud2f1\uc758 \uacbd\ub85c\uac00 \uc815\ud655\ud569\ub2c8\uae4c?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "\ub124\ud2b8\uc6cc\ud06c \ud0a4 (\uacf5\ub780\uc73c\ub85c \ube44\uc6cc\ub450\uba74 \uc790\ub3d9 \uc0dd\uc131\ud569\ub2c8\ub2e4)",
+ "usb_path": "USB \uacbd\ub85c"
+ },
+ "description": "\uad6c\uc131 \ubcc0\uc218\uc5d0 \ub300\ud55c \uc815\ubcf4\ub294 https://www.home-assistant.io/docs/z-wave/installation/ \uc744 \ucc38\uc870\ud574\uc8fc\uc138\uc694",
+ "title": "Z-Wave \uc124\uc815"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/lb.json b/homeassistant/components/zwave/.translations/lb.json
new file mode 100644
index 00000000000..84b6d8aa67d
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/lb.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave ass scho konfigur\u00e9iert",
+ "one_instance_only": "Komponent \u00ebnnerst\u00ebtzt n\u00ebmmen eng Z-Wave Instanz"
+ },
+ "error": {
+ "option_error": "Z-Wave Validatioun net g\u00eblteg. Ass de Pad zum USB Stick richteg?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "Netzwierk Schl\u00ebssel (eidel loossen fir een automatesch z'erstellen)",
+ "usb_path": "USB Pad"
+ },
+ "description": "Lies op https://www.home-assistant.io/docs/z-wave/installation/ fir weider Informatiounen iwwert d'Konfiguratioun vun den Variabelen",
+ "title": "Z-Wave konfigur\u00e9ieren"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/nl.json b/homeassistant/components/zwave/.translations/nl.json
new file mode 100644
index 00000000000..0b700b895fd
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/nl.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave is al geconfigureerd",
+ "one_instance_only": "Component ondersteunt slechts \u00e9\u00e9n Z-Wave-instantie"
+ },
+ "error": {
+ "option_error": "Z-Wave-validatie mislukt. Is het pad naar de USB-stick correct?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "Netwerksleutel (laat leeg om automatisch te genereren)",
+ "usb_path": "USB-pad"
+ },
+ "description": "Zie https://www.home-assistant.io/docs/z-wave/installation/ voor informatie over de configuratievariabelen",
+ "title": "Stel Z-Wave in"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/ru.json b/homeassistant/components/zwave/.translations/ru.json
new file mode 100644
index 00000000000..457bfd3baa8
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/ru.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "\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.",
+ "one_instance_only": "\u041a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442 \u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u0438\u043d \u044d\u043a\u0437\u0435\u043c\u043f\u043b\u044f\u0440 Z-Wave"
+ },
+ "error": {
+ "option_error": "\u041e\u0448\u0438\u0431\u043a\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 Z-Wave. \u041f\u0440\u043e\u0432\u0435\u0440\u044c\u0442\u0435 \u043f\u0443\u0442\u044c \u043a USB-\u043d\u0430\u043a\u043e\u043f\u0438\u0442\u0435\u043b\u044e."
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "\u041a\u043b\u044e\u0447 \u0441\u0435\u0442\u0438 (\u043e\u0441\u0442\u0430\u0432\u044c\u0442\u0435 \u043f\u043e\u043b\u0435 \u043f\u0443\u0441\u0442\u044b\u043c \u0434\u043b\u044f \u0430\u0432\u0442\u043e\u043c\u0430\u0442\u0438\u0447\u0435\u0441\u043a\u043e\u0439 \u0433\u0435\u043d\u0435\u0440\u0430\u0446\u0438\u0438)",
+ "usb_path": "\u041f\u0443\u0442\u044c \u043a USB"
+ },
+ "description": "\u041e\u0437\u043d\u0430\u043a\u043e\u043c\u044c\u0442\u0435\u0441\u044c \u0441 [\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430\u0446\u0438\u0435\u0439](https://www.home-assistant.io/docs/z-wave/installation/) \u0434\u043b\u044f \u043f\u043e\u043b\u0443\u0447\u0435\u043d\u0438\u044f \u0438\u043d\u0444\u043e\u0440\u043c\u0430\u0446\u0438\u0438 \u043e \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0445 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438",
+ "title": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/sl.json b/homeassistant/components/zwave/.translations/sl.json
new file mode 100644
index 00000000000..fa799d1ed36
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/sl.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave je \u017ee konfiguriran",
+ "one_instance_only": "Komponente podpirajo le eno Z-Wave instanco"
+ },
+ "error": {
+ "option_error": "Potrjevanje Z-Wave ni uspelo. Ali je pot do USB klju\u010da pravilna?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "Omre\u017eni klju\u010d (pustite prazno za samodejno generiranje)",
+ "usb_path": "USB Pot"
+ },
+ "description": "Za informacije o konfiguracijskih spremenljivka si oglejte https://www.home-assistant.io/docs/z-wave/installation/",
+ "title": "Nastavite Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/sv.json b/homeassistant/components/zwave/.translations/sv.json
new file mode 100644
index 00000000000..508652a1784
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/sv.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave \u00e4r redan konfigurerat",
+ "one_instance_only": "Komponenten st\u00f6der endast en Z-Wave-instans"
+ },
+ "error": {
+ "option_error": "Z-Wave-valideringen misslyckades. \u00c4r s\u00f6kv\u00e4gen till USB-minnet korrekt?"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "N\u00e4tverksnyckel (l\u00e4mna blank f\u00f6r automatisk generering)",
+ "usb_path": "USB-s\u00f6kv\u00e4g"
+ },
+ "description": "Se https://www.home-assistant.io/docs/z-wave/installation/ f\u00f6r information om konfigurationsvariabler",
+ "title": "St\u00e4lla in Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/zh-Hans.json b/homeassistant/components/zwave/.translations/zh-Hans.json
new file mode 100644
index 00000000000..2c72ce72c60
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/zh-Hans.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave \u5df2\u914d\u7f6e\u5b8c\u6210",
+ "one_instance_only": "\u7ec4\u4ef6\u53ea\u652f\u6301\u4e00\u4e2a Z-Wave \u5b9e\u4f8b"
+ },
+ "error": {
+ "option_error": "Z-Wave \u9a8c\u8bc1\u5931\u8d25\u3002 USB \u68d2\u7684\u8def\u5f84\u662f\u5426\u6b63\u786e\uff1f"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "\u7f51\u7edc\u5bc6\u94a5\uff08\u7559\u7a7a\u5c06\u81ea\u52a8\u751f\u6210\uff09",
+ "usb_path": "USB \u8def\u5f84"
+ },
+ "description": "\u6709\u5173\u914d\u7f6e\u7684\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 https://www.home-assistant.io/docs/z-wave/installation/",
+ "title": "\u8bbe\u7f6e Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/.translations/zh-Hant.json b/homeassistant/components/zwave/.translations/zh-Hant.json
new file mode 100644
index 00000000000..2a84e8b3fd6
--- /dev/null
+++ b/homeassistant/components/zwave/.translations/zh-Hant.json
@@ -0,0 +1,22 @@
+{
+ "config": {
+ "abort": {
+ "already_configured": "Z-Wave \u5df2\u7d93\u8a2d\u5b9a\u5b8c\u6210",
+ "one_instance_only": "\u7d44\u4ef6\u50c5\u652f\u63f4\u4e00\u7d44 Z-Wave \u7269\u4ef6"
+ },
+ "error": {
+ "option_error": "Z-Wave \u9a57\u8b49\u5931\u6557\uff0c\u8acb\u78ba\u5b9a USB \u96a8\u8eab\u789f\u8def\u5f91\u6b63\u78ba\uff1f"
+ },
+ "step": {
+ "user": {
+ "data": {
+ "network_key": "\u7db2\u8def\u5bc6\u9470\uff08\u4fdd\u7559\u7a7a\u767d\u5c07\u6703\u81ea\u52d5\u7522\u751f\uff09",
+ "usb_path": "USB \u8def\u5f91"
+ },
+ "description": "\u95dc\u65bc\u8a2d\u5b9a\u8b8a\u6578\u8cc7\u8a0a\uff0c\u8acb\u53c3\u95b1 https://www.home-assistant.io/docs/z-wave/installation/",
+ "title": "\u8a2d\u5b9a Z-Wave"
+ }
+ },
+ "title": "Z-Wave"
+ }
+}
\ No newline at end of file
diff --git a/homeassistant/components/zwave/__init__.py b/homeassistant/components/zwave/__init__.py
index 4cb2f6b0f7b..fa78f719557 100644
--- a/homeassistant/components/zwave/__init__.py
+++ b/homeassistant/components/zwave/__init__.py
@@ -84,6 +84,17 @@ SET_CONFIG_PARAMETER_SCHEMA = vol.Schema({
vol.Optional(const.ATTR_CONFIG_SIZE, default=2): vol.Coerce(int)
})
+SET_NODE_VALUE_SCHEMA = vol.Schema({
+ vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
+ vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int),
+ vol.Required(const.ATTR_CONFIG_VALUE): vol.Coerce(int)
+})
+
+REFRESH_NODE_VALUE_SCHEMA = vol.Schema({
+ vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
+ vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int)
+})
+
SET_POLL_INTENSITY_SCHEMA = vol.Schema({
vol.Required(const.ATTR_NODE_ID): vol.Coerce(int),
vol.Required(const.ATTR_VALUE_ID): vol.Coerce(int),
@@ -264,7 +275,9 @@ async def async_setup(hass, config):
ZWaveNetwork.SIGNAL_SCENE_EVENT,
ZWaveNetwork.SIGNAL_NODE_EVENT,
ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED,
- ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED):
+ ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED,
+ ZWaveNetwork
+ .SIGNAL_ALL_NODES_QUERIED_SOME_DEAD):
pprint(_obj_to_dict(value))
print("")
@@ -345,6 +358,12 @@ async def async_setup(hass, config):
"have been queried")
hass.bus.fire(const.EVENT_NETWORK_COMPLETE)
+ def network_complete_some_dead():
+ """Handle the querying of all nodes on network."""
+ _LOGGER.info("Z-Wave network is complete. All nodes on the network "
+ "have been queried, but some node are marked dead")
+ hass.bus.fire(const.EVENT_NETWORK_COMPLETE_SOME_DEAD)
+
dispatcher.connect(
value_added, ZWaveNetwork.SIGNAL_VALUE_ADDED, weak=False)
dispatcher.connect(
@@ -353,6 +372,9 @@ async def async_setup(hass, config):
network_ready, ZWaveNetwork.SIGNAL_AWAKE_NODES_QUERIED, weak=False)
dispatcher.connect(
network_complete, ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED, weak=False)
+ dispatcher.connect(
+ network_complete_some_dead,
+ ZWaveNetwork.SIGNAL_ALL_NODES_QUERIED_SOME_DEAD, weak=False)
def add_node(service):
"""Switch into inclusion mode."""
@@ -492,6 +514,23 @@ async def async_setup(hass, config):
"with selection %s", param, node_id,
selection)
+ def refresh_node_value(service):
+ """Refresh the specified value from a node."""
+ node_id = service.data.get(const.ATTR_NODE_ID)
+ value_id = service.data.get(const.ATTR_VALUE_ID)
+ node = network.nodes[node_id]
+ node.values[value_id].refresh()
+ _LOGGER.info("Node %s value %s refreshed", node_id, value_id)
+
+ def set_node_value(service):
+ """Set the specified value on a node."""
+ node_id = service.data.get(const.ATTR_NODE_ID)
+ value_id = service.data.get(const.ATTR_VALUE_ID)
+ value = service.data.get(const.ATTR_CONFIG_VALUE)
+ node = network.nodes[node_id]
+ node.values[value_id].data = value
+ _LOGGER.info("Node %s value %s set to %s", node_id, value_id, value)
+
def print_config_parameter(service):
"""Print a config parameter from a node."""
node_id = service.data.get(const.ATTR_NODE_ID)
@@ -662,6 +701,12 @@ async def async_setup(hass, config):
hass.services.register(DOMAIN, const.SERVICE_SET_CONFIG_PARAMETER,
set_config_parameter,
schema=SET_CONFIG_PARAMETER_SCHEMA)
+ hass.services.register(DOMAIN, const.SERVICE_SET_NODE_VALUE,
+ set_node_value,
+ schema=SET_NODE_VALUE_SCHEMA)
+ hass.services.register(DOMAIN, const.SERVICE_REFRESH_NODE_VALUE,
+ refresh_node_value,
+ schema=REFRESH_NODE_VALUE_SCHEMA)
hass.services.register(DOMAIN, const.SERVICE_PRINT_CONFIG_PARAMETER,
print_config_parameter,
schema=PRINT_CONFIG_PARAMETER_SCHEMA)
diff --git a/homeassistant/components/zwave/const.py b/homeassistant/components/zwave/const.py
index 0228e64cf6e..b84f0287349 100644
--- a/homeassistant/components/zwave/const.py
+++ b/homeassistant/components/zwave/const.py
@@ -39,6 +39,8 @@ SERVICE_SOFT_RESET = "soft_reset"
SERVICE_TEST_NODE = "test_node"
SERVICE_TEST_NETWORK = "test_network"
SERVICE_SET_CONFIG_PARAMETER = "set_config_parameter"
+SERVICE_SET_NODE_VALUE = "set_node_value"
+SERVICE_REFRESH_NODE_VALUE = "refresh_node_value"
SERVICE_PRINT_CONFIG_PARAMETER = "print_config_parameter"
SERVICE_PRINT_NODE = "print_node"
SERVICE_REMOVE_FAILED_NODE = "remove_failed_node"
@@ -58,6 +60,7 @@ EVENT_SCENE_ACTIVATED = "zwave.scene_activated"
EVENT_NODE_EVENT = "zwave.node_event"
EVENT_NETWORK_READY = "zwave.network_ready"
EVENT_NETWORK_COMPLETE = "zwave.network_complete"
+EVENT_NETWORK_COMPLETE_SOME_DEAD = "zwave.network_complete_some_dead"
EVENT_NETWORK_START = "zwave.network_start"
EVENT_NETWORK_STOP = "zwave.network_stop"
diff --git a/homeassistant/components/zwave/discovery_schemas.py b/homeassistant/components/zwave/discovery_schemas.py
index 2a4e42ab92c..56d63d658a9 100644
--- a/homeassistant/components/zwave/discovery_schemas.py
+++ b/homeassistant/components/zwave/discovery_schemas.py
@@ -211,7 +211,8 @@ DISCOVERY_SCHEMAS = [
const.COMMAND_CLASS_SENSOR_MULTILEVEL,
const.COMMAND_CLASS_METER,
const.COMMAND_CLASS_ALARM,
- const.COMMAND_CLASS_SENSOR_ALARM],
+ const.COMMAND_CLASS_SENSOR_ALARM,
+ const.COMMAND_CLASS_INDICATOR],
const.DISC_GENRE: const.GENRE_USER,
}})},
{const.DISC_COMPONENT: 'switch',
diff --git a/homeassistant/components/zwave/services.yaml b/homeassistant/components/zwave/services.yaml
index 1762c33237d..7c926a5a879 100644
--- a/homeassistant/components/zwave/services.yaml
+++ b/homeassistant/components/zwave/services.yaml
@@ -18,40 +18,40 @@ change_association:
description: (Optional) Instance of multichannel association. Defaults to 0.
add_node:
- description: Add a new (unsecure) node to the Z-Wave network. Refer to OZW.log for progress.
+ description: Add a new (unsecure) node to the Z-Wave network. Refer to OZW_Log.txt for progress.
add_node_secure:
- description: Add a new node to the Z-Wave network with secure communications. Secure network key must be set, this process will fallback to add_node (unsecure) for unsupported devices. Note that unsecure devices can't directly talk to secure devices. Refer to OZW.log for progress.
+ description: Add a new node to the Z-Wave network with secure communications. Secure network key must be set, this process will fallback to add_node (unsecure) for unsupported devices. Note that unsecure devices can't directly talk to secure devices. Refer to OZW_Log.txt for progress.
cancel_command:
description: Cancel a running Z-Wave controller command. Use this to exit add_node, if you weren't going to use it but activated it.
heal_network:
- description: Start a Z-Wave network heal. This might take a while and will slow down the Z-Wave network greatly while it is being processed. Refer to OZW.log for progress.
+ description: Start a Z-Wave network heal. This might take a while and will slow down the Z-Wave network greatly while it is being processed. Refer to OZW_Log.txt for progress.
fields:
return_routes:
description: Whether or not to update the return routes from the nodes to the controller. Defaults to False.
example: True
heal_node:
- description: Start a Z-Wave node heal. Refer to OZW.log for progress.
+ description: Start a Z-Wave node heal. Refer to OZW_Log.txt for progress.
fields:
return_routes:
description: Whether or not to update the return routes from the node to the controller. Defaults to False.
example: True
remove_node:
- description: Remove a node from the Z-Wave network. Refer to OZW.log for progress.
+ description: Remove a node from the Z-Wave network. Refer to OZW_Log.txt for progress.
remove_failed_node:
- description: This command will remove a failed node from the network. The node should be on the controllers failed nodes list, otherwise this command will fail. Refer to OZW.log for progress.
+ description: This command will remove a failed node from the network. The node should be on the controller's failed nodes list, otherwise this command will fail. Refer to OZW_Log.txt for progress.
fields:
node_id:
description: Node id of the device to remove (integer).
example: 10
replace_failed_node:
- description: Replace a failed node with another. If the node is not in the controller's failed nodes list, or the node responds, this command will fail. Refer to OZW.log for progress.
+ description: Replace a failed node with another. If the node is not in the controller's failed nodes list, or the node responds, this command will fail. Refer to OZW_Log.txt for progress.
fields:
node_id:
description: Node id of the device to replace (integer).
@@ -69,6 +69,24 @@ set_config_parameter:
size:
description: (Optional) Set the size of the parameter value. Only needed if no parameters are available.
+set_node_value:
+ description: Set the value for a given value_id on a Z-Wave device.
+ fields:
+ node_id:
+ description: Node id of the device to set the value on (integer).
+ value_id:
+ description: Value id of the value to set (integer).
+ value:
+ description: Value to set (integer).
+
+refresh_node_value:
+ description: Refresh the value for a given value_id on a Z-Wave device.
+ fields:
+ node_id:
+ description: Node id of the device to refresh value from (integer).
+ value_id:
+ description: Value id of the value to refresh.
+
set_poll_intensity:
description: Set the polling interval to a nodes value
fields:
@@ -129,10 +147,10 @@ stop_network:
description: Stop the Z-Wave network, all updates into HASS will stop.
soft_reset:
- description: This will reset the controller without removing its data. Use carefully because not all controllers support this. Refer to controllers manual.
+ description: This will reset the controller without removing its data. Use carefully because not all controllers support this. Refer to your controller's manual.
test_network:
- description: This will send test to nodes in the Z-Wave network. This will greatly slow down the Z-Wave network while it is being processed. Refer to OZW.log for progress.
+ description: This will send test to nodes in the Z-Wave network. This will greatly slow down the Z-Wave network while it is being processed. Refer to OZW_Log.txt for progress.
test_node:
description: This will send test messages to a node in the Z-Wave network. This could bring back dead nodes.
diff --git a/homeassistant/config_entries.py b/homeassistant/config_entries.py
index 7763594e0e1..1cc2e1362af 100644
--- a/homeassistant/config_entries.py
+++ b/homeassistant/config_entries.py
@@ -115,14 +115,13 @@ should follow the same return values as a normal step.
If the result of the step is to show a form, the user will be able to continue
the flow from the config panel.
"""
-
import logging
import uuid
from typing import Set, Optional, List, Dict # noqa pylint: disable=unused-import
from homeassistant import data_entry_flow
from homeassistant.core import callback, HomeAssistant
-from homeassistant.exceptions import HomeAssistantError
+from homeassistant.exceptions import HomeAssistantError, ConfigEntryNotReady
from homeassistant.setup import async_setup_component, async_process_deps_reqs
from homeassistant.util.decorator import Registry
@@ -141,6 +140,7 @@ FLOWS = [
'deconz',
'homematicip_cloud',
'hue',
+ 'ifttt',
'ios',
'mqtt',
'nest',
@@ -148,6 +148,7 @@ FLOWS = [
'sonos',
'tradfri',
'zone',
+ 'upnp',
]
@@ -159,9 +160,15 @@ PATH_CONFIG = '.config_entries.json'
SAVE_DELAY = 1
+# The config entry has been set up successfully
ENTRY_STATE_LOADED = 'loaded'
+# There was an error while trying to set up this config entry
ENTRY_STATE_SETUP_ERROR = 'setup_error'
+# The config entry was not ready to be set up yet, but might be later
+ENTRY_STATE_SETUP_RETRY = 'setup_retry'
+# The config entry has not been loaded
ENTRY_STATE_NOT_LOADED = 'not_loaded'
+# An error occurred when trying to unload the entry
ENTRY_STATE_FAILED_UNLOAD = 'failed_unload'
DISCOVERY_NOTIFICATION_ID = 'config_entry_discovery'
@@ -184,7 +191,8 @@ class ConfigEntry:
"""Hold a configuration entry."""
__slots__ = ('entry_id', 'version', 'domain', 'title', 'data', 'source',
- 'connection_class', 'state')
+ 'connection_class', 'state', '_setup_lock',
+ '_async_cancel_retry_setup')
def __init__(self, version: str, domain: str, title: str, data: dict,
source: str, connection_class: str,
@@ -215,8 +223,11 @@ class ConfigEntry:
# State of the entry (LOADED, NOT_LOADED)
self.state = state
+ # Function to cancel a scheduled retry
+ self._async_cancel_retry_setup = None
+
async def async_setup(
- self, hass: HomeAssistant, *, component=None) -> None:
+ self, hass: HomeAssistant, *, component=None, tries=0) -> None:
"""Set up an entry."""
if component is None:
component = getattr(hass.components, self.domain)
@@ -228,6 +239,22 @@ class ConfigEntry:
_LOGGER.error('%s.async_config_entry did not return boolean',
component.DOMAIN)
result = False
+ except ConfigEntryNotReady:
+ self.state = ENTRY_STATE_SETUP_RETRY
+ wait_time = 2**min(tries, 4) * 5
+ tries += 1
+ _LOGGER.warning(
+ 'Config entry for %s not ready yet. Retrying in %d seconds.',
+ self.domain, wait_time)
+
+ async def setup_again(now):
+ """Run setup again."""
+ self._async_cancel_retry_setup = None
+ await self.async_setup(hass, component=component, tries=tries)
+
+ self._async_cancel_retry_setup = \
+ hass.helpers.event.async_call_later(wait_time, setup_again)
+ return
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error setting up entry %s for %s',
self.title, component.DOMAIN)
@@ -250,6 +277,15 @@ class ConfigEntry:
if component is None:
component = getattr(hass.components, self.domain)
+ if component.DOMAIN == self.domain:
+ if self._async_cancel_retry_setup is not None:
+ self._async_cancel_retry_setup()
+ self.state = ENTRY_STATE_NOT_LOADED
+ return True
+
+ if self.state != ENTRY_STATE_LOADED:
+ return True
+
supports_unload = hasattr(component, 'async_unload_entry')
if not supports_unload:
@@ -258,16 +294,18 @@ class ConfigEntry:
try:
result = await component.async_unload_entry(hass, self)
- if not isinstance(result, bool):
- _LOGGER.error('%s.async_unload_entry did not return boolean',
- component.DOMAIN)
- result = False
+ assert isinstance(result, bool)
+
+ # Only adjust state if we unloaded the component
+ if result and component.DOMAIN == self.domain:
+ self.state = ENTRY_STATE_NOT_LOADED
return result
except Exception: # pylint: disable=broad-except
_LOGGER.exception('Error unloading entry %s for %s',
self.title, component.DOMAIN)
- self.state = ENTRY_STATE_FAILED_UNLOAD
+ if component.DOMAIN == self.domain:
+ self.state = ENTRY_STATE_FAILED_UNLOAD
return False
def as_dict(self):
@@ -379,6 +417,12 @@ class ConfigEntries:
CONN_CLASS_UNKNOWN))
for entry in config['entries']]
+ @callback
+ def async_update_entry(self, entry, *, data):
+ """Update a config entry."""
+ entry.data = data
+ self._async_schedule_save()
+
async def async_forward_entry_setup(self, entry, component):
"""Forward the setup of an entry to a different component.
diff --git a/homeassistant/const.py b/homeassistant/const.py
index 59d19d2b29a..d023591c828 100644
--- a/homeassistant/const.py
+++ b/homeassistant/const.py
@@ -1,8 +1,8 @@
# coding: utf-8
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
-MINOR_VERSION = 79
-PATCH_VERSION = '3'
+MINOR_VERSION = 80
+PATCH_VERSION = '0'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 5, 3)
@@ -172,6 +172,7 @@ DEVICE_CLASS_BATTERY = 'battery'
DEVICE_CLASS_HUMIDITY = 'humidity'
DEVICE_CLASS_ILLUMINANCE = 'illuminance'
DEVICE_CLASS_TEMPERATURE = 'temperature'
+DEVICE_CLASS_PRESSURE = 'pressure'
# #### STATES ####
STATE_ON = 'on'
@@ -410,7 +411,9 @@ HTTP_UNAUTHORIZED = 401
HTTP_NOT_FOUND = 404
HTTP_METHOD_NOT_ALLOWED = 405
HTTP_UNPROCESSABLE_ENTITY = 422
+HTTP_TOO_MANY_REQUESTS = 429
HTTP_INTERNAL_SERVER_ERROR = 500
+HTTP_SERVICE_UNAVAILABLE = 503
HTTP_BASIC_AUTHENTICATION = 'basic'
HTTP_DIGEST_AUTHENTICATION = 'digest'
diff --git a/homeassistant/data_entry_flow.py b/homeassistant/data_entry_flow.py
index ecf9850a67c..57265cf696d 100644
--- a/homeassistant/data_entry_flow.py
+++ b/homeassistant/data_entry_flow.py
@@ -153,7 +153,10 @@ class FlowHandler:
}
@callback
- def async_create_entry(self, *, title: str, data: Dict) -> Dict:
+ def async_create_entry(self, *, title: str, data: Dict,
+ description: Optional[str] = None,
+ description_placeholders: Optional[Dict] = None) \
+ -> Dict:
"""Finish config flow and create a config entry."""
return {
'version': self.VERSION,
@@ -162,6 +165,8 @@ class FlowHandler:
'handler': self.handler,
'title': title,
'data': data,
+ 'description': description,
+ 'description_placeholders': description_placeholders,
}
@callback
diff --git a/homeassistant/exceptions.py b/homeassistant/exceptions.py
index 73bd2377950..11aa1848529 100644
--- a/homeassistant/exceptions.py
+++ b/homeassistant/exceptions.py
@@ -35,6 +35,12 @@ class PlatformNotReady(HomeAssistantError):
pass
+class ConfigEntryNotReady(HomeAssistantError):
+ """Error to indicate that config entry is not ready."""
+
+ pass
+
+
class InvalidStateError(HomeAssistantError):
"""When an invalid state is encountered."""
diff --git a/homeassistant/helpers/device_registry.py b/homeassistant/helpers/device_registry.py
index 8d4cd0a5bbf..478b29c75b2 100644
--- a/homeassistant/helpers/device_registry.py
+++ b/homeassistant/helpers/device_registry.py
@@ -26,11 +26,12 @@ CONNECTION_ZIGBEE = 'zigbee'
class DeviceEntry:
"""Device Registry Entry."""
- config_entries = attr.ib(type=set, converter=set)
- connections = attr.ib(type=set, converter=set)
- identifiers = attr.ib(type=set, converter=set)
- manufacturer = attr.ib(type=str)
- model = attr.ib(type=str)
+ config_entries = attr.ib(type=set, converter=set,
+ default=attr.Factory(set))
+ connections = attr.ib(type=set, converter=set, default=attr.Factory(set))
+ identifiers = attr.ib(type=set, converter=set, default=attr.Factory(set))
+ manufacturer = attr.ib(type=str, default=None)
+ model = attr.ib(type=str, default=None)
name = attr.ib(type=str, default=None)
sw_version = attr.ib(type=str, default=None)
hub_device_id = attr.ib(type=str, default=None)
@@ -56,46 +57,53 @@ class DeviceRegistry:
return None
@callback
- def async_get_or_create(self, *, config_entry_id, connections, identifiers,
- manufacturer, model, name=None, sw_version=None,
+ def async_get_or_create(self, *, config_entry_id, connections=None,
+ identifiers=None, manufacturer=_UNDEF,
+ model=_UNDEF, name=_UNDEF, sw_version=_UNDEF,
via_hub=None):
"""Get device. Create if it doesn't exist."""
if not identifiers and not connections:
return None
+ if identifiers is None:
+ identifiers = set()
+
+ if connections is None:
+ connections = set()
+
device = self.async_get_device(identifiers, connections)
+ if device is None:
+ device = DeviceEntry()
+ self.devices[device.id] = device
+
if via_hub is not None:
hub_device = self.async_get_device({via_hub}, set())
- hub_device_id = hub_device.id if hub_device else None
+ hub_device_id = hub_device.id if hub_device else _UNDEF
else:
- hub_device_id = None
+ hub_device_id = _UNDEF
- if device is not None:
- return self._async_update_device(
- device.id, config_entry_id=config_entry_id,
- hub_device_id=hub_device_id
- )
-
- device = DeviceEntry(
- config_entries={config_entry_id},
- connections=connections,
- identifiers=identifiers,
+ return self._async_update_device(
+ device.id,
+ add_config_entry_id=config_entry_id,
+ hub_device_id=hub_device_id,
+ merge_connections=connections,
+ merge_identifiers=identifiers,
manufacturer=manufacturer,
model=model,
name=name,
sw_version=sw_version,
- hub_device_id=hub_device_id
)
- self.devices[device.id] = device
-
- self.async_schedule_save()
-
- return device
@callback
- def _async_update_device(self, device_id, *, config_entry_id=_UNDEF,
+ def _async_update_device(self, device_id, *, add_config_entry_id=_UNDEF,
remove_config_entry_id=_UNDEF,
+ merge_connections=_UNDEF,
+ merge_identifiers=_UNDEF,
+ manufacturer=_UNDEF,
+ model=_UNDEF,
+ name=_UNDEF,
+ sw_version=_UNDEF,
hub_device_id=_UNDEF):
"""Update device attributes."""
old = self.devices[device_id]
@@ -104,21 +112,34 @@ class DeviceRegistry:
config_entries = old.config_entries
- if (config_entry_id is not _UNDEF and
- config_entry_id not in old.config_entries):
- config_entries = old.config_entries | {config_entry_id}
+ if (add_config_entry_id is not _UNDEF and
+ add_config_entry_id not in old.config_entries):
+ config_entries = old.config_entries | {add_config_entry_id}
if (remove_config_entry_id is not _UNDEF and
remove_config_entry_id in config_entries):
- config_entries = set(config_entries)
- config_entries.remove(remove_config_entry_id)
+ config_entries = config_entries - {remove_config_entry_id}
if config_entries is not old.config_entries:
changes['config_entries'] = config_entries
- if (hub_device_id is not _UNDEF and
- hub_device_id != old.hub_device_id):
- changes['hub_device_id'] = hub_device_id
+ for attr_name, value in (
+ ('connections', merge_connections),
+ ('identifiers', merge_identifiers),
+ ):
+ old_value = getattr(old, attr_name)
+ if value is not _UNDEF and value != old_value:
+ changes[attr_name] = old_value | value
+
+ for attr_name, value in (
+ ('manufacturer', manufacturer),
+ ('model', model),
+ ('name', name),
+ ('sw_version', sw_version),
+ ('hub_device_id', hub_device_id),
+ ):
+ if value is not _UNDEF and value != getattr(old, attr_name):
+ changes[attr_name] = value
if not changes:
return old
diff --git a/homeassistant/helpers/entity.py b/homeassistant/helpers/entity.py
index e48af6a3365..60fd661a765 100644
--- a/homeassistant/helpers/entity.py
+++ b/homeassistant/helpers/entity.py
@@ -1,5 +1,4 @@
"""An abstract class for entities."""
-import asyncio
from datetime import timedelta
import logging
import functools as ft
@@ -202,8 +201,7 @@ class Entity:
self._context = context
self._context_set = dt_util.utcnow()
- @asyncio.coroutine
- def async_update_ha_state(self, force_refresh=False):
+ async def async_update_ha_state(self, force_refresh=False):
"""Update Home Assistant with current state of entity.
If force_refresh == True will update entity before setting state.
@@ -220,7 +218,7 @@ class Entity:
# update entity data
if force_refresh:
try:
- yield from self.async_device_update()
+ await self.async_device_update()
except Exception: # pylint: disable=broad-except
_LOGGER.exception("Update for %s fails", self.entity_id)
return
@@ -323,8 +321,7 @@ class Entity:
"""Schedule an update ha state change task."""
self.hass.async_add_job(self.async_update_ha_state(force_refresh))
- @asyncio.coroutine
- def async_device_update(self, warning=True):
+ async def async_device_update(self, warning=True):
"""Process 'update' or 'async_update' from entity.
This method is a coroutine.
@@ -335,7 +332,7 @@ class Entity:
# Process update sequential
if self.parallel_updates:
- yield from self.parallel_updates.acquire()
+ await self.parallel_updates.acquire()
if warning:
update_warn = self.hass.loop.call_later(
@@ -347,9 +344,9 @@ class Entity:
try:
# pylint: disable=no-member
if hasattr(self, 'async_update'):
- yield from self.async_update()
+ await self.async_update()
elif hasattr(self, 'update'):
- yield from self.hass.async_add_job(self.update)
+ await self.hass.async_add_job(self.update)
finally:
self._update_staged = False
if warning:
diff --git a/homeassistant/helpers/entity_component.py b/homeassistant/helpers/entity_component.py
index 09f8838b160..c2ab8722c97 100644
--- a/homeassistant/helpers/entity_component.py
+++ b/homeassistant/helpers/entity_component.py
@@ -190,10 +190,13 @@ class EntityComponent:
sorted(self.entities,
key=lambda entity: entity.name or entity.entity_id)]
- self.hass.components.group.async_set_group(
- slugify(self.group_name), name=self.group_name,
- visible=False, entity_ids=ids
- )
+ self.hass.async_create_task(
+ self.hass.services.async_call(
+ 'group', 'set', dict(
+ object_id=slugify(self.group_name),
+ name=self.group_name,
+ visible=False,
+ entities=ids)))
async def _async_reset(self):
"""Remove entities and reset the entity component to initial values.
@@ -212,7 +215,9 @@ class EntityComponent:
self.config = None
if self.group_name is not None:
- self.hass.components.group.async_remove(slugify(self.group_name))
+ await self.hass.services.async_call(
+ 'group', 'remove', dict(
+ object_id=slugify(self.group_name)))
async def async_remove_entity(self, entity_id):
"""Remove an entity managed by one of the platforms."""
diff --git a/homeassistant/helpers/entity_platform.py b/homeassistant/helpers/entity_platform.py
index f2913e37339..3ab45577236 100644
--- a/homeassistant/helpers/entity_platform.py
+++ b/homeassistant/helpers/entity_platform.py
@@ -273,21 +273,28 @@ class EntityPlatform:
config_entry_id = None
device_info = entity.device_info
+ device_id = None
if config_entry_id is not None and device_info is not None:
+ processed_dev_info = {
+ 'config_entry_id': config_entry_id
+ }
+ for key in (
+ 'connections',
+ 'identifiers',
+ 'manufacturer',
+ 'model',
+ 'name',
+ 'sw_version',
+ 'via_hub',
+ ):
+ if key in device_info:
+ processed_dev_info[key] = device_info[key]
+
device = device_registry.async_get_or_create(
- config_entry_id=config_entry_id,
- connections=device_info.get('connections') or set(),
- identifiers=device_info.get('identifiers') or set(),
- manufacturer=device_info.get('manufacturer'),
- model=device_info.get('model'),
- name=device_info.get('name'),
- sw_version=device_info.get('sw_version'),
- via_hub=device_info.get('via_hub'))
+ **processed_dev_info)
if device:
device_id = device.id
- else:
- device_id = None
entry = entity_registry.async_get_or_create(
self.domain, self.platform_name, entity.unique_id,
diff --git a/homeassistant/helpers/event.py b/homeassistant/helpers/event.py
index c8488fa3334..05555e8b5c6 100644
--- a/homeassistant/helpers/event.py
+++ b/homeassistant/helpers/event.py
@@ -227,6 +227,10 @@ def async_call_later(hass, delay, action):
hass, action, dt_util.utcnow() + timedelta(seconds=delay))
+call_later = threaded_listener_factory(
+ async_call_later)
+
+
@callback
@bind_hass
def async_track_time_interval(hass, action, interval):
diff --git a/homeassistant/helpers/script.py b/homeassistant/helpers/script.py
index 96f9b2d5069..5e660ba7b7f 100644
--- a/homeassistant/helpers/script.py
+++ b/homeassistant/helpers/script.py
@@ -1,6 +1,7 @@
"""Helpers to execute scripts."""
import logging
+from contextlib import suppress
from itertools import islice
from typing import Optional, Sequence
@@ -95,7 +96,9 @@ class Script():
def async_script_delay(now):
"""Handle delay."""
# pylint: disable=cell-var-from-loop
- self._async_listener.remove(unsub)
+ with suppress(ValueError):
+ self._async_listener.remove(unsub)
+
self.hass.async_create_task(
self.async_run(variables, context))
@@ -240,7 +243,8 @@ class Script():
@callback
def async_script_timeout(now):
"""Call after timeout is retrieve."""
- self._async_listener.remove(unsub)
+ with suppress(ValueError):
+ self._async_listener.remove(unsub)
# Check if we want to continue to execute
# the script after the timeout
diff --git a/homeassistant/helpers/storage.py b/homeassistant/helpers/storage.py
index 95e6925b2a4..cfe73d6d147 100644
--- a/homeassistant/helpers/storage.py
+++ b/homeassistant/helpers/storage.py
@@ -2,7 +2,7 @@
import asyncio
import logging
import os
-from typing import Dict, Optional, Callable
+from typing import Dict, Optional, Callable, Any
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import callback
@@ -46,11 +46,12 @@ async def async_migrator(hass, old_path, store, *,
class Store:
"""Class to help storing data."""
- def __init__(self, hass, version: int, key: str):
+ def __init__(self, hass, version: int, key: str, private: bool = False):
"""Initialize storage class."""
self.version = version
self.key = key
self.hass = hass
+ self._private = private
self._data = None
self._unsub_delay_listener = None
self._unsub_stop_listener = None
@@ -62,7 +63,7 @@ class Store:
"""Return the config path."""
return self.hass.config.path(STORAGE_DIR, self.key)
- async def async_load(self):
+ async def async_load(self) -> Optional[Dict[str, Any]]:
"""Load data.
If the expected version does not match the given version, the migrate
@@ -186,7 +187,7 @@ class Store:
os.makedirs(os.path.dirname(path))
_LOGGER.debug('Writing data for %s', self.key)
- json.save_json(path, data)
+ json.save_json(path, data, self._private)
async def _async_migrate_func(self, old_version, old_data):
"""Migrate to the new version."""
diff --git a/homeassistant/helpers/template.py b/homeassistant/helpers/template.py
index d0d3fb457b1..c68aa311998 100644
--- a/homeassistant/helpers/template.py
+++ b/homeassistant/helpers/template.py
@@ -580,6 +580,16 @@ def regex_findall_index(value, find='', index=0, ignorecase=False):
return re.findall(find, value, flags)[index]
+def bitwise_and(first_value, second_value):
+ """Perform a bitwise and operation."""
+ return first_value & second_value
+
+
+def bitwise_or(first_value, second_value):
+ """Perform a bitwise or operation."""
+ return first_value | second_value
+
+
@contextfilter
def random_every_time(context, values):
"""Choose a random value.
@@ -617,6 +627,8 @@ ENV.filters['regex_match'] = regex_match
ENV.filters['regex_replace'] = regex_replace
ENV.filters['regex_search'] = regex_search
ENV.filters['regex_findall_index'] = regex_findall_index
+ENV.filters['bitwise_and'] = bitwise_and
+ENV.filters['bitwise_or'] = bitwise_or
ENV.globals['log'] = logarithm
ENV.globals['sin'] = sine
ENV.globals['cos'] = cosine
diff --git a/homeassistant/scripts/benchmark/__init__.py b/homeassistant/scripts/benchmark/__init__.py
index 98de59f2da1..f0df58a51f4 100644
--- a/homeassistant/scripts/benchmark/__init__.py
+++ b/homeassistant/scripts/benchmark/__init__.py
@@ -186,6 +186,6 @@ def _logbook_filtering(hass, last_changed, last_updated):
# pylint: disable=protected-access
events = logbook._exclude_events(events, {})
- list(logbook.humanify(events))
+ list(logbook.humanify(None, events))
return timer() - start
diff --git a/homeassistant/util/json.py b/homeassistant/util/json.py
index 8ecfebd5b33..0a2a2a1edf3 100644
--- a/homeassistant/util/json.py
+++ b/homeassistant/util/json.py
@@ -3,6 +3,8 @@ import logging
from typing import Union, List, Dict
import json
+import os
+from os import O_WRONLY, O_CREAT, O_TRUNC
from homeassistant.exceptions import HomeAssistantError
@@ -38,15 +40,20 @@ def load_json(filename: str, default: Union[List, Dict, None] = None) \
return {} if default is None else default
-def save_json(filename: str, data: Union[List, Dict]) -> None:
+def save_json(filename: str, data: Union[List, Dict],
+ private: bool = False) -> None:
"""Save JSON data to a file.
Returns True on success.
"""
+ tmp_filename = filename + "__TEMP__"
try:
json_data = json.dumps(data, sort_keys=True, indent=4)
- with open(filename, 'w', encoding='utf-8') as fdesc:
+ mode = 0o600 if private else 0o644
+ with open(os.open(tmp_filename, O_WRONLY | O_CREAT | O_TRUNC, mode),
+ 'w', encoding='utf-8') as fdesc:
fdesc.write(json_data)
+ os.replace(tmp_filename, filename)
except TypeError as error:
_LOGGER.exception('Failed to serialize to JSON: %s',
filename)
@@ -55,3 +62,11 @@ def save_json(filename: str, data: Union[List, Dict]) -> None:
_LOGGER.exception('Saving JSON file failed: %s',
filename)
raise WriteError(error)
+ finally:
+ if os.path.exists(tmp_filename):
+ try:
+ os.remove(tmp_filename)
+ except OSError as err:
+ # If we are cleaning up then something else went wrong, so
+ # we should suppress likely follow-on errors in the cleanup
+ _LOGGER.error("JSON replacement cleanup failed: %s", err)
diff --git a/homeassistant/util/network.py b/homeassistant/util/network.py
new file mode 100644
index 00000000000..48840f339c1
--- /dev/null
+++ b/homeassistant/util/network.py
@@ -0,0 +1,22 @@
+"""Network utilities."""
+from ipaddress import IPv4Address, IPv6Address, ip_address, ip_network
+from typing import Union
+
+# IP addresses of loopback interfaces
+LOCAL_IPS = (
+ ip_address('127.0.0.1'),
+ ip_address('::1'),
+)
+
+# RFC1918 - Address allocation for Private Internets
+LOCAL_NETWORKS = (
+ ip_network('10.0.0.0/8'),
+ ip_network('172.16.0.0/12'),
+ ip_network('192.168.0.0/16'),
+)
+
+
+def is_local(address: Union[IPv4Address, IPv6Address]) -> bool:
+ """Check if an address is local."""
+ return address in LOCAL_IPS or \
+ any(address in network for network in LOCAL_NETWORKS)
diff --git a/requirements_all.txt b/requirements_all.txt
index cdcab14d81c..c91b4127880 100644
--- a/requirements_all.txt
+++ b/requirements_all.txt
@@ -58,7 +58,7 @@ PyRMVtransport==0.1
PySwitchbot==0.3
# homeassistant.components.xiaomi_aqara
-PyXiaomiGateway==0.10.0
+PyXiaomiGateway==0.11.0
# homeassistant.components.rpi_gpio
# RPi.GPIO==0.6.1
@@ -76,7 +76,7 @@ TwitterAPI==2.5.4
WazeRouteCalculator==0.6
# homeassistant.components.notify.yessssms
-YesssSMS==0.1.1b3
+YesssSMS==0.2.3
# homeassistant.components.abode
abodepy==0.13.1
@@ -110,7 +110,7 @@ aioimaplib==0.7.13
aiolifx==0.6.3
# homeassistant.components.light.lifx
-aiolifx_effects==0.1.2
+aiolifx_effects==0.2.1
# homeassistant.components.scene.hunterdouglas_powerview
aiopvapi==1.5.4
@@ -139,9 +139,13 @@ apcaccess==0.0.13
# homeassistant.components.notify.apns
apns2==0.3.0
+# homeassistant.components.aqualogic
+aqualogic==1.0
+
# homeassistant.components.asterisk_mbox
asterisk_mbox==0.5.0
+# homeassistant.components.upnp
# homeassistant.components.media_player.dlna_dmr
async-upnp-client==0.12.4
@@ -172,10 +176,10 @@ beautifulsoup4==4.6.3
bellows==0.7.0
# homeassistant.components.bmw_connected_drive
-bimmer_connected==0.5.2
+bimmer_connected==0.5.3
# homeassistant.components.blink
-blinkpy==0.6.0
+blinkpy==0.9.0
# homeassistant.components.light.blinksticklight
blinkstick==1.1.8
@@ -324,7 +328,7 @@ enocean==0.40
# envirophat==0.0.6
# homeassistant.components.sensor.enphase_envoy
-envoy_reader==0.2
+envoy_reader==0.3
# homeassistant.components.sensor.season
ephem==3.7.6.0
@@ -338,8 +342,9 @@ eternalegypt==0.0.5
# homeassistant.components.keyboard_remote
# evdev==0.6.1
+# homeassistant.components.evohome
# homeassistant.components.climate.honeywell
-evohomeclient==0.2.5
+evohomeclient==0.2.7
# homeassistant.components.image_processing.dlib_face_detect
# homeassistant.components.image_processing.dlib_face_identify
@@ -352,7 +357,6 @@ fastdotcom==0.0.3
fedexdeliverymanager==1.0.6
# homeassistant.components.feedreader
-# homeassistant.components.sensor.geo_rss_events
feedparser==5.2.1
# homeassistant.components.sensor.fints
@@ -393,6 +397,9 @@ geizhals==0.0.7
# homeassistant.components.geo_location.geo_json_events
geojson_client==0.1
+# homeassistant.components.sensor.geo_rss_events
+georss_client==0.3
+
# homeassistant.components.sensor.gitter
gitterpy==0.1.7
@@ -429,9 +436,6 @@ habitipy==0.2.0
# homeassistant.components.hangouts
hangups==0.4.5
-# homeassistant.components.sensor.geo_rss_events
-haversine==0.4.5
-
# homeassistant.components.mqtt.server
hbmqtt==0.9.4
@@ -454,7 +458,7 @@ hole==0.3.0
holidays==0.9.7
# homeassistant.components.frontend
-home-assistant-frontend==20180927.0
+home-assistant-frontend==20181012.0
# homeassistant.components.homekit_controller
# homekit==0.10
@@ -467,7 +471,7 @@ homematicip==0.9.8
httplib2==0.10.3
# homeassistant.components.huawei_lte
-huawei-lte-api==1.0.12
+huawei-lte-api==1.0.16
# homeassistant.components.hydrawise
hydrawiser==0.1.1
@@ -557,7 +561,7 @@ liveboxplaytv==2.0.2
lmnotify==0.0.4
# homeassistant.components.device_tracker.google_maps
-locationsharinglib==2.0.11
+locationsharinglib==3.0.3
# homeassistant.components.logi_circle
logi_circle==0.1.7
@@ -763,8 +767,8 @@ pyRFXtrx==0.23
# homeassistant.components.switch.switchmate
pySwitchmate==0.4.1
-# homeassistant.components.sensor.tibber
-pyTibber==0.5.1
+# homeassistant.components.tibber
+pyTibber==0.7.2
# homeassistant.components.switch.dlink
pyW215==0.6.0
@@ -794,7 +798,7 @@ pyasn1-modules==0.1.5
pyasn1==0.3.7
# homeassistant.components.netatmo
-pyatmo==1.1.1
+pyatmo==1.2
# homeassistant.components.apple_tv
pyatv==0.3.10
@@ -865,7 +869,7 @@ pyeight==0.0.9
pyemby==1.5
# homeassistant.components.envisalink
-pyenvisalink==2.3
+pyenvisalink==3.7
# homeassistant.components.climate.ephember
pyephember==0.2.0
@@ -883,7 +887,7 @@ pyflic-homeassistant==0.4.dev0
pyfnip==0.2
# homeassistant.components.fritzbox
-pyfritzhome==0.3.7
+pyfritzhome==0.4.0
# homeassistant.components.ifttt
pyfttt==0.3
@@ -908,7 +912,7 @@ pyhik==0.1.8
pyhiveapi==0.2.14
# homeassistant.components.homematic
-pyhomematic==0.1.49
+pyhomematic==0.1.50
# homeassistant.components.sensor.hydroquebec
pyhydroquebec==2.2.2
@@ -996,7 +1000,7 @@ pymysensors==0.17.0
pynello==1.5.1
# homeassistant.components.device_tracker.netgear
-pynetgear==0.4.1
+pynetgear==0.4.2
# homeassistant.components.switch.netio
pynetio==0.1.6
@@ -1014,6 +1018,9 @@ pynx584==0.4
# homeassistant.components.openuv
pyopenuv==1.0.4
+# homeassistant.components.light.opple
+pyoppleio==1.0.5
+
# homeassistant.components.iota
pyota==2.0.5
@@ -1068,7 +1075,7 @@ pysma==0.2
pysnmp==4.4.5
# homeassistant.components.sonos
-pysonos==0.0.2
+pysonos==0.0.3
# homeassistant.components.spc
pyspcwebgw==0.4.0
@@ -1114,6 +1121,9 @@ python-forecastio==1.4.0
# homeassistant.components.gc100
python-gc100==1.0.3a
+# homeassistant.components.sensor.gitlab_ci
+python-gitlab==1.6.0
+
# homeassistant.components.sensor.hp_ilo
python-hpilo==3.9
@@ -1207,7 +1217,7 @@ pytouchline==0.7
pytrackr==0.0.5
# homeassistant.components.tradfri
-pytradfri[async]==5.5.1
+pytradfri[async]==6.0.1
# homeassistant.components.sensor.trafikverket_weatherstation
pytrafikverket==0.1.5.8
@@ -1215,9 +1225,6 @@ pytrafikverket==0.1.5.8
# homeassistant.components.device_tracker.unifi
pyunifi==2.13
-# homeassistant.components.upnp
-pyupnp-async==0.1.1.1
-
# homeassistant.components.binary_sensor.uptimerobot
pyuptimerobot==0.0.5
@@ -1328,7 +1335,7 @@ shodan==1.10.2
simplepush==1.1.4
# homeassistant.components.alarm_control_panel.simplisafe
-simplisafe-python==2.0.2
+simplisafe-python==3.1.2
# homeassistant.components.sisyphus
sisyphus-control==2.1
@@ -1546,7 +1553,7 @@ yeelight==0.4.0
yeelightsunflower==0.0.10
# homeassistant.components.media_extractor
-youtube_dl==2018.09.18
+youtube_dl==2018.09.26
# homeassistant.components.light.zengge
zengge==0.2
@@ -1567,4 +1574,4 @@ zigpy-xbee==0.1.1
zigpy==0.2.0
# homeassistant.components.zoneminder
-zm-py==0.0.4
+zm-py==0.0.5
diff --git a/requirements_test.txt b/requirements_test.txt
index 15e06c4e53d..9f36d8f42ca 100644
--- a/requirements_test.txt
+++ b/requirements_test.txt
@@ -13,5 +13,5 @@ pytest-aiohttp==0.3.0
pytest-cov==2.5.1
pytest-sugar==0.9.1
pytest-timeout==1.3.2
-pytest==3.8.0
+pytest==3.8.2
requests_mock==1.5.2
diff --git a/requirements_test_all.txt b/requirements_test_all.txt
index e67da9755cd..871714cc47d 100644
--- a/requirements_test_all.txt
+++ b/requirements_test_all.txt
@@ -14,7 +14,7 @@ pytest-aiohttp==0.3.0
pytest-cov==2.5.1
pytest-sugar==0.9.1
pytest-timeout==1.3.2
-pytest==3.8.0
+pytest==3.8.2
requests_mock==1.5.2
@@ -24,6 +24,9 @@ HAP-python==2.2.2
# homeassistant.components.sensor.rmvtransport
PyRMVtransport==0.1
+# homeassistant.components.notify.yessssms
+YesssSMS==0.2.3
+
# homeassistant.components.device_tracker.automatic
aioautomatic==0.6.5
@@ -52,11 +55,11 @@ dsmr_parser==0.11
# homeassistant.components.sensor.season
ephem==3.7.6.0
+# homeassistant.components.evohome
# homeassistant.components.climate.honeywell
-evohomeclient==0.2.5
+evohomeclient==0.2.7
# homeassistant.components.feedreader
-# homeassistant.components.sensor.geo_rss_events
feedparser==5.2.1
# homeassistant.components.sensor.foobot
@@ -68,15 +71,15 @@ gTTS-token==1.1.2
# homeassistant.components.geo_location.geo_json_events
geojson_client==0.1
+# homeassistant.components.sensor.geo_rss_events
+georss_client==0.3
+
# homeassistant.components.ffmpeg
ha-ffmpeg==1.9
# homeassistant.components.hangouts
hangups==0.4.5
-# homeassistant.components.sensor.geo_rss_events
-haversine==0.4.5
-
# homeassistant.components.mqtt.server
hbmqtt==0.9.4
@@ -87,7 +90,7 @@ hdate==0.6.3
holidays==0.9.7
# homeassistant.components.frontend
-home-assistant-frontend==20180927.0
+home-assistant-frontend==20181012.0
# homeassistant.components.homematicip_cloud
homematicip==0.9.8
@@ -169,7 +172,7 @@ pyotp==2.2.6
pyqwikswitch==0.8
# homeassistant.components.sonos
-pysonos==0.0.2
+pysonos==0.0.3
# homeassistant.components.spc
pyspcwebgw==0.4.0
@@ -185,14 +188,11 @@ python-nest==4.0.3
pythonwhois==2.4.3
# homeassistant.components.tradfri
-pytradfri[async]==5.5.1
+pytradfri[async]==6.0.1
# homeassistant.components.device_tracker.unifi
pyunifi==2.13
-# homeassistant.components.upnp
-pyupnp-async==0.1.1.1
-
# homeassistant.components.notify.html5
pywebpush==1.6.0
diff --git a/script/check_dirty b/script/check_dirty
new file mode 100755
index 00000000000..94db657a542
--- /dev/null
+++ b/script/check_dirty
@@ -0,0 +1,7 @@
+#!/bin/bash
+[[ -z $(git ls-files --others --exclude-standard) ]] && exit 0
+
+echo -e '\n***** ERROR\nTests are leaving files behind. Please update the tests to avoid writing any files:'
+git ls-files --others --exclude-standard
+echo
+exit 1
diff --git a/script/gen_requirements_all.py b/script/gen_requirements_all.py
index 7493e523273..9c0323bf5ca 100755
--- a/script/gen_requirements_all.py
+++ b/script/gen_requirements_all.py
@@ -51,6 +51,7 @@ TEST_REQUIREMENTS = (
'foobot_async',
'gTTS-token',
'geojson_client',
+ 'georss_client',
'hangups',
'HAP-python',
'ha-ffmpeg',
@@ -103,7 +104,8 @@ TEST_REQUIREMENTS = (
'yahoo-finance',
'pythonwhois',
'wakeonlan',
- 'vultr'
+ 'vultr',
+ 'YesssSMS',
)
IGNORE_PACKAGES = (
diff --git a/tests/common.py b/tests/common.py
index 0cb15d683b5..ee181cfa2e9 100644
--- a/tests/common.py
+++ b/tests/common.py
@@ -392,7 +392,7 @@ def ensure_auth_manager_loaded(auth_mgr):
"""Ensure an auth manager is considered loaded."""
store = auth_mgr._store
if store._users is None:
- store._users = OrderedDict()
+ store._set_defaults()
class MockModule:
diff --git a/tests/components/alarm_control_panel/common.py b/tests/components/alarm_control_panel/common.py
new file mode 100644
index 00000000000..cf2de857076
--- /dev/null
+++ b/tests/components/alarm_control_panel/common.py
@@ -0,0 +1,83 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.alarm_control_panel import DOMAIN
+from homeassistant.const import (
+ ATTR_CODE, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
+ SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
+ SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def alarm_disarm(hass, code=None, entity_id=None):
+ """Send the alarm the command for disarm."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
+
+
+@bind_hass
+def alarm_arm_home(hass, code=None, entity_id=None):
+ """Send the alarm the command for arm home."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
+
+
+@bind_hass
+def alarm_arm_away(hass, code=None, entity_id=None):
+ """Send the alarm the command for arm away."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
+
+
+@bind_hass
+def alarm_arm_night(hass, code=None, entity_id=None):
+ """Send the alarm the command for arm night."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
+
+
+@bind_hass
+def alarm_trigger(hass, code=None, entity_id=None):
+ """Send the alarm the command for trigger."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
+
+
+@bind_hass
+def alarm_arm_custom_bypass(hass, code=None, entity_id=None):
+ """Send the alarm the command for arm custom bypass."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_ALARM_ARM_CUSTOM_BYPASS, data)
diff --git a/tests/components/alarm_control_panel/test_manual.py b/tests/components/alarm_control_panel/test_manual.py
index 29f630093d9..02085a44b47 100644
--- a/tests/components/alarm_control_panel/test_manual.py
+++ b/tests/components/alarm_control_panel/test_manual.py
@@ -14,6 +14,7 @@ from homeassistant.components import alarm_control_panel
import homeassistant.util.dt as dt_util
from tests.common import fire_time_changed, get_test_home_assistant
+from tests.components.alarm_control_panel import common
CODE = 'HELLO_CODE'
@@ -53,7 +54,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, CODE)
+ common.alarm_arm_home(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_HOME,
@@ -76,7 +77,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, CODE, entity_id)
+ common.alarm_arm_home(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -111,7 +112,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, CODE + '2')
+ common.alarm_arm_home(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -134,7 +135,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id)
+ common.alarm_arm_away(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
@@ -160,7 +161,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, 'abc')
+ common.alarm_arm_home(self.hass, 'abc')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -183,7 +184,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -218,7 +219,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE + '2')
+ common.alarm_arm_away(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -241,7 +242,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_night(self.hass, CODE)
+ common.alarm_arm_night(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_NIGHT,
@@ -264,7 +265,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_night(self.hass, CODE, entity_id)
+ common.alarm_arm_night(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -284,7 +285,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
assert state.state == STATE_ALARM_ARMED_NIGHT
# Do not go to the pending state when updating to the same state
- alarm_control_panel.alarm_arm_night(self.hass, CODE, entity_id)
+ common.alarm_arm_night(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_NIGHT,
@@ -307,7 +308,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_night(self.hass, CODE + '2')
+ common.alarm_arm_night(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -329,7 +330,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -362,13 +363,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -402,7 +403,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -425,7 +426,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -448,7 +449,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -496,13 +497,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -540,13 +541,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -584,13 +585,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -640,13 +641,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -687,7 +688,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_home(self.hass)
+ common.alarm_arm_home(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -717,7 +718,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_away(self.hass)
+ common.alarm_arm_away(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -747,7 +748,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_night(self.hass)
+ common.alarm_arm_night(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -779,7 +780,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -820,7 +821,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -855,7 +856,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -881,7 +882,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -915,7 +916,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -947,13 +948,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id)
+ common.alarm_arm_away(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -985,13 +986,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id)
+ common.alarm_arm_away(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -1006,7 +1007,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -1037,13 +1038,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id)
+ common.alarm_disarm(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -1075,13 +1076,13 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id)
+ common.alarm_disarm(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1117,19 +1118,19 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, 'def')
+ common.alarm_arm_home(self.hass, 'def')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
self.assertEqual(STATE_ALARM_ARMED_HOME, state.state)
- alarm_control_panel.alarm_disarm(self.hass, 'def')
+ common.alarm_disarm(self.hass, 'def')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
self.assertEqual(STATE_ALARM_ARMED_HOME, state.state)
- alarm_control_panel.alarm_disarm(self.hass, 'abc')
+ common.alarm_disarm(self.hass, 'abc')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1152,7 +1153,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_custom_bypass(self.hass, CODE)
+ common.alarm_arm_custom_bypass(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_CUSTOM_BYPASS,
@@ -1175,7 +1176,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_custom_bypass(self.hass, CODE, entity_id)
+ common.alarm_arm_custom_bypass(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1211,7 +1212,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_custom_bypass(self.hass, CODE + '2')
+ common.alarm_arm_custom_bypass(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -1232,7 +1233,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_custom_bypass(self.hass)
+ common.alarm_arm_custom_bypass(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1271,7 +1272,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1281,7 +1282,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
self.assertEqual(STATE_ALARM_ARMED_AWAY,
state.attributes['post_pending_state'])
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1300,7 +1301,7 @@ class TestAlarmControlPanelManual(unittest.TestCase):
state = self.hass.states.get(entity_id)
self.assertEqual(STATE_ALARM_ARMED_AWAY, state.state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
diff --git a/tests/components/alarm_control_panel/test_manual_mqtt.py b/tests/components/alarm_control_panel/test_manual_mqtt.py
index 5b601f089dd..4e2ec6a9489 100644
--- a/tests/components/alarm_control_panel/test_manual_mqtt.py
+++ b/tests/components/alarm_control_panel/test_manual_mqtt.py
@@ -1,7 +1,7 @@
"""The tests for the manual_mqtt Alarm Control Panel component."""
from datetime import timedelta
import unittest
-from unittest.mock import patch
+from unittest.mock import patch, Mock
from homeassistant.setup import setup_component
from homeassistant.const import (
@@ -13,6 +13,7 @@ import homeassistant.util.dt as dt_util
from tests.common import (
fire_time_changed, get_test_home_assistant,
mock_mqtt_component, fire_mqtt_message, assert_setup_component)
+from tests.components.alarm_control_panel import common
CODE = 'HELLO_CODE'
@@ -23,6 +24,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
def setUp(self): # pylint: disable=invalid-name
"""Set up things to be run when tests are started."""
self.hass = get_test_home_assistant()
+ self.hass.config_entries._async_schedule_save = Mock()
self.mock_publish = mock_mqtt_component(self.hass)
def tearDown(self): # pylint: disable=invalid-name
@@ -69,7 +71,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, CODE)
+ common.alarm_arm_home(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_HOME,
@@ -94,7 +96,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, CODE, entity_id)
+ common.alarm_arm_home(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -131,7 +133,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, CODE + '2')
+ common.alarm_arm_home(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -156,7 +158,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id)
+ common.alarm_arm_away(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
@@ -184,7 +186,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, 'abc')
+ common.alarm_arm_home(self.hass, 'abc')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -209,7 +211,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -246,7 +248,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE + '2')
+ common.alarm_arm_away(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -271,7 +273,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_night(self.hass, CODE, entity_id)
+ common.alarm_arm_night(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_NIGHT,
@@ -296,7 +298,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_night(self.hass, CODE)
+ common.alarm_arm_night(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -316,7 +318,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.hass.states.get(entity_id).state)
# Do not go to the pending state when updating to the same state
- alarm_control_panel.alarm_arm_night(self.hass, CODE, entity_id)
+ common.alarm_arm_night(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_NIGHT,
@@ -341,7 +343,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_night(self.hass, CODE + '2')
+ common.alarm_arm_night(self.hass, CODE + '2')
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -365,7 +367,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -400,13 +402,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -442,7 +444,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -467,7 +469,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -492,7 +494,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -538,7 +540,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -575,7 +577,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -603,7 +605,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -639,7 +641,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -673,13 +675,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE, entity_id)
+ common.alarm_arm_away(self.hass, CODE, entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -694,7 +696,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_TRIGGERED,
@@ -727,13 +729,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id)
+ common.alarm_disarm(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_DISARMED,
@@ -767,13 +769,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_disarm(self.hass, entity_id=entity_id)
+ common.alarm_disarm(self.hass, entity_id=entity_id)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -811,13 +813,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -857,13 +859,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -903,13 +905,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -961,13 +963,13 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_ARMED_AWAY,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1010,7 +1012,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_home(self.hass)
+ common.alarm_arm_home(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1042,7 +1044,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_away(self.hass)
+ common.alarm_arm_away(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1074,7 +1076,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_arm_night(self.hass)
+ common.alarm_arm_night(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1108,7 +1110,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
entity_id = 'alarm_control_panel.test'
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1158,7 +1160,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_away(self.hass, CODE)
+ common.alarm_arm_away(self.hass, CODE)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1168,7 +1170,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_ARMED_AWAY,
state.attributes['post_pending_state'])
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1187,7 +1189,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
state = self.hass.states.get(entity_id)
self.assertEqual(STATE_ALARM_ARMED_AWAY, state.state)
- alarm_control_panel.alarm_trigger(self.hass, entity_id=entity_id)
+ common.alarm_trigger(self.hass, entity_id=entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1229,19 +1231,19 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_arm_home(self.hass, 'def')
+ common.alarm_arm_home(self.hass, 'def')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
self.assertEqual(STATE_ALARM_ARMED_HOME, state.state)
- alarm_control_panel.alarm_disarm(self.hass, 'def')
+ common.alarm_disarm(self.hass, 'def')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
self.assertEqual(STATE_ALARM_ARMED_HOME, state.state)
- alarm_control_panel.alarm_disarm(self.hass, 'abc')
+ common.alarm_disarm(self.hass, 'abc')
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
@@ -1367,7 +1369,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.assertEqual(STATE_ALARM_DISARMED,
self.hass.states.get(entity_id).state)
- alarm_control_panel.alarm_trigger(self.hass)
+ common.alarm_trigger(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_ALARM_PENDING,
@@ -1400,7 +1402,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# Arm in home mode
- alarm_control_panel.alarm_arm_home(self.hass)
+ common.alarm_arm_home(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/state', STATE_ALARM_PENDING, 0, True)
@@ -1416,7 +1418,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# Arm in away mode
- alarm_control_panel.alarm_arm_away(self.hass)
+ common.alarm_arm_away(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/state', STATE_ALARM_PENDING, 0, True)
@@ -1432,7 +1434,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# Arm in night mode
- alarm_control_panel.alarm_arm_night(self.hass)
+ common.alarm_arm_night(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/state', STATE_ALARM_PENDING, 0, True)
@@ -1448,7 +1450,7 @@ class TestAlarmControlPanelManualMqtt(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# Disarm
- alarm_control_panel.alarm_disarm(self.hass)
+ common.alarm_disarm(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/state', STATE_ALARM_DISARMED, 0, True)
diff --git a/tests/components/alarm_control_panel/test_mqtt.py b/tests/components/alarm_control_panel/test_mqtt.py
index ce152a3d7c9..dd606bb53ec 100644
--- a/tests/components/alarm_control_panel/test_mqtt.py
+++ b/tests/components/alarm_control_panel/test_mqtt.py
@@ -6,11 +6,13 @@ from homeassistant.const import (
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNAVAILABLE,
STATE_UNKNOWN)
-from homeassistant.components import alarm_control_panel
+from homeassistant.components import alarm_control_panel, mqtt
+from homeassistant.components.mqtt.discovery import async_start
from tests.common import (
- mock_mqtt_component, fire_mqtt_message, get_test_home_assistant,
- assert_setup_component)
+ mock_mqtt_component, async_fire_mqtt_message, fire_mqtt_message,
+ get_test_home_assistant, assert_setup_component, MockConfigEntry)
+from tests.components.alarm_control_panel import common
CODE = 'HELLO_CODE'
@@ -104,7 +106,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
}
})
- alarm_control_panel.alarm_arm_home(self.hass)
+ common.alarm_arm_home(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/command', 'ARM_HOME', 0, False)
@@ -122,7 +124,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
})
call_count = self.mock_publish.call_count
- alarm_control_panel.alarm_arm_home(self.hass, 'abcd')
+ common.alarm_arm_home(self.hass, 'abcd')
self.hass.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)
@@ -137,7 +139,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
}
})
- alarm_control_panel.alarm_arm_away(self.hass)
+ common.alarm_arm_away(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/command', 'ARM_AWAY', 0, False)
@@ -155,7 +157,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
})
call_count = self.mock_publish.call_count
- alarm_control_panel.alarm_arm_away(self.hass, 'abcd')
+ common.alarm_arm_away(self.hass, 'abcd')
self.hass.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)
@@ -170,7 +172,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
}
})
- alarm_control_panel.alarm_disarm(self.hass)
+ common.alarm_disarm(self.hass)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'alarm/command', 'DISARM', 0, False)
@@ -188,7 +190,7 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
})
call_count = self.mock_publish.call_count
- alarm_control_panel.alarm_disarm(self.hass, 'abcd')
+ common.alarm_disarm(self.hass, 'abcd')
self.hass.block_till_done()
self.assertEqual(call_count, self.mock_publish.call_count)
@@ -239,3 +241,33 @@ class TestAlarmControlPanelMQTT(unittest.TestCase):
self.assertEqual(STATE_UNAVAILABLE, state.state)
fire_mqtt_message(self.hass, 'availability-topic', 'good')
+
+
+async def test_discovery_removal_alarm(hass, mqtt_mock, caplog):
+ """Test removal of discovered alarm_control_panel."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Beer",'
+ ' "status_topic": "test_topic",'
+ ' "command_topic": "test_topic" }'
+ )
+
+ async_fire_mqtt_message(hass,
+ 'homeassistant/alarm_control_panel/bla/config',
+ data)
+ await hass.async_block_till_done()
+
+ state = hass.states.get('alarm_control_panel.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+
+ async_fire_mqtt_message(hass,
+ 'homeassistant/alarm_control_panel/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('alarm_control_panel.beer')
+ assert state is None
diff --git a/tests/components/alarm_control_panel/test_spc.py b/tests/components/alarm_control_panel/test_spc.py
deleted file mode 100644
index b1078e1b14f..00000000000
--- a/tests/components/alarm_control_panel/test_spc.py
+++ /dev/null
@@ -1,58 +0,0 @@
-"""Tests for Vanderbilt SPC alarm control panel platform."""
-from homeassistant.components.alarm_control_panel import spc
-from homeassistant.const import (
- STATE_ALARM_ARMED_AWAY, STATE_ALARM_DISARMED)
-from homeassistant.components.spc import (DATA_API)
-
-
-async def test_setup_platform(hass):
- """Test adding areas as separate alarm control panel devices."""
- added_entities = []
-
- def add_entities(entities):
- nonlocal added_entities
- added_entities = list(entities)
-
- area_defs = [{
- 'id': '1',
- 'name': 'House',
- 'mode': '3',
- 'last_set_time': '1485759851',
- 'last_set_user_id': '1',
- 'last_set_user_name': 'Pelle',
- 'last_unset_time': '1485800564',
- 'last_unset_user_id': '1',
- 'last_unset_user_name': 'Lisa',
- 'last_alarm': '1478174896'
- }, {
- 'id': '3',
- 'name': 'Garage',
- 'mode': '0',
- 'last_set_time': '1483705803',
- 'last_set_user_id': '9998',
- 'last_set_user_name': 'Pelle',
- 'last_unset_time': '1483705808',
- 'last_unset_user_id': '9998',
- 'last_unset_user_name': 'Lisa'
- }]
-
- from pyspcwebgw import Area
-
- areas = [Area(gateway=None, spc_area=a) for a in area_defs]
-
- hass.data[DATA_API] = None
-
- await spc.async_setup_platform(hass=hass,
- config={},
- async_add_entities=add_entities,
- discovery_info={'areas': areas})
-
- assert len(added_entities) == 2
-
- assert added_entities[0].name == 'House'
- assert added_entities[0].state == STATE_ALARM_ARMED_AWAY
- assert added_entities[0].changed_by == 'Pelle'
-
- assert added_entities[1].name == 'Garage'
- assert added_entities[1].state == STATE_ALARM_DISARMED
- assert added_entities[1].changed_by == 'Lisa'
diff --git a/tests/components/automation/common.py b/tests/components/automation/common.py
new file mode 100644
index 00000000000..4c8f91849aa
--- /dev/null
+++ b/tests/components/automation/common.py
@@ -0,0 +1,53 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.automation import DOMAIN, SERVICE_TRIGGER
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
+ SERVICE_RELOAD)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, entity_id=None):
+ """Turn on specified automation or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
+
+
+@bind_hass
+def turn_off(hass, entity_id=None):
+ """Turn off specified automation or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+def toggle(hass, entity_id=None):
+ """Toggle specified automation or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
+
+
+@bind_hass
+def trigger(hass, entity_id=None):
+ """Trigger specified automation or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
+
+
+@bind_hass
+def reload(hass):
+ """Reload the automation from config."""
+ hass.services.call(DOMAIN, SERVICE_RELOAD)
+
+
+@bind_hass
+def async_reload(hass):
+ """Reload the automation from config.
+
+ Returns a coroutine object.
+ """
+ return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
diff --git a/tests/components/automation/test_event.py b/tests/components/automation/test_event.py
index 6e16c03f2dc..09d237013b0 100644
--- a/tests/components/automation/test_event.py
+++ b/tests/components/automation/test_event.py
@@ -6,6 +6,7 @@ from homeassistant.setup import setup_component
import homeassistant.components.automation as automation
from tests.common import get_test_home_assistant, mock_component
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -50,7 +51,7 @@ class TestAutomationEvent(unittest.TestCase):
self.assertEqual(1, len(self.calls))
assert self.calls[0].context is context
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.hass.bus.fire('test_event')
@@ -75,7 +76,7 @@ class TestAutomationEvent(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.hass.bus.fire('test_event')
diff --git a/tests/components/automation/test_init.py b/tests/components/automation/test_init.py
index c3bd6c224af..3bcbc7da04f 100644
--- a/tests/components/automation/test_init.py
+++ b/tests/components/automation/test_init.py
@@ -15,6 +15,7 @@ import homeassistant.util.dt as dt_util
from tests.common import (
assert_setup_component, get_test_home_assistant, fire_time_changed,
mock_service, async_mock_service, mock_restore_cache)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -363,7 +364,7 @@ class TestAutomation(unittest.TestCase):
self.hass.block_till_done()
assert len(self.calls) == 1
- automation.turn_off(self.hass, entity_id)
+ common.turn_off(self.hass, entity_id)
self.hass.block_till_done()
assert not automation.is_on(self.hass, entity_id)
@@ -371,7 +372,7 @@ class TestAutomation(unittest.TestCase):
self.hass.block_till_done()
assert len(self.calls) == 1
- automation.toggle(self.hass, entity_id)
+ common.toggle(self.hass, entity_id)
self.hass.block_till_done()
assert automation.is_on(self.hass, entity_id)
@@ -379,17 +380,17 @@ class TestAutomation(unittest.TestCase):
self.hass.block_till_done()
assert len(self.calls) == 2
- automation.trigger(self.hass, entity_id)
+ common.trigger(self.hass, entity_id)
self.hass.block_till_done()
assert len(self.calls) == 3
- automation.turn_off(self.hass, entity_id)
+ common.turn_off(self.hass, entity_id)
self.hass.block_till_done()
- automation.trigger(self.hass, entity_id)
+ common.trigger(self.hass, entity_id)
self.hass.block_till_done()
assert len(self.calls) == 4
- automation.turn_on(self.hass, entity_id)
+ common.turn_on(self.hass, entity_id)
self.hass.block_till_done()
assert automation.is_on(self.hass, entity_id)
@@ -439,7 +440,7 @@ class TestAutomation(unittest.TestCase):
}}):
with patch('homeassistant.config.find_config_file',
return_value=''):
- automation.reload(self.hass)
+ common.reload(self.hass)
self.hass.block_till_done()
# De-flake ?!
self.hass.block_till_done()
@@ -489,7 +490,7 @@ class TestAutomation(unittest.TestCase):
return_value={automation.DOMAIN: 'not valid'}):
with patch('homeassistant.config.find_config_file',
return_value=''):
- automation.reload(self.hass)
+ common.reload(self.hass)
self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is None
@@ -527,7 +528,7 @@ class TestAutomation(unittest.TestCase):
side_effect=HomeAssistantError('bla')):
with patch('homeassistant.config.find_config_file',
return_value=''):
- automation.reload(self.hass)
+ common.reload(self.hass)
self.hass.block_till_done()
assert self.hass.states.get('automation.hello') is not None
diff --git a/tests/components/automation/test_litejet.py b/tests/components/automation/test_litejet.py
index ca6f7796cfc..3d88174708b 100644
--- a/tests/components/automation/test_litejet.py
+++ b/tests/components/automation/test_litejet.py
@@ -7,9 +7,10 @@ from datetime import timedelta
from homeassistant import setup
import homeassistant.util.dt as dt_util
from homeassistant.components import litejet
-from tests.common import (fire_time_changed, get_test_home_assistant)
import homeassistant.components.automation as automation
+from tests.common import (fire_time_changed, get_test_home_assistant)
+
_LOGGER = logging.getLogger(__name__)
ENTITY_SWITCH = 'switch.mock_switch_1'
diff --git a/tests/components/automation/test_mqtt.py b/tests/components/automation/test_mqtt.py
index 8ec5351af94..29a53467c4f 100644
--- a/tests/components/automation/test_mqtt.py
+++ b/tests/components/automation/test_mqtt.py
@@ -7,6 +7,7 @@ import homeassistant.components.automation as automation
from tests.common import (
mock_mqtt_component, fire_mqtt_message, get_test_home_assistant,
mock_component)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -56,7 +57,7 @@ class TestAutomationMQTT(unittest.TestCase):
self.assertEqual('mqtt - test-topic - { "hello": "world" } - world',
self.calls[0].data['some'])
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
fire_mqtt_message(self.hass, 'test-topic', 'test_payload')
self.hass.block_till_done()
diff --git a/tests/components/automation/test_numeric_state.py b/tests/components/automation/test_numeric_state.py
index af95bc0ff02..183d1f4a5f9 100644
--- a/tests/components/automation/test_numeric_state.py
+++ b/tests/components/automation/test_numeric_state.py
@@ -11,6 +11,7 @@ import homeassistant.util.dt as dt_util
from tests.common import (
get_test_home_assistant, mock_component, fire_time_changed,
assert_setup_component)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -57,7 +58,7 @@ class TestAutomationNumericState(unittest.TestCase):
# Set above 12 so the automation will fire again
self.hass.states.set('test.entity', 12)
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.hass.states.set('test.entity', 9)
self.hass.block_till_done()
@@ -775,7 +776,7 @@ class TestAutomationNumericState(unittest.TestCase):
self.hass.states.set('test.entity_1', 9)
self.hass.states.set('test.entity_2', 9)
self.hass.block_till_done()
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10))
diff --git a/tests/components/automation/test_state.py b/tests/components/automation/test_state.py
index 274980fabc0..15c6353b234 100644
--- a/tests/components/automation/test_state.py
+++ b/tests/components/automation/test_state.py
@@ -12,6 +12,7 @@ import homeassistant.components.automation as automation
from tests.common import (
fire_time_changed, get_test_home_assistant, assert_setup_component,
mock_component)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -68,7 +69,7 @@ class TestAutomationState(unittest.TestCase):
'state - test.entity - hello - world - None',
self.calls[0].data['some'])
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.hass.states.set('test.entity', 'planet')
self.hass.block_till_done()
@@ -370,7 +371,7 @@ class TestAutomationState(unittest.TestCase):
self.hass.states.set('test.entity_1', 'world')
self.hass.states.set('test.entity_2', 'world')
self.hass.block_till_done()
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
fire_time_changed(self.hass, dt_util.utcnow() + timedelta(seconds=10))
diff --git a/tests/components/automation/test_sun.py b/tests/components/automation/test_sun.py
index 4556b7cbe45..ad8709fdf36 100644
--- a/tests/components/automation/test_sun.py
+++ b/tests/components/automation/test_sun.py
@@ -12,6 +12,7 @@ import homeassistant.util.dt as dt_util
from tests.common import (
fire_time_changed, get_test_home_assistant, mock_component)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -57,7 +58,7 @@ class TestAutomationSun(unittest.TestCase):
}
})
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
fire_time_changed(self.hass, trigger_time)
@@ -66,7 +67,7 @@ class TestAutomationSun(unittest.TestCase):
with patch('homeassistant.util.dt.utcnow',
return_value=now):
- automation.turn_on(self.hass)
+ common.turn_on(self.hass)
self.hass.block_till_done()
fire_time_changed(self.hass, trigger_time)
diff --git a/tests/components/automation/test_template.py b/tests/components/automation/test_template.py
index e9c763ccc73..4fec0e707a9 100644
--- a/tests/components/automation/test_template.py
+++ b/tests/components/automation/test_template.py
@@ -7,6 +7,7 @@ import homeassistant.components.automation as automation
from tests.common import (
get_test_home_assistant, assert_setup_component, mock_component)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -49,7 +50,7 @@ class TestAutomationTemplate(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.hass.states.set('test.entity', 'planet')
diff --git a/tests/components/automation/test_time.py b/tests/components/automation/test_time.py
index 5f928cf92a0..dcb723d725e 100644
--- a/tests/components/automation/test_time.py
+++ b/tests/components/automation/test_time.py
@@ -11,6 +11,7 @@ import homeassistant.components.automation as automation
from tests.common import (
fire_time_changed, get_test_home_assistant, assert_setup_component,
mock_component)
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -52,7 +53,7 @@ class TestAutomationTime(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
fire_time_changed(self.hass, dt_util.utcnow().replace(hour=0))
diff --git a/tests/components/automation/test_webhook.py b/tests/components/automation/test_webhook.py
new file mode 100644
index 00000000000..a6cde395583
--- /dev/null
+++ b/tests/components/automation/test_webhook.py
@@ -0,0 +1,75 @@
+"""The tests for the webhook automation trigger."""
+from homeassistant.core import callback
+from homeassistant.setup import async_setup_component
+
+
+async def test_webhook_json(hass, aiohttp_client):
+ """Test triggering with a JSON webhook."""
+ events = []
+
+ @callback
+ def store_event(event):
+ """Helepr to store events."""
+ events.append(event)
+
+ hass.bus.async_listen('test_success', store_event)
+
+ assert await async_setup_component(hass, 'automation', {
+ 'automation': {
+ 'trigger': {
+ 'platform': 'webhook',
+ 'webhook_id': 'json_webhook'
+ },
+ 'action': {
+ 'event': 'test_success',
+ 'event_data_template': {
+ 'hello': 'yo {{ trigger.json.hello }}',
+ }
+ }
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ await client.post('/api/webhook/json_webhook', json={
+ 'hello': 'world'
+ })
+
+ assert len(events) == 1
+ assert events[0].data['hello'] == 'yo world'
+
+
+async def test_webhook_post(hass, aiohttp_client):
+ """Test triggering with a POST webhook."""
+ events = []
+
+ @callback
+ def store_event(event):
+ """Helepr to store events."""
+ events.append(event)
+
+ hass.bus.async_listen('test_success', store_event)
+
+ assert await async_setup_component(hass, 'automation', {
+ 'automation': {
+ 'trigger': {
+ 'platform': 'webhook',
+ 'webhook_id': 'post_webhook'
+ },
+ 'action': {
+ 'event': 'test_success',
+ 'event_data_template': {
+ 'hello': 'yo {{ trigger.data.hello }}',
+ }
+ }
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ await client.post('/api/webhook/post_webhook', data={
+ 'hello': 'world'
+ })
+
+ assert len(events) == 1
+ assert events[0].data['hello'] == 'yo world'
diff --git a/tests/components/automation/test_zone.py b/tests/components/automation/test_zone.py
index d146278a997..795f55a3e0b 100644
--- a/tests/components/automation/test_zone.py
+++ b/tests/components/automation/test_zone.py
@@ -6,6 +6,7 @@ from homeassistant.setup import setup_component
from homeassistant.components import automation, zone
from tests.common import get_test_home_assistant, mock_component
+from tests.components.automation import common
# pylint: disable=invalid-name
@@ -87,7 +88,7 @@ class TestAutomationZone(unittest.TestCase):
})
self.hass.block_till_done()
- automation.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.hass.states.set('test.entity', 'hello', {
diff --git a/tests/components/binary_sensor/test_mqtt.py b/tests/components/binary_sensor/test_mqtt.py
index 57050c2cbf5..84619ce4ee6 100644
--- a/tests/components/binary_sensor/test_mqtt.py
+++ b/tests/components/binary_sensor/test_mqtt.py
@@ -2,14 +2,17 @@
import unittest
import homeassistant.core as ha
-from homeassistant.setup import setup_component
-import homeassistant.components.binary_sensor as binary_sensor
+from homeassistant.setup import setup_component, async_setup_component
+from homeassistant.components import binary_sensor, mqtt
+from homeassistant.components.mqtt.discovery import async_start
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE
-from tests.common import get_test_home_assistant, fire_mqtt_message
-from tests.common import mock_component, mock_mqtt_component
+from tests.common import (
+ get_test_home_assistant, fire_mqtt_message, async_fire_mqtt_message,
+ mock_component, mock_mqtt_component, async_mock_mqtt_component,
+ MockConfigEntry)
class TestSensorMQTT(unittest.TestCase):
@@ -77,25 +80,6 @@ class TestSensorMQTT(unittest.TestCase):
state = self.hass.states.get('binary_sensor.test')
self.assertIsNone(state)
- def test_unique_id(self):
- """Test unique id option only creates one sensor per unique_id."""
- assert setup_component(self.hass, binary_sensor.DOMAIN, {
- binary_sensor.DOMAIN: [{
- 'platform': 'mqtt',
- 'name': 'Test 1',
- 'state_topic': 'test-topic',
- 'unique_id': 'TOTALLY_UNIQUE'
- }, {
- 'platform': 'mqtt',
- 'name': 'Test 2',
- 'state_topic': 'test-topic',
- 'unique_id': 'TOTALLY_UNIQUE'
- }]
- })
- fire_mqtt_message(self.hass, 'test-topic', 'payload')
- self.hass.block_till_done()
- assert len(self.hass.states.all()) == 1
-
def test_availability_without_topic(self):
"""Test availability without defined availability topic."""
self.assertTrue(setup_component(self.hass, binary_sensor.DOMAIN, {
@@ -223,3 +207,46 @@ class TestSensorMQTT(unittest.TestCase):
fire_mqtt_message(self.hass, 'test-topic', 'ON')
self.hass.block_till_done()
self.assertEqual(2, len(events))
+
+
+async def test_unique_id(hass):
+ """Test unique id option only creates one sensor per unique_id."""
+ await async_mock_mqtt_component(hass)
+ assert await async_setup_component(hass, binary_sensor.DOMAIN, {
+ binary_sensor.DOMAIN: [{
+ 'platform': 'mqtt',
+ 'name': 'Test 1',
+ 'state_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }, {
+ 'platform': 'mqtt',
+ 'name': 'Test 2',
+ 'state_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }]
+ })
+ async_fire_mqtt_message(hass, 'test-topic', 'payload')
+ await hass.async_block_till_done()
+ assert len(hass.states.async_all()) == 1
+
+
+async def test_discovery_removal_binary_sensor(hass, mqtt_mock, caplog):
+ """Test removal of discovered binary_sensor."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+ data = (
+ '{ "name": "Beer",'
+ ' "status_topic": "test_topic" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('binary_sensor.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('binary_sensor.beer')
+ assert state is None
diff --git a/tests/components/binary_sensor/test_spc.py b/tests/components/binary_sensor/test_spc.py
deleted file mode 100644
index ec0886aeed8..00000000000
--- a/tests/components/binary_sensor/test_spc.py
+++ /dev/null
@@ -1,55 +0,0 @@
-"""Tests for Vanderbilt SPC binary sensor platform."""
-from homeassistant.components.binary_sensor import spc
-
-
-async def test_setup_platform(hass):
- """Test autodiscovery of supported device types."""
- added_entities = []
-
- zone_defs = [{
- 'id': '1',
- 'type': '3',
- 'zone_name': 'Kitchen smoke',
- 'area': '1',
- 'area_name': 'House',
- 'input': '0',
- 'status': '0',
- }, {
- 'id': '3',
- 'type': '0',
- 'zone_name': 'Hallway PIR',
- 'area': '1',
- 'area_name': 'House',
- 'input': '0',
- 'status': '0',
- }, {
- 'id': '5',
- 'type': '1',
- 'zone_name': 'Front door',
- 'area': '1',
- 'area_name': 'House',
- 'input': '1',
- 'status': '0',
- }]
-
- def add_entities(entities):
- nonlocal added_entities
- added_entities = list(entities)
-
- from pyspcwebgw import Zone
-
- zones = [Zone(area=None, spc_zone=z) for z in zone_defs]
-
- await spc.async_setup_platform(hass=hass,
- config={},
- async_add_entities=add_entities,
- discovery_info={'devices': zones})
-
- assert len(added_entities) == 3
- assert added_entities[0].device_class == 'smoke'
- assert added_entities[0].state == 'off'
- assert added_entities[1].device_class == 'motion'
- assert added_entities[1].state == 'off'
- assert added_entities[2].device_class == 'opening'
- assert added_entities[2].state == 'on'
- assert all(d.hidden for d in added_entities)
diff --git a/tests/components/camera/common.py b/tests/components/camera/common.py
new file mode 100644
index 00000000000..21f7244bd29
--- /dev/null
+++ b/tests/components/camera/common.py
@@ -0,0 +1,47 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.camera import (
+ ATTR_FILENAME, DOMAIN, SERVICE_ENABLE_MOTION, SERVICE_SNAPSHOT)
+from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \
+ SERVICE_TURN_ON
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+async def async_turn_off(hass, entity_id=None):
+ """Turn off camera."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+async def async_turn_on(hass, entity_id=None):
+ """Turn on camera, and set operation mode."""
+ data = {}
+ if entity_id is not None:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)
+
+
+@bind_hass
+def enable_motion_detection(hass, entity_id=None):
+ """Enable Motion Detection."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_ENABLE_MOTION, data))
+
+
+@bind_hass
+@callback
+def async_snapshot(hass, filename, entity_id=None):
+ """Make a snapshot from a camera."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ data[ATTR_FILENAME] = filename
+
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_SNAPSHOT, data))
diff --git a/tests/components/camera/test_demo.py b/tests/components/camera/test_demo.py
index 63c70ddc6ca..f6e2513380c 100644
--- a/tests/components/camera/test_demo.py
+++ b/tests/components/camera/test_demo.py
@@ -8,6 +8,8 @@ from homeassistant.components.camera import STATE_STREAMING, STATE_IDLE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
+from tests.components.camera import common
+
@pytest.fixture
def demo_camera(hass):
@@ -37,12 +39,12 @@ async def test_init_state_is_streaming(hass, demo_camera):
async def test_turn_on_state_back_to_streaming(hass, demo_camera):
"""After turn on state back to streaming."""
assert demo_camera.state == STATE_STREAMING
- await camera.async_turn_off(hass, demo_camera.entity_id)
+ await common.async_turn_off(hass, demo_camera.entity_id)
await hass.async_block_till_done()
assert demo_camera.state == STATE_IDLE
- await camera.async_turn_on(hass, demo_camera.entity_id)
+ await common.async_turn_on(hass, demo_camera.entity_id)
await hass.async_block_till_done()
assert demo_camera.state == STATE_STREAMING
@@ -50,7 +52,7 @@ async def test_turn_on_state_back_to_streaming(hass, demo_camera):
async def test_turn_off_image(hass, demo_camera):
"""After turn off, Demo camera raise error."""
- await camera.async_turn_off(hass, demo_camera.entity_id)
+ await common.async_turn_off(hass, demo_camera.entity_id)
await hass.async_block_till_done()
with pytest.raises(HomeAssistantError) as error:
@@ -61,7 +63,7 @@ async def test_turn_off_image(hass, demo_camera):
async def test_turn_off_invalid_camera(hass, demo_camera):
"""Turn off non-exist camera should quietly fail."""
assert demo_camera.state == STATE_STREAMING
- await camera.async_turn_off(hass, 'camera.invalid_camera')
+ await common.async_turn_off(hass, 'camera.invalid_camera')
await hass.async_block_till_done()
assert demo_camera.state == STATE_STREAMING
@@ -81,7 +83,7 @@ async def test_motion_detection(hass):
assert not state.attributes.get('motion_detection')
# Call service to turn on motion detection
- camera.enable_motion_detection(hass, 'camera.demo_camera')
+ common.enable_motion_detection(hass, 'camera.demo_camera')
await hass.async_block_till_done()
# Check if state has been updated.
diff --git a/tests/components/camera/test_init.py b/tests/components/camera/test_init.py
index 053fa6d29dc..6b98f378ef0 100644
--- a/tests/components/camera/test_init.py
+++ b/tests/components/camera/test_init.py
@@ -7,13 +7,15 @@ import pytest
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.const import ATTR_ENTITY_PICTURE
-from homeassistant.components import camera, http, websocket_api
+from homeassistant.components import camera, http
+from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.async_ import run_coroutine_threadsafe
from tests.common import (
get_test_home_assistant, get_test_instance_port, assert_setup_component,
mock_coro)
+from tests.components.camera import common
@pytest.fixture
@@ -126,7 +128,7 @@ def test_snapshot_service(hass, mock_camera):
with patch('homeassistant.components.camera.open', mopen, create=True), \
patch.object(hass.config, 'is_allowed_path',
return_value=True):
- hass.components.camera.async_snapshot('/tmp/bla')
+ common.async_snapshot(hass, '/tmp/bla')
yield from hass.async_block_till_done()
mock_write = mopen().write
@@ -149,7 +151,7 @@ async def test_webocket_camera_thumbnail(hass, hass_ws_client, mock_camera):
msg = await client.receive_json()
assert msg['id'] == 5
- assert msg['type'] == websocket_api.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
assert msg['result']['content_type'] == 'image/jpeg'
assert msg['result']['content'] == \
diff --git a/tests/components/climate/common.py b/tests/components/climate/common.py
new file mode 100644
index 00000000000..4ac6f553091
--- /dev/null
+++ b/tests/components/climate/common.py
@@ -0,0 +1,115 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.climate import (
+ _LOGGER, ATTR_AUX_HEAT, ATTR_AWAY_MODE, ATTR_FAN_MODE, ATTR_HOLD_MODE,
+ ATTR_HUMIDITY, ATTR_OPERATION_MODE, ATTR_SWING_MODE, ATTR_TARGET_TEMP_HIGH,
+ ATTR_TARGET_TEMP_LOW, DOMAIN, SERVICE_SET_AWAY_MODE, SERVICE_SET_HOLD_MODE,
+ SERVICE_SET_AUX_HEAT, SERVICE_SET_TEMPERATURE, SERVICE_SET_HUMIDITY,
+ SERVICE_SET_FAN_MODE, SERVICE_SET_OPERATION_MODE, SERVICE_SET_SWING_MODE)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, ATTR_TEMPERATURE)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def set_away_mode(hass, away_mode, entity_id=None):
+ """Turn all or specified climate devices away mode on."""
+ data = {
+ ATTR_AWAY_MODE: away_mode
+ }
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
+
+
+@bind_hass
+def set_hold_mode(hass, hold_mode, entity_id=None):
+ """Set new hold mode."""
+ data = {
+ ATTR_HOLD_MODE: hold_mode
+ }
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
+
+
+@bind_hass
+def set_aux_heat(hass, aux_heat, entity_id=None):
+ """Turn all or specified climate devices auxiliary heater on."""
+ data = {
+ ATTR_AUX_HEAT: aux_heat
+ }
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
+
+
+@bind_hass
+def set_temperature(hass, temperature=None, entity_id=None,
+ target_temp_high=None, target_temp_low=None,
+ operation_mode=None):
+ """Set new target temperature."""
+ kwargs = {
+ key: value for key, value in [
+ (ATTR_TEMPERATURE, temperature),
+ (ATTR_TARGET_TEMP_HIGH, target_temp_high),
+ (ATTR_TARGET_TEMP_LOW, target_temp_low),
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_OPERATION_MODE, operation_mode)
+ ] if value is not None
+ }
+ _LOGGER.debug("set_temperature start data=%s", kwargs)
+ hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs)
+
+
+@bind_hass
+def set_humidity(hass, humidity, entity_id=None):
+ """Set new target humidity."""
+ data = {ATTR_HUMIDITY: humidity}
+
+ if entity_id is not None:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data)
+
+
+@bind_hass
+def set_fan_mode(hass, fan, entity_id=None):
+ """Set all or specified climate devices fan mode on."""
+ data = {ATTR_FAN_MODE: fan}
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
+
+
+@bind_hass
+def set_operation_mode(hass, operation_mode, entity_id=None):
+ """Set new target operation mode."""
+ data = {ATTR_OPERATION_MODE: operation_mode}
+
+ if entity_id is not None:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
+
+
+@bind_hass
+def set_swing_mode(hass, swing_mode, entity_id=None):
+ """Set new target swing mode."""
+ data = {ATTR_SWING_MODE: swing_mode}
+
+ if entity_id is not None:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
diff --git a/tests/components/climate/test_demo.py b/tests/components/climate/test_demo.py
index 0cd6d288536..4990a8a6998 100644
--- a/tests/components/climate/test_demo.py
+++ b/tests/components/climate/test_demo.py
@@ -8,6 +8,7 @@ from homeassistant.setup import setup_component
from homeassistant.components import climate
from tests.common import get_test_home_assistant
+from tests.components.climate import common
ENTITY_CLIMATE = 'climate.hvac'
@@ -56,7 +57,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting the target temperature without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(21, state.attributes.get('temperature'))
- climate.set_temperature(self.hass, None, ENTITY_CLIMATE)
+ common.set_temperature(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
self.assertEqual(21, state.attributes.get('temperature'))
@@ -64,7 +65,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test the setting of the target temperature."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(21, state.attributes.get('temperature'))
- climate.set_temperature(self.hass, 30, ENTITY_CLIMATE)
+ common.set_temperature(self.hass, 30, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(30.0, state.attributes.get('temperature'))
@@ -73,7 +74,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test the setting of the target temperature."""
state = self.hass.states.get(ENTITY_HEATPUMP)
self.assertEqual(20, state.attributes.get('temperature'))
- climate.set_temperature(self.hass, 21, ENTITY_HEATPUMP)
+ common.set_temperature(self.hass, 21, ENTITY_HEATPUMP)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_HEATPUMP)
self.assertEqual(21.0, state.attributes.get('temperature'))
@@ -84,8 +85,8 @@ class TestDemoClimate(unittest.TestCase):
self.assertEqual(None, state.attributes.get('temperature'))
self.assertEqual(21.0, state.attributes.get('target_temp_low'))
self.assertEqual(24.0, state.attributes.get('target_temp_high'))
- climate.set_temperature(self.hass, target_temp_high=25,
- target_temp_low=20, entity_id=ENTITY_ECOBEE)
+ common.set_temperature(self.hass, target_temp_high=25,
+ target_temp_low=20, entity_id=ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
self.assertEqual(None, state.attributes.get('temperature'))
@@ -98,9 +99,9 @@ class TestDemoClimate(unittest.TestCase):
self.assertEqual(None, state.attributes.get('temperature'))
self.assertEqual(21.0, state.attributes.get('target_temp_low'))
self.assertEqual(24.0, state.attributes.get('target_temp_high'))
- climate.set_temperature(self.hass, temperature=None,
- entity_id=ENTITY_ECOBEE, target_temp_low=None,
- target_temp_high=None)
+ common.set_temperature(self.hass, temperature=None,
+ entity_id=ENTITY_ECOBEE, target_temp_low=None,
+ target_temp_high=None)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
self.assertEqual(None, state.attributes.get('temperature'))
@@ -111,7 +112,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting the target humidity without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(67, state.attributes.get('humidity'))
- climate.set_humidity(self.hass, None, ENTITY_CLIMATE)
+ common.set_humidity(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(67, state.attributes.get('humidity'))
@@ -120,7 +121,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test the setting of the target humidity."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(67, state.attributes.get('humidity'))
- climate.set_humidity(self.hass, 64, ENTITY_CLIMATE)
+ common.set_humidity(self.hass, 64, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(64.0, state.attributes.get('humidity'))
@@ -129,7 +130,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting fan mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("On High", state.attributes.get('fan_mode'))
- climate.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("On High", state.attributes.get('fan_mode'))
@@ -138,7 +139,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting of new fan mode."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("On High", state.attributes.get('fan_mode'))
- climate.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE)
+ common.set_fan_mode(self.hass, "On Low", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("On Low", state.attributes.get('fan_mode'))
@@ -147,7 +148,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting swing mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("Off", state.attributes.get('swing_mode'))
- climate.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("Off", state.attributes.get('swing_mode'))
@@ -156,7 +157,7 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting of new swing mode."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("Off", state.attributes.get('swing_mode'))
- climate.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE)
+ common.set_swing_mode(self.hass, "Auto", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("Auto", state.attributes.get('swing_mode'))
@@ -169,7 +170,7 @@ class TestDemoClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("cool", state.attributes.get('operation_mode'))
self.assertEqual("cool", state.state)
- climate.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("cool", state.attributes.get('operation_mode'))
@@ -180,7 +181,7 @@ class TestDemoClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("cool", state.attributes.get('operation_mode'))
self.assertEqual("cool", state.state)
- climate.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, "heat", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("heat", state.attributes.get('operation_mode'))
@@ -190,41 +191,41 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting the away mode without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('on', state.attributes.get('away_mode'))
- climate.set_away_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_away_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
self.assertEqual('on', state.attributes.get('away_mode'))
def test_set_away_mode_on(self):
"""Test setting the away mode on/true."""
- climate.set_away_mode(self.hass, True, ENTITY_CLIMATE)
+ common.set_away_mode(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('on', state.attributes.get('away_mode'))
def test_set_away_mode_off(self):
"""Test setting the away mode off/false."""
- climate.set_away_mode(self.hass, False, ENTITY_CLIMATE)
+ common.set_away_mode(self.hass, False, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('away_mode'))
def test_set_hold_mode_home(self):
"""Test setting the hold mode home."""
- climate.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE)
+ common.set_hold_mode(self.hass, 'home', ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
self.assertEqual('home', state.attributes.get('hold_mode'))
def test_set_hold_mode_away(self):
"""Test setting the hold mode away."""
- climate.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE)
+ common.set_hold_mode(self.hass, 'away', ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
self.assertEqual('away', state.attributes.get('hold_mode'))
def test_set_hold_mode_none(self):
"""Test setting the hold mode off/false."""
- climate.set_hold_mode(self.hass, 'off', ENTITY_ECOBEE)
+ common.set_hold_mode(self.hass, 'off', ENTITY_ECOBEE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ECOBEE)
self.assertEqual('off', state.attributes.get('hold_mode'))
@@ -233,20 +234,20 @@ class TestDemoClimate(unittest.TestCase):
"""Test setting the auxiliary heater without required attribute."""
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('aux_heat'))
- climate.set_aux_heat(self.hass, None, ENTITY_CLIMATE)
+ common.set_aux_heat(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
self.assertEqual('off', state.attributes.get('aux_heat'))
def test_set_aux_heat_on(self):
"""Test setting the axillary heater on/true."""
- climate.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
+ common.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('on', state.attributes.get('aux_heat'))
def test_set_aux_heat_off(self):
"""Test setting the auxiliary heater off/false."""
- climate.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
+ common.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('aux_heat'))
diff --git a/tests/components/climate/test_generic_thermostat.py b/tests/components/climate/test_generic_thermostat.py
index c4e07705230..47ec621aeb5 100644
--- a/tests/components/climate/test_generic_thermostat.py
+++ b/tests/components/climate/test_generic_thermostat.py
@@ -25,6 +25,7 @@ from homeassistant.components.climate import STATE_HEAT, STATE_COOL
import homeassistant.components as comps
from tests.common import (assert_setup_component, get_test_home_assistant,
mock_restore_cache)
+from tests.components.climate import common
ENTITY = 'climate.test'
@@ -108,7 +109,7 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase):
self._setup_sensor(18)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 23)
+ common.set_temperature(self.hass, 23)
self.hass.block_till_done()
self.assertEqual(STATE_ON,
@@ -135,7 +136,7 @@ class TestGenericThermostatHeaterSwitching(unittest.TestCase):
self._setup_sensor(18)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 23)
+ common.set_temperature(self.hass, 23)
self.hass.block_till_done()
self.assertEqual(STATE_ON,
@@ -186,20 +187,20 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_set_target_temp(self):
"""Test the setting of the target temperature."""
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
self.assertEqual(30.0, state.attributes.get('temperature'))
- climate.set_temperature(self.hass, None)
+ common.set_temperature(self.hass, None)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
self.assertEqual(30.0, state.attributes.get('temperature'))
def test_set_away_mode(self):
"""Test the setting away mode."""
- climate.set_temperature(self.hass, 23)
+ common.set_temperature(self.hass, 23)
self.hass.block_till_done()
- climate.set_away_mode(self.hass, True)
+ common.set_away_mode(self.hass, True)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
self.assertEqual(16, state.attributes.get('temperature'))
@@ -209,13 +210,13 @@ class TestClimateGenericThermostat(unittest.TestCase):
Verify original temperature is restored.
"""
- climate.set_temperature(self.hass, 23)
+ common.set_temperature(self.hass, 23)
self.hass.block_till_done()
- climate.set_away_mode(self.hass, True)
+ common.set_away_mode(self.hass, True)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
self.assertEqual(16, state.attributes.get('temperature'))
- climate.set_away_mode(self.hass, False)
+ common.set_away_mode(self.hass, False)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
self.assertEqual(23, state.attributes.get('temperature'))
@@ -236,7 +237,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
self._setup_switch(False)
self._setup_sensor(25)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
call = self.calls[0]
@@ -249,7 +250,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
self._setup_switch(True)
self._setup_sensor(30)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self.assertEqual(2, len(self.calls))
call = self.calls[0]
@@ -260,7 +261,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_temp_change_heater_on_within_tolerance(self):
"""Test if temperature change doesn't turn on within tolerance."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(29)
self.hass.block_till_done()
@@ -269,7 +270,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_temp_change_heater_on_outside_tolerance(self):
"""Test if temperature change turn heater on outside cold tolerance."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(27)
self.hass.block_till_done()
@@ -282,7 +283,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_temp_change_heater_off_within_tolerance(self):
"""Test if temperature change doesn't turn off within tolerance."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(33)
self.hass.block_till_done()
@@ -291,7 +292,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_temp_change_heater_off_outside_tolerance(self):
"""Test if temperature change turn heater off outside hot tolerance."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(35)
self.hass.block_till_done()
@@ -304,9 +305,9 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_running_when_operating_mode_is_off(self):
"""Test that the switch turns off when enabled is set False."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
- climate.set_operation_mode(self.hass, STATE_OFF)
+ common.set_operation_mode(self.hass, STATE_OFF)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
call = self.calls[0]
@@ -317,9 +318,9 @@ class TestClimateGenericThermostat(unittest.TestCase):
def test_no_state_change_when_operation_mode_off(self):
"""Test that the switch doesn't turn on when enabled is False."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
- climate.set_operation_mode(self.hass, STATE_OFF)
+ common.set_operation_mode(self.hass, STATE_OFF)
self.hass.block_till_done()
self._setup_sensor(25)
self.hass.block_till_done()
@@ -328,7 +329,7 @@ class TestClimateGenericThermostat(unittest.TestCase):
@mock.patch('logging.Logger.error')
def test_invalid_operating_mode(self, log_mock):
"""Test error handling for invalid operation mode."""
- climate.set_operation_mode(self.hass, 'invalid mode')
+ common.set_operation_mode(self.hass, 'invalid mode')
self.hass.block_till_done()
self.assertEqual(log_mock.call_count, 1)
@@ -337,12 +338,12 @@ class TestClimateGenericThermostat(unittest.TestCase):
Switch turns on when temp below setpoint and mode changes.
"""
- climate.set_operation_mode(self.hass, STATE_OFF)
- climate.set_temperature(self.hass, 30)
+ common.set_operation_mode(self.hass, STATE_OFF)
+ common.set_temperature(self.hass, 30)
self._setup_sensor(25)
self.hass.block_till_done()
self._setup_switch(False)
- climate.set_operation_mode(self.hass, climate.STATE_HEAT)
+ common.set_operation_mode(self.hass, climate.STATE_HEAT)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
call = self.calls[0]
@@ -395,7 +396,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
self._setup_switch(True)
self._setup_sensor(25)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self.assertEqual(2, len(self.calls))
call = self.calls[0]
@@ -407,9 +408,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
"""Test the setting away mode when cooling."""
self._setup_sensor(25)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 19)
+ common.set_temperature(self.hass, 19)
self.hass.block_till_done()
- climate.set_away_mode(self.hass, True)
+ common.set_away_mode(self.hass, True)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY)
self.assertEqual(30, state.attributes.get('temperature'))
@@ -419,12 +420,12 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
Switch turns on when temp below setpoint and mode changes.
"""
- climate.set_operation_mode(self.hass, STATE_OFF)
- climate.set_temperature(self.hass, 25)
+ common.set_operation_mode(self.hass, STATE_OFF)
+ common.set_temperature(self.hass, 25)
self._setup_sensor(30)
self.hass.block_till_done()
self._setup_switch(False)
- climate.set_operation_mode(self.hass, climate.STATE_COOL)
+ common.set_operation_mode(self.hass, climate.STATE_COOL)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
call = self.calls[0]
@@ -437,7 +438,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
self._setup_switch(False)
self._setup_sensor(30)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
call = self.calls[0]
@@ -448,7 +449,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
def test_temp_change_ac_off_within_tolerance(self):
"""Test if temperature change doesn't turn ac off within tolerance."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(29.8)
self.hass.block_till_done()
@@ -457,7 +458,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
def test_set_temp_change_ac_off_outside_tolerance(self):
"""Test if temperature change turn ac off."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(27)
self.hass.block_till_done()
@@ -470,7 +471,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
def test_temp_change_ac_on_within_tolerance(self):
"""Test if temperature change doesn't turn ac on within tolerance."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self._setup_sensor(25.2)
self.hass.block_till_done()
@@ -479,7 +480,7 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
def test_temp_change_ac_on_outside_tolerance(self):
"""Test if temperature change turn ac on."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
@@ -492,9 +493,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
def test_running_when_operating_mode_is_off(self):
"""Test that the switch turns off when enabled is set False."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
- climate.set_operation_mode(self.hass, STATE_OFF)
+ common.set_operation_mode(self.hass, STATE_OFF)
self.hass.block_till_done()
self.assertEqual(1, len(self.calls))
call = self.calls[0]
@@ -505,9 +506,9 @@ class TestClimateGenericThermostatACMode(unittest.TestCase):
def test_no_state_change_when_operation_mode_off(self):
"""Test that the switch doesn't turn on when enabled is False."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
- climate.set_operation_mode(self.hass, STATE_OFF)
+ common.set_operation_mode(self.hass, STATE_OFF)
self.hass.block_till_done()
self._setup_sensor(35)
self.hass.block_till_done()
@@ -556,7 +557,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
def test_temp_change_ac_trigger_on_not_long_enough(self):
"""Test if temperature change turn ac on."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
@@ -569,7 +570,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
return_value=fake_changed):
self._setup_switch(False)
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
@@ -582,7 +583,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
def test_temp_change_ac_trigger_off_not_long_enough(self):
"""Test if temperature change turn ac on."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(25)
self.hass.block_till_done()
@@ -595,7 +596,7 @@ class TestClimateGenericThermostatACModeMinCycle(unittest.TestCase):
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
return_value=fake_changed):
self._setup_switch(True)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(25)
self.hass.block_till_done()
@@ -647,7 +648,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase):
def test_temp_change_heater_trigger_off_not_long_enough(self):
"""Test if temp change doesn't turn heater off because of time."""
self._setup_switch(True)
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
@@ -656,7 +657,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase):
def test_temp_change_heater_trigger_on_not_long_enough(self):
"""Test if temp change doesn't turn heater on because of time."""
self._setup_switch(False)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(25)
self.hass.block_till_done()
@@ -669,7 +670,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase):
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
return_value=fake_changed):
self._setup_switch(False)
- climate.set_temperature(self.hass, 30)
+ common.set_temperature(self.hass, 30)
self.hass.block_till_done()
self._setup_sensor(25)
self.hass.block_till_done()
@@ -686,7 +687,7 @@ class TestClimateGenericThermostatMinCycle(unittest.TestCase):
with mock.patch('homeassistant.helpers.condition.dt_util.utcnow',
return_value=fake_changed):
self._setup_switch(True)
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
@@ -743,7 +744,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase):
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
test_time = datetime.datetime.now(pytz.UTC)
self._send_time_changed(test_time)
@@ -766,7 +767,7 @@ class TestClimateGenericThermostatACKeepAlive(unittest.TestCase):
self.hass.block_till_done()
self._setup_sensor(20)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
test_time = datetime.datetime.now(pytz.UTC)
self._send_time_changed(test_time)
@@ -833,7 +834,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase):
self.hass.block_till_done()
self._setup_sensor(20)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
test_time = datetime.datetime.now(pytz.UTC)
self._send_time_changed(test_time)
@@ -856,7 +857,7 @@ class TestClimateGenericThermostatKeepAlive(unittest.TestCase):
self.hass.block_till_done()
self._setup_sensor(30)
self.hass.block_till_done()
- climate.set_temperature(self.hass, 25)
+ common.set_temperature(self.hass, 25)
self.hass.block_till_done()
test_time = datetime.datetime.now(pytz.UTC)
self._send_time_changed(test_time)
@@ -926,7 +927,7 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase):
def test_turn_on_when_off(self):
"""Test if climate.turn_on turns on a turned off device."""
- climate.set_operation_mode(self.hass, STATE_OFF)
+ common.set_operation_mode(self.hass, STATE_OFF)
self.hass.block_till_done()
self.hass.services.call('climate', SERVICE_TURN_ON)
self.hass.block_till_done()
@@ -939,8 +940,8 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase):
def test_turn_on_when_on(self):
"""Test if climate.turn_on does nothing to a turned on device."""
- climate.set_operation_mode(self.hass, STATE_HEAT, self.HEAT_ENTITY)
- climate.set_operation_mode(self.hass, STATE_COOL, self.COOL_ENTITY)
+ common.set_operation_mode(self.hass, STATE_HEAT, self.HEAT_ENTITY)
+ common.set_operation_mode(self.hass, STATE_COOL, self.COOL_ENTITY)
self.hass.block_till_done()
self.hass.services.call('climate', SERVICE_TURN_ON)
self.hass.block_till_done()
@@ -953,8 +954,8 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase):
def test_turn_off_when_on(self):
"""Test if climate.turn_off turns off a turned on device."""
- climate.set_operation_mode(self.hass, STATE_HEAT, self.HEAT_ENTITY)
- climate.set_operation_mode(self.hass, STATE_COOL, self.COOL_ENTITY)
+ common.set_operation_mode(self.hass, STATE_HEAT, self.HEAT_ENTITY)
+ common.set_operation_mode(self.hass, STATE_COOL, self.COOL_ENTITY)
self.hass.block_till_done()
self.hass.services.call('climate', SERVICE_TURN_OFF)
self.hass.block_till_done()
@@ -967,7 +968,7 @@ class TestClimateGenericThermostatTurnOnOff(unittest.TestCase):
def test_turn_off_when_off(self):
"""Test if climate.turn_off does nothing to a turned off device."""
- climate.set_operation_mode(self.hass, STATE_OFF)
+ common.set_operation_mode(self.hass, STATE_OFF)
self.hass.block_till_done()
self.hass.services.call('climate', SERVICE_TURN_OFF)
self.hass.block_till_done()
diff --git a/tests/components/climate/test_mqtt.py b/tests/components/climate/test_mqtt.py
index f46a23e4f97..c63dbf26690 100644
--- a/tests/components/climate/test_mqtt.py
+++ b/tests/components/climate/test_mqtt.py
@@ -6,14 +6,17 @@ from homeassistant.util.unit_system import (
METRIC_SYSTEM
)
from homeassistant.setup import setup_component
-from homeassistant.components import climate
+from homeassistant.components import climate, mqtt
from homeassistant.const import STATE_OFF, STATE_UNAVAILABLE
from homeassistant.components.climate import (
SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, SUPPORT_HOLD_MODE,
SUPPORT_AWAY_MODE, SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP)
+from homeassistant.components.mqtt.discovery import async_start
from tests.common import (get_test_home_assistant, mock_mqtt_component,
- fire_mqtt_message, mock_component)
+ async_fire_mqtt_message, fire_mqtt_message,
+ mock_component, MockConfigEntry)
+from tests.components.climate import common
ENTITY_CLIMATE = 'climate.test'
@@ -88,7 +91,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('operation_mode'))
self.assertEqual("off", state.state)
- climate.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('operation_mode'))
@@ -101,7 +104,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('operation_mode'))
self.assertEqual("off", state.state)
- climate.set_operation_mode(self.hass, "cool", ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, "cool", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("cool", state.attributes.get('operation_mode'))
@@ -119,7 +122,7 @@ class TestMQTTClimate(unittest.TestCase):
self.assertEqual("off", state.attributes.get('operation_mode'))
self.assertEqual("off", state.state)
- climate.set_operation_mode(self.hass, "cool", ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, "cool", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('operation_mode'))
@@ -146,7 +149,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('operation_mode'))
self.assertEqual("off", state.state)
- climate.set_operation_mode(self.hass, "on", ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, "on", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("on", state.attributes.get('operation_mode'))
@@ -157,7 +160,7 @@ class TestMQTTClimate(unittest.TestCase):
])
self.mock_publish.async_publish.reset_mock()
- climate.set_operation_mode(self.hass, "off", ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, "off", ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('operation_mode'))
@@ -174,7 +177,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("low", state.attributes.get('fan_mode'))
- climate.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_fan_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("low", state.attributes.get('fan_mode'))
@@ -188,7 +191,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("low", state.attributes.get('fan_mode'))
- climate.set_fan_mode(self.hass, 'high', ENTITY_CLIMATE)
+ common.set_fan_mode(self.hass, 'high', ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("low", state.attributes.get('fan_mode'))
@@ -209,7 +212,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("low", state.attributes.get('fan_mode'))
- climate.set_fan_mode(self.hass, 'high', ENTITY_CLIMATE)
+ common.set_fan_mode(self.hass, 'high', ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'fan-mode-topic', 'high', 0, False)
@@ -222,7 +225,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('swing_mode'))
- climate.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
+ common.set_swing_mode(self.hass, None, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('swing_mode'))
@@ -236,7 +239,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('swing_mode'))
- climate.set_swing_mode(self.hass, 'on', ENTITY_CLIMATE)
+ common.set_swing_mode(self.hass, 'on', ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('swing_mode'))
@@ -257,7 +260,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual("off", state.attributes.get('swing_mode'))
- climate.set_swing_mode(self.hass, 'on', ENTITY_CLIMATE)
+ common.set_swing_mode(self.hass, 'on', ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'swing-mode-topic', 'on', 0, False)
@@ -270,15 +273,15 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(21, state.attributes.get('temperature'))
- climate.set_operation_mode(self.hass, 'heat', ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, 'heat', ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('heat', state.attributes.get('operation_mode'))
self.mock_publish.async_publish.assert_called_once_with(
'mode-topic', 'heat', 0, False)
self.mock_publish.async_publish.reset_mock()
- climate.set_temperature(self.hass, temperature=47,
- entity_id=ENTITY_CLIMATE)
+ common.set_temperature(self.hass, temperature=47,
+ entity_id=ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(47, state.attributes.get('temperature'))
@@ -287,9 +290,9 @@ class TestMQTTClimate(unittest.TestCase):
# also test directly supplying the operation mode to set_temperature
self.mock_publish.async_publish.reset_mock()
- climate.set_temperature(self.hass, temperature=21,
- operation_mode="cool",
- entity_id=ENTITY_CLIMATE)
+ common.set_temperature(self.hass, temperature=21,
+ operation_mode="cool",
+ entity_id=ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('cool', state.attributes.get('operation_mode'))
@@ -308,10 +311,10 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(21, state.attributes.get('temperature'))
- climate.set_operation_mode(self.hass, 'heat', ENTITY_CLIMATE)
+ common.set_operation_mode(self.hass, 'heat', ENTITY_CLIMATE)
self.hass.block_till_done()
- climate.set_temperature(self.hass, temperature=47,
- entity_id=ENTITY_CLIMATE)
+ common.set_temperature(self.hass, temperature=47,
+ entity_id=ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(21, state.attributes.get('temperature'))
@@ -347,7 +350,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('away_mode'))
- climate.set_away_mode(self.hass, True, ENTITY_CLIMATE)
+ common.set_away_mode(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('away_mode'))
@@ -377,7 +380,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('away_mode'))
- climate.set_away_mode(self.hass, True, ENTITY_CLIMATE)
+ common.set_away_mode(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'away-mode-topic', 'AN', 0, False)
@@ -385,7 +388,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('on', state.attributes.get('away_mode'))
- climate.set_away_mode(self.hass, False, ENTITY_CLIMATE)
+ common.set_away_mode(self.hass, False, ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'away-mode-topic', 'AUS', 0, False)
@@ -401,7 +404,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(None, state.attributes.get('hold_mode'))
- climate.set_hold_mode(self.hass, 'on', ENTITY_CLIMATE)
+ common.set_hold_mode(self.hass, 'on', ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(None, state.attributes.get('hold_mode'))
@@ -422,7 +425,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual(None, state.attributes.get('hold_mode'))
- climate.set_hold_mode(self.hass, 'on', ENTITY_CLIMATE)
+ common.set_hold_mode(self.hass, 'on', ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'hold-topic', 'on', 0, False)
@@ -430,7 +433,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('on', state.attributes.get('hold_mode'))
- climate.set_hold_mode(self.hass, 'off', ENTITY_CLIMATE)
+ common.set_hold_mode(self.hass, 'off', ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'hold-topic', 'off', 0, False)
@@ -446,7 +449,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('aux_heat'))
- climate.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
+ common.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('aux_heat'))
@@ -472,7 +475,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('off', state.attributes.get('aux_heat'))
- climate.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
+ common.set_aux_heat(self.hass, True, ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'aux-topic', 'ON', 0, False)
@@ -480,7 +483,7 @@ class TestMQTTClimate(unittest.TestCase):
state = self.hass.states.get(ENTITY_CLIMATE)
self.assertEqual('on', state.attributes.get('aux_heat'))
- climate.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
+ common.set_aux_heat(self.hass, False, ENTITY_CLIMATE)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'aux-topic', 'OFF', 0, False)
@@ -649,3 +652,24 @@ class TestMQTTClimate(unittest.TestCase):
self.assertIsInstance(max_temp, float)
self.assertEqual(60, max_temp)
+
+
+async def test_discovery_removal_climate(hass, mqtt_mock, caplog):
+ """Test removal of discovered climate."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+ data = (
+ '{ "name": "Beer" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/climate/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('climate.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/climate/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('climate.beer')
+ assert state is None
diff --git a/tests/components/config/test_config_entries.py b/tests/components/config/test_config_entries.py
index 66d29aac757..67d7eebbfec 100644
--- a/tests/components/config/test_config_entries.py
+++ b/tests/components/config/test_config_entries.py
@@ -72,7 +72,7 @@ def test_get_entries(hass, client):
@asyncio.coroutine
def test_remove_entry(hass, client):
"""Test removing an entry via the API."""
- entry = MockConfigEntry(domain='demo')
+ entry = MockConfigEntry(domain='demo', state=core_ce.ENTRY_STATE_LOADED)
entry.add_to_hass(hass)
resp = yield from client.delete(
'/api/config/config_entries/entry/{}'.format(entry.entry_id))
@@ -206,6 +206,8 @@ def test_create_account(hass, client):
'title': 'Test Entry',
'type': 'create_entry',
'version': 1,
+ 'description': None,
+ 'description_placeholders': None,
}
@@ -266,6 +268,8 @@ def test_two_step_flow(hass, client):
'type': 'create_entry',
'title': 'user-title',
'version': 1,
+ 'description': None,
+ 'description_placeholders': None,
}
diff --git a/tests/components/config/test_device_registry.py b/tests/components/config/test_device_registry.py
index f8ea51cfdc8..87eb0fb2d6f 100644
--- a/tests/components/config/test_device_registry.py
+++ b/tests/components/config/test_device_registry.py
@@ -27,7 +27,6 @@ async def test_list_devices(hass, client, registry):
manufacturer='manufacturer', model='model')
registry.async_get_or_create(
config_entry_id='1234',
- connections={},
identifiers={('bridgeid', '1234')},
manufacturer='manufacturer', model='model',
via_hub=('bridgeid', '0123'))
diff --git a/tests/components/conftest.py b/tests/components/conftest.py
index 232405a632c..252d0b1d872 100644
--- a/tests/components/conftest.py
+++ b/tests/components/conftest.py
@@ -4,7 +4,9 @@ from unittest.mock import patch
import pytest
from homeassistant.setup import async_setup_component
-from homeassistant.components import websocket_api
+from homeassistant.components.websocket_api.http import URL
+from homeassistant.components.websocket_api.auth import (
+ TYPE_AUTH, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED)
from tests.common import MockUser, CLIENT_ID
@@ -14,41 +16,52 @@ def hass_ws_client(aiohttp_client):
"""Websocket client fixture connected to websocket server."""
async def create_client(hass, access_token=None):
"""Create a websocket client."""
- wapi = hass.components.websocket_api
assert await async_setup_component(hass, 'websocket_api')
client = await aiohttp_client(hass.http.app)
- patching = None
+ patches = []
- if access_token is not None:
- patching = patch('homeassistant.auth.AuthManager.active',
- return_value=True)
- patching.start()
+ if access_token is None:
+ patches.append(patch(
+ 'homeassistant.auth.AuthManager.active', return_value=False))
+ patches.append(patch(
+ 'homeassistant.auth.AuthManager.support_legacy',
+ return_value=True))
+ patches.append(patch(
+ 'homeassistant.components.websocket_api.auth.'
+ 'validate_password', return_value=True))
+ else:
+ patches.append(patch(
+ 'homeassistant.auth.AuthManager.active', return_value=True))
+ patches.append(patch(
+ 'homeassistant.components.http.auth.setup_auth'))
+
+ for p in patches:
+ p.start()
try:
- websocket = await client.ws_connect(wapi.URL)
+ websocket = await client.ws_connect(URL)
auth_resp = await websocket.receive_json()
+ assert auth_resp['type'] == TYPE_AUTH_REQUIRED
- if auth_resp['type'] == wapi.TYPE_AUTH_OK:
- assert access_token is None, \
- 'Access token given but no auth required'
- return websocket
-
- assert access_token is not None, \
- 'Access token required for fixture'
-
- await websocket.send_json({
- 'type': websocket_api.TYPE_AUTH,
- 'access_token': access_token
- })
+ if access_token is None:
+ await websocket.send_json({
+ 'type': TYPE_AUTH,
+ 'api_password': 'bla'
+ })
+ else:
+ await websocket.send_json({
+ 'type': TYPE_AUTH,
+ 'access_token': access_token
+ })
auth_ok = await websocket.receive_json()
- assert auth_ok['type'] == wapi.TYPE_AUTH_OK
+ assert auth_ok['type'] == TYPE_AUTH_OK
finally:
- if patching is not None:
- patching.stop()
+ for p in patches:
+ p.stop()
# wrap in client
websocket.client = client
diff --git a/tests/components/counter/common.py b/tests/components/counter/common.py
new file mode 100644
index 00000000000..36d09979d0d
--- /dev/null
+++ b/tests/components/counter/common.py
@@ -0,0 +1,52 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.const import ATTR_ENTITY_ID
+from homeassistant.components.counter import (
+ DOMAIN, SERVICE_DECREMENT, SERVICE_INCREMENT, SERVICE_RESET)
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def increment(hass, entity_id):
+ """Increment a counter."""
+ hass.add_job(async_increment, hass, entity_id)
+
+
+@callback
+@bind_hass
+def async_increment(hass, entity_id):
+ """Increment a counter."""
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_INCREMENT, {ATTR_ENTITY_ID: entity_id}))
+
+
+@bind_hass
+def decrement(hass, entity_id):
+ """Decrement a counter."""
+ hass.add_job(async_decrement, hass, entity_id)
+
+
+@callback
+@bind_hass
+def async_decrement(hass, entity_id):
+ """Decrement a counter."""
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_DECREMENT, {ATTR_ENTITY_ID: entity_id}))
+
+
+@bind_hass
+def reset(hass, entity_id):
+ """Reset a counter."""
+ hass.add_job(async_reset, hass, entity_id)
+
+
+@callback
+@bind_hass
+def async_reset(hass, entity_id):
+ """Reset a counter."""
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id}))
diff --git a/tests/components/counter/test_init.py b/tests/components/counter/test_init.py
index af36c1c8f95..929d96d4650 100644
--- a/tests/components/counter/test_init.py
+++ b/tests/components/counter/test_init.py
@@ -7,11 +7,11 @@ import logging
from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.counter import (
- DOMAIN, decrement, increment, reset, CONF_INITIAL, CONF_STEP, CONF_NAME,
- CONF_ICON)
+ DOMAIN, CONF_INITIAL, CONF_RESTORE, CONF_STEP, CONF_NAME, CONF_ICON)
from homeassistant.const import (ATTR_ICON, ATTR_FRIENDLY_NAME)
from tests.common import (get_test_home_assistant, mock_restore_cache)
+from tests.components.counter.common import decrement, increment, reset
_LOGGER = logging.getLogger(__name__)
@@ -55,6 +55,7 @@ class TestCounter(unittest.TestCase):
CONF_NAME: 'Hello World',
CONF_ICON: 'mdi:work',
CONF_INITIAL: 10,
+ CONF_RESTORE: False,
CONF_STEP: 5,
}
}
@@ -172,9 +173,12 @@ def test_initial_state_overrules_restore_state(hass):
yield from async_setup_component(hass, DOMAIN, {
DOMAIN: {
- 'test1': {},
+ 'test1': {
+ CONF_RESTORE: False,
+ },
'test2': {
CONF_INITIAL: 10,
+ CONF_RESTORE: False,
},
}})
@@ -187,6 +191,33 @@ def test_initial_state_overrules_restore_state(hass):
assert int(state.state) == 10
+@asyncio.coroutine
+def test_restore_state_overrules_initial_state(hass):
+ """Ensure states are restored on startup."""
+ mock_restore_cache(hass, (
+ State('counter.test1', '11'),
+ State('counter.test2', '-22'),
+ ))
+
+ hass.state = CoreState.starting
+
+ yield from async_setup_component(hass, DOMAIN, {
+ DOMAIN: {
+ 'test1': {},
+ 'test2': {
+ CONF_INITIAL: 10,
+ },
+ }})
+
+ state = hass.states.get('counter.test1')
+ assert state
+ assert int(state.state) == 11
+
+ state = hass.states.get('counter.test2')
+ assert state
+ assert int(state.state) == -22
+
+
@asyncio.coroutine
def test_no_initial_state_and_no_restore_state(hass):
"""Ensure that entity is create without initial and restore feature."""
diff --git a/tests/components/cover/__init__.py b/tests/components/cover/__init__.py
new file mode 100644
index 00000000000..aaaf6b237cd
--- /dev/null
+++ b/tests/components/cover/__init__.py
@@ -0,0 +1 @@
+"""Tests for the cover component."""
diff --git a/tests/components/cover/test_command_line.py b/tests/components/cover/test_command_line.py
index 346c3f94683..0e03539d58c 100644
--- a/tests/components/cover/test_command_line.py
+++ b/tests/components/cover/test_command_line.py
@@ -1,87 +1,75 @@
"""The tests the cover command line platform."""
-
import os
import tempfile
-import unittest
from unittest import mock
-from homeassistant.setup import setup_component
-import homeassistant.components.cover as cover
-from homeassistant.components.cover import (
- command_line as cmd_rs)
+import pytest
-from tests.common import get_test_home_assistant
+from homeassistant.components.cover import DOMAIN
+import homeassistant.components.cover.command_line as cmd_rs
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_OPEN_COVER,
+ SERVICE_STOP_COVER)
+from homeassistant.setup import async_setup_component
-class TestCommandCover(unittest.TestCase):
- """Test the cover command line platform."""
+@pytest.fixture
+def rs(hass):
+ """Return CommandCover instance."""
+ return cmd_rs.CommandCover(hass, 'foo', 'command_open', 'command_close',
+ 'command_stop', 'command_state', None)
- def setup_method(self, method):
- """Set up things to be run when tests are started."""
- self.hass = get_test_home_assistant()
- self.rs = cmd_rs.CommandCover(self.hass, 'foo',
- 'command_open', 'command_close',
- 'command_stop', 'command_state',
- None)
- def teardown_method(self, method):
- """Stop down everything that was started."""
- self.hass.stop()
+def test_should_poll_new(rs):
+ """Test the setting of polling."""
+ assert rs.should_poll is True
+ rs._command_state = None
+ assert rs.should_poll is False
- def test_should_poll(self):
- """Test the setting of polling."""
- self.assertTrue(self.rs.should_poll)
- self.rs._command_state = None
- self.assertFalse(self.rs.should_poll)
- def test_query_state_value(self):
- """Test with state value."""
- with mock.patch('subprocess.check_output') as mock_run:
- mock_run.return_value = b' foo bar '
- result = self.rs._query_state_value('runme')
- self.assertEqual('foo bar', result)
- self.assertEqual(mock_run.call_count, 1)
- self.assertEqual(
- mock_run.call_args, mock.call('runme', shell=True)
- )
+def test_query_state_value(rs):
+ """Test with state value."""
+ with mock.patch('subprocess.check_output') as mock_run:
+ mock_run.return_value = b' foo bar '
+ result = rs._query_state_value('runme')
+ assert 'foo bar' == result
+ assert mock_run.call_count == 1
+ assert mock_run.call_args == mock.call('runme', shell=True)
- def test_state_value(self):
- """Test with state value."""
- with tempfile.TemporaryDirectory() as tempdirname:
- path = os.path.join(tempdirname, 'cover_status')
- test_cover = {
- 'command_state': 'cat {}'.format(path),
- 'command_open': 'echo 1 > {}'.format(path),
- 'command_close': 'echo 1 > {}'.format(path),
- 'command_stop': 'echo 0 > {}'.format(path),
- 'value_template': '{{ value }}'
- }
- self.assertTrue(setup_component(self.hass, cover.DOMAIN, {
- 'cover': {
- 'platform': 'command_line',
- 'covers': {
- 'test': test_cover
- }
+
+async def test_state_value(hass):
+ """Test with state value."""
+ with tempfile.TemporaryDirectory() as tempdirname:
+ path = os.path.join(tempdirname, 'cover_status')
+ test_cover = {
+ 'command_state': 'cat {}'.format(path),
+ 'command_open': 'echo 1 > {}'.format(path),
+ 'command_close': 'echo 1 > {}'.format(path),
+ 'command_stop': 'echo 0 > {}'.format(path),
+ 'value_template': '{{ value }}'
+ }
+ assert await async_setup_component(hass, DOMAIN, {
+ 'cover': {
+ 'platform': 'command_line',
+ 'covers': {
+ 'test': test_cover
}
- }))
+ }
+ }) is True
- state = self.hass.states.get('cover.test')
- self.assertEqual('unknown', state.state)
+ assert 'unknown' == hass.states.get('cover.test').state
- cover.open_cover(self.hass, 'cover.test')
- self.hass.block_till_done()
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
+ assert 'open' == hass.states.get('cover.test').state
- state = self.hass.states.get('cover.test')
- self.assertEqual('open', state.state)
+ await hass.services.async_call(
+ DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
+ assert 'open' == hass.states.get('cover.test').state
- cover.close_cover(self.hass, 'cover.test')
- self.hass.block_till_done()
-
- state = self.hass.states.get('cover.test')
- self.assertEqual('open', state.state)
-
- cover.stop_cover(self.hass, 'cover.test')
- self.hass.block_till_done()
-
- state = self.hass.states.get('cover.test')
- self.assertEqual('closed', state.state)
+ await hass.services.async_call(
+ DOMAIN, SERVICE_STOP_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
+ assert 'closed' == hass.states.get('cover.test').state
diff --git a/tests/components/cover/test_demo.py b/tests/components/cover/test_demo.py
index 65aa9a9b9ef..011928f851a 100644
--- a/tests/components/cover/test_demo.py
+++ b/tests/components/cover/test_demo.py
@@ -1,158 +1,181 @@
"""The tests for the Demo cover platform."""
-import unittest
from datetime import timedelta
+
+import pytest
+
+from homeassistant.components.cover import (
+ ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT,
+ SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION,
+ SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER,
+ SERVICE_STOP_COVER_TILT)
+from homeassistant.setup import async_setup_component
import homeassistant.util.dt as dt_util
-from homeassistant.setup import setup_component
-from homeassistant.components import cover
-from tests.common import get_test_home_assistant, fire_time_changed
+from tests.common import assert_setup_component, async_fire_time_changed
+CONFIG = {'cover': {'platform': 'demo'}}
ENTITY_COVER = 'cover.living_room_window'
-class TestCoverDemo(unittest.TestCase):
- """Test the Demo cover."""
+@pytest.fixture
+async def setup_comp(hass):
+ """Set up demo cover component."""
+ with assert_setup_component(1, DOMAIN):
+ await async_setup_component(hass, DOMAIN, CONFIG)
- def setUp(self): # pylint: disable=invalid-name
- """Set up things to be run when tests are started."""
- self.hass = get_test_home_assistant()
- self.assertTrue(setup_component(self.hass, cover.DOMAIN, {'cover': {
- 'platform': 'demo',
- }}))
- def tearDown(self): # pylint: disable=invalid-name
- """Stop down everything that was started."""
- self.hass.stop()
+async def test_supported_features(hass, setup_comp):
+ """Test cover supported features."""
+ state = hass.states.get('cover.garage_door')
+ assert 3 == state.attributes.get('supported_features')
+ state = hass.states.get('cover.kitchen_window')
+ assert 11 == state.attributes.get('supported_features')
+ state = hass.states.get('cover.hall_window')
+ assert 15 == state.attributes.get('supported_features')
+ state = hass.states.get('cover.living_room_window')
+ assert 255 == state.attributes.get('supported_features')
- def test_supported_features(self):
- """Test cover supported features."""
- state = self.hass.states.get('cover.garage_door')
- self.assertEqual(3, state.attributes.get('supported_features'))
- state = self.hass.states.get('cover.kitchen_window')
- self.assertEqual(11, state.attributes.get('supported_features'))
- state = self.hass.states.get('cover.hall_window')
- self.assertEqual(15, state.attributes.get('supported_features'))
- state = self.hass.states.get('cover.living_room_window')
- self.assertEqual(255, state.attributes.get('supported_features'))
- def test_close_cover(self):
- """Test closing the cover."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(state.state, 'open')
- self.assertEqual(70, state.attributes.get('current_position'))
- cover.close_cover(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(state.state, 'closing')
- for _ in range(7):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
+async def test_close_cover(hass, setup_comp):
+ """Test closing the cover."""
+ state = hass.states.get(ENTITY_COVER)
+ assert state.state == 'open'
+ assert 70 == state.attributes.get('current_position')
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(state.state, 'closed')
- self.assertEqual(0, state.attributes.get('current_position'))
-
- def test_open_cover(self):
- """Test opening the cover."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(state.state, 'open')
- self.assertEqual(70, state.attributes.get('current_position'))
- cover.open_cover(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(state.state, 'opening')
- for _ in range(7):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(state.state, 'open')
- self.assertEqual(100, state.attributes.get('current_position'))
-
- def test_set_cover_position(self):
- """Test moving the cover to a specific position."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(70, state.attributes.get('current_position'))
- cover.set_cover_position(self.hass, 10, ENTITY_COVER)
- self.hass.block_till_done()
- for _ in range(6):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(10, state.attributes.get('current_position'))
-
- def test_stop_cover(self):
- """Test stopping the cover."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(70, state.attributes.get('current_position'))
- cover.open_cover(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
+ await hass.services.async_call(
+ DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ state = hass.states.get(ENTITY_COVER)
+ assert state.state == 'closing'
+ for _ in range(7):
future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
- cover.stop_cover(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
- fire_time_changed(self.hass, future)
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(80, state.attributes.get('current_position'))
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
- def test_close_cover_tilt(self):
- """Test closing the cover tilt."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(50, state.attributes.get('current_tilt_position'))
- cover.close_cover_tilt(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
- for _ in range(7):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
+ state = hass.states.get(ENTITY_COVER)
+ assert state.state == 'closed'
+ assert 0 == state.attributes.get('current_position')
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(0, state.attributes.get('current_tilt_position'))
- def test_open_cover_tilt(self):
- """Test opening the cover tilt."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(50, state.attributes.get('current_tilt_position'))
- cover.open_cover_tilt(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
- for _ in range(7):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(100, state.attributes.get('current_tilt_position'))
-
- def test_set_cover_tilt_position(self):
- """Test moving the cover til to a specific position."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(50, state.attributes.get('current_tilt_position'))
- cover.set_cover_tilt_position(self.hass, 90, ENTITY_COVER)
- self.hass.block_till_done()
- for _ in range(7):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(90, state.attributes.get('current_tilt_position'))
-
- def test_stop_cover_tilt(self):
- """Test stopping the cover tilt."""
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(50, state.attributes.get('current_tilt_position'))
- cover.close_cover_tilt(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
+async def test_open_cover(hass, setup_comp):
+ """Test opening the cover."""
+ state = hass.states.get(ENTITY_COVER)
+ assert state.state == 'open'
+ assert 70 == state.attributes.get('current_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ state = hass.states.get(ENTITY_COVER)
+ assert state.state == 'opening'
+ for _ in range(7):
future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
- cover.stop_cover_tilt(self.hass, ENTITY_COVER)
- self.hass.block_till_done()
- fire_time_changed(self.hass, future)
- state = self.hass.states.get(ENTITY_COVER)
- self.assertEqual(40, state.attributes.get('current_tilt_position'))
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(ENTITY_COVER)
+ assert state.state == 'open'
+ assert 100 == state.attributes.get('current_position')
+
+
+async def test_set_cover_position(hass, setup_comp):
+ """Test moving the cover to a specific position."""
+ state = hass.states.get(ENTITY_COVER)
+ assert 70 == state.attributes.get('current_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_SET_COVER_POSITION,
+ {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_POSITION: 10}, blocking=True)
+ for _ in range(6):
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(ENTITY_COVER)
+ assert 10 == state.attributes.get('current_position')
+
+
+async def test_stop_cover(hass, setup_comp):
+ """Test stopping the cover."""
+ state = hass.states.get(ENTITY_COVER)
+ assert 70 == state.attributes.get('current_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+ await hass.services.async_call(
+ DOMAIN, SERVICE_STOP_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+ state = hass.states.get(ENTITY_COVER)
+ assert 80 == state.attributes.get('current_position')
+
+
+async def test_close_cover_tilt(hass, setup_comp):
+ """Test closing the cover tilt."""
+ state = hass.states.get(ENTITY_COVER)
+ assert 50 == state.attributes.get('current_tilt_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ for _ in range(7):
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(ENTITY_COVER)
+ assert 0 == state.attributes.get('current_tilt_position')
+
+
+async def test_open_cover_tilt(hass, setup_comp):
+ """Test opening the cover tilt."""
+ state = hass.states.get(ENTITY_COVER)
+ assert 50 == state.attributes.get('current_tilt_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ for _ in range(7):
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(ENTITY_COVER)
+ assert 100 == state.attributes.get('current_tilt_position')
+
+
+async def test_set_cover_tilt_position(hass, setup_comp):
+ """Test moving the cover til to a specific position."""
+ state = hass.states.get(ENTITY_COVER)
+ assert 50 == state.attributes.get('current_tilt_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_SET_COVER_TILT_POSITION,
+ {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_TILT_POSITION: 90}, blocking=True)
+ for _ in range(7):
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(ENTITY_COVER)
+ assert 90 == state.attributes.get('current_tilt_position')
+
+
+async def test_stop_cover_tilt(hass, setup_comp):
+ """Test stopping the cover tilt."""
+ state = hass.states.get(ENTITY_COVER)
+ assert 50 == state.attributes.get('current_tilt_position')
+ await hass.services.async_call(
+ DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+ await hass.services.async_call(
+ DOMAIN, SERVICE_STOP_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+ state = hass.states.get(ENTITY_COVER)
+ assert 40 == state.attributes.get('current_tilt_position')
diff --git a/tests/components/cover/test_group.py b/tests/components/cover/test_group.py
index 028845983a0..2211c8c77bc 100644
--- a/tests/components/cover/test_group.py
+++ b/tests/components/cover/test_group.py
@@ -1,19 +1,23 @@
"""The tests for the group cover platform."""
-
-import unittest
from datetime import timedelta
-import homeassistant.util.dt as dt_util
-from homeassistant import setup
-from homeassistant.components import cover
+import pytest
+
from homeassistant.components.cover import (
- ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, DOMAIN)
+ ATTR_CURRENT_POSITION, ATTR_CURRENT_TILT_POSITION, ATTR_POSITION,
+ ATTR_TILT_POSITION, DOMAIN)
from homeassistant.components.cover.group import DEFAULT_NAME
from homeassistant.const import (
- ATTR_ASSUMED_STATE, ATTR_FRIENDLY_NAME, ATTR_SUPPORTED_FEATURES,
- CONF_ENTITIES, STATE_OPEN, STATE_CLOSED)
-from tests.common import (
- assert_setup_component, get_test_home_assistant, fire_time_changed)
+ ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME,
+ ATTR_SUPPORTED_FEATURES, CONF_ENTITIES,
+ SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT,
+ SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION,
+ SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER,
+ SERVICE_STOP_COVER_TILT, STATE_OPEN, STATE_CLOSED)
+from homeassistant.setup import async_setup_component
+import homeassistant.util.dt as dt_util
+
+from tests.common import assert_setup_component, async_fire_time_changed
COVER_GROUP = 'cover.cover_group'
DEMO_COVER = 'cover.kitchen_window'
@@ -31,320 +35,301 @@ CONFIG = {
}
-class TestMultiCover(unittest.TestCase):
- """Test the group cover platform."""
+@pytest.fixture
+async def setup_comp(hass):
+ """Set up group cover component."""
+ with assert_setup_component(2, DOMAIN):
+ await async_setup_component(hass, DOMAIN, CONFIG)
- def setUp(self):
- """Set up things to be run when tests are started."""
- self.hass = get_test_home_assistant()
- def tearDown(self):
- """Stop down everything that was started."""
- self.hass.stop()
+async def test_attributes(hass):
+ """Test handling of state attributes."""
+ config = {DOMAIN: {'platform': 'group', CONF_ENTITIES: [
+ DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT]}}
- def test_attributes(self):
- """Test handling of state attributes."""
- config = {DOMAIN: {'platform': 'group', CONF_ENTITIES: [
- DEMO_COVER, DEMO_COVER_POS, DEMO_COVER_TILT, DEMO_TILT]}}
+ with assert_setup_component(1, DOMAIN):
+ await async_setup_component(hass, DOMAIN, config)
- with assert_setup_component(1, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, config)
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_CLOSED
+ assert state.attributes.get(ATTR_FRIENDLY_NAME) == DEFAULT_NAME
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 0
+ assert state.attributes.get(ATTR_CURRENT_POSITION) is None
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) is None
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_CLOSED)
- self.assertEqual(attr.get(ATTR_FRIENDLY_NAME), DEFAULT_NAME)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 0)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None)
+ # Add Entity that supports open / close / stop
+ hass.states.async_set(
+ DEMO_COVER, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 11})
+ await hass.async_block_till_done()
- # Add Entity that supports open / close / stop
- self.hass.states.set(
- DEMO_COVER, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 11})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 11
+ assert state.attributes.get(ATTR_CURRENT_POSITION) is None
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) is None
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 11)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None)
+ # Add Entity that supports set_cover_position
+ hass.states.async_set(
+ DEMO_COVER_POS, STATE_OPEN,
+ {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 70})
+ await hass.async_block_till_done()
- # Add Entity that supports set_cover_position
- self.hass.states.set(
- DEMO_COVER_POS, STATE_OPEN,
- {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 70})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 15
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 70
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) is None
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 15)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 70)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None)
+ # Add Entity that supports open tilt / close tilt / stop tilt
+ hass.states.async_set(
+ DEMO_TILT, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 112})
+ await hass.async_block_till_done()
- # Add Entity that supports open tilt / close tilt / stop tilt
- self.hass.states.set(
- DEMO_TILT, STATE_OPEN, {ATTR_SUPPORTED_FEATURES: 112})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 127
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 70
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) is None
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 127)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 70)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None)
+ # Add Entity that supports set_tilt_position
+ hass.states.async_set(
+ DEMO_COVER_TILT, STATE_OPEN,
+ {ATTR_SUPPORTED_FEATURES: 128, ATTR_CURRENT_TILT_POSITION: 60})
+ await hass.async_block_till_done()
- # Add Entity that supports set_tilt_position
- self.hass.states.set(
- DEMO_COVER_TILT, STATE_OPEN,
- {ATTR_SUPPORTED_FEATURES: 128, ATTR_CURRENT_TILT_POSITION: 60})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 255
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 70
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 60
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 255)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 70)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 60)
+ # ### Test assumed state ###
+ # ##########################
- # ### Test assumed state ###
- # ##########################
+ # For covers
+ hass.states.async_set(
+ DEMO_COVER, STATE_OPEN,
+ {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 100})
+ await hass.async_block_till_done()
- # For covers
- self.hass.states.set(
- DEMO_COVER, STATE_OPEN,
- {ATTR_SUPPORTED_FEATURES: 4, ATTR_CURRENT_POSITION: 100})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is True
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 244
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 100
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 60
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), True)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 244)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), 100)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 60)
+ hass.states.async_remove(DEMO_COVER)
+ hass.states.async_remove(DEMO_COVER_POS)
+ await hass.async_block_till_done()
- self.hass.states.remove(DEMO_COVER)
- self.hass.block_till_done()
- self.hass.states.remove(DEMO_COVER_POS)
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 240
+ assert state.attributes.get(ATTR_CURRENT_POSITION) is None
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 60
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 240)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 60)
+ # For tilts
+ hass.states.async_set(
+ DEMO_TILT, STATE_OPEN,
+ {ATTR_SUPPORTED_FEATURES: 128, ATTR_CURRENT_TILT_POSITION: 100})
+ await hass.async_block_till_done()
- # For tilts
- self.hass.states.set(
- DEMO_TILT, STATE_OPEN,
- {ATTR_SUPPORTED_FEATURES: 128, ATTR_CURRENT_TILT_POSITION: 100})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is True
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 128
+ assert state.attributes.get(ATTR_CURRENT_POSITION) is None
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 100
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), True)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 128)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), 100)
+ hass.states.async_remove(DEMO_COVER_TILT)
+ hass.states.async_set(DEMO_TILT, STATE_CLOSED)
+ await hass.async_block_till_done()
- self.hass.states.remove(DEMO_COVER_TILT)
- self.hass.states.set(DEMO_TILT, STATE_CLOSED)
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_CLOSED
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is None
+ assert state.attributes.get(ATTR_SUPPORTED_FEATURES) == 0
+ assert state.attributes.get(ATTR_CURRENT_POSITION) is None
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) is None
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(state.state, STATE_CLOSED)
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), None)
- self.assertEqual(attr.get(ATTR_SUPPORTED_FEATURES), 0)
- self.assertEqual(attr.get(ATTR_CURRENT_POSITION), None)
- self.assertEqual(attr.get(ATTR_CURRENT_TILT_POSITION), None)
+ hass.states.async_set(
+ DEMO_TILT, STATE_CLOSED, {ATTR_ASSUMED_STATE: True})
+ await hass.async_block_till_done()
- self.hass.states.set(
- DEMO_TILT, STATE_CLOSED, {ATTR_ASSUMED_STATE: True})
- self.hass.block_till_done()
+ state = hass.states.get(COVER_GROUP)
+ assert state.attributes.get(ATTR_ASSUMED_STATE) is True
- state = self.hass.states.get(COVER_GROUP)
- attr = state.attributes
- self.assertEqual(attr.get(ATTR_ASSUMED_STATE), True)
- def test_open_covers(self):
- """Test open cover function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
-
- cover.open_cover(self.hass, COVER_GROUP)
- self.hass.block_till_done()
- for _ in range(10):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 100)
-
- self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_OPEN)
- self.assertEqual(self.hass.states.get(DEMO_COVER_POS)
- .attributes.get(ATTR_CURRENT_POSITION), 100)
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_POSITION), 100)
-
- def test_close_covers(self):
- """Test close cover function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
-
- cover.close_cover(self.hass, COVER_GROUP)
- self.hass.block_till_done()
- for _ in range(10):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_CLOSED)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 0)
-
- self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_CLOSED)
- self.assertEqual(self.hass.states.get(DEMO_COVER_POS)
- .attributes.get(ATTR_CURRENT_POSITION), 0)
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_POSITION), 0)
-
- def test_stop_covers(self):
- """Test stop cover function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
-
- cover.open_cover(self.hass, COVER_GROUP)
- self.hass.block_till_done()
+async def test_open_covers(hass, setup_comp):
+ """Test open cover function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ for _ in range(10):
future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
- cover.stop_cover(self.hass, COVER_GROUP)
- self.hass.block_till_done()
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 100
+
+ assert hass.states.get(DEMO_COVER).state == STATE_OPEN
+ assert hass.states.get(DEMO_COVER_POS) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 100
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 100
+
+
+async def test_close_covers(hass, setup_comp):
+ """Test close cover function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ for _ in range(10):
future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 100)
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_CLOSED
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 0
- self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_OPEN)
- self.assertEqual(self.hass.states.get(DEMO_COVER_POS)
- .attributes.get(ATTR_CURRENT_POSITION), 20)
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_POSITION), 80)
+ assert hass.states.get(DEMO_COVER).state == STATE_CLOSED
+ assert hass.states.get(DEMO_COVER_POS) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 0
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 0
- def test_set_cover_position(self):
- """Test set cover position function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
- cover.set_cover_position(self.hass, 50, COVER_GROUP)
- self.hass.block_till_done()
- for _ in range(4):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
+async def test_stop_covers(hass, setup_comp):
+ """Test stop cover function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_POSITION), 50)
+ await hass.services.async_call(
+ DOMAIN, SERVICE_STOP_COVER,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
- self.assertEqual(self.hass.states.get(DEMO_COVER).state, STATE_CLOSED)
- self.assertEqual(self.hass.states.get(DEMO_COVER_POS)
- .attributes.get(ATTR_CURRENT_POSITION), 50)
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_POSITION), 50)
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 100
- def test_open_tilts(self):
- """Test open tilt function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
+ assert hass.states.get(DEMO_COVER).state == STATE_OPEN
+ assert hass.states.get(DEMO_COVER_POS) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 20
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 80
- cover.open_cover_tilt(self.hass, COVER_GROUP)
- self.hass.block_till_done()
- for _ in range(5):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 100)
-
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_TILT_POSITION), 100)
-
- def test_close_tilts(self):
- """Test close tilt function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
-
- cover.close_cover_tilt(self.hass, COVER_GROUP)
- self.hass.block_till_done()
- for _ in range(5):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
-
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 0)
-
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_TILT_POSITION), 0)
-
- def test_stop_tilts(self):
- """Test stop tilts function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
-
- cover.open_cover_tilt(self.hass, COVER_GROUP)
- self.hass.block_till_done()
+async def test_set_cover_position(hass, setup_comp):
+ """Test set cover position function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_SET_COVER_POSITION,
+ {ATTR_ENTITY_ID: COVER_GROUP, ATTR_POSITION: 50}, blocking=True)
+ for _ in range(4):
future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
- cover.stop_cover_tilt(self.hass, COVER_GROUP)
- self.hass.block_till_done()
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_POSITION) == 50
+
+ assert hass.states.get(DEMO_COVER).state == STATE_CLOSED
+ assert hass.states.get(DEMO_COVER_POS) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 50
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_POSITION) == 50
+
+
+async def test_open_tilts(hass, setup_comp):
+ """Test open tilt function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ for _ in range(5):
future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 60)
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 100
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_TILT_POSITION), 60)
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_TILT_POSITION) == 100
- def test_set_tilt_positions(self):
- """Test set tilt position function."""
- with assert_setup_component(2, DOMAIN):
- assert setup.setup_component(self.hass, DOMAIN, CONFIG)
- cover.set_cover_tilt_position(self.hass, 80, COVER_GROUP)
- self.hass.block_till_done()
- for _ in range(3):
- future = dt_util.utcnow() + timedelta(seconds=1)
- fire_time_changed(self.hass, future)
- self.hass.block_till_done()
+async def test_close_tilts(hass, setup_comp):
+ """Test close tilt function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ for _ in range(5):
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
- state = self.hass.states.get(COVER_GROUP)
- self.assertEqual(state.state, STATE_OPEN)
- self.assertEqual(state.attributes.get(ATTR_CURRENT_TILT_POSITION), 80)
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 0
- self.assertEqual(self.hass.states.get(DEMO_COVER_TILT)
- .attributes.get(ATTR_CURRENT_TILT_POSITION), 80)
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_TILT_POSITION) == 0
+
+
+async def test_stop_tilts(hass, setup_comp):
+ """Test stop tilts function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ await hass.services.async_call(
+ DOMAIN, SERVICE_STOP_COVER_TILT,
+ {ATTR_ENTITY_ID: COVER_GROUP}, blocking=True)
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 60
+
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_TILT_POSITION) == 60
+
+
+async def test_set_tilt_positions(hass, setup_comp):
+ """Test set tilt position function."""
+ await hass.services.async_call(
+ DOMAIN, SERVICE_SET_COVER_TILT_POSITION,
+ {ATTR_ENTITY_ID: COVER_GROUP, ATTR_TILT_POSITION: 80}, blocking=True)
+ for _ in range(3):
+ future = dt_util.utcnow() + timedelta(seconds=1)
+ async_fire_time_changed(hass, future)
+ await hass.async_block_till_done()
+
+ state = hass.states.get(COVER_GROUP)
+ assert state.state == STATE_OPEN
+ assert state.attributes.get(ATTR_CURRENT_TILT_POSITION) == 80
+
+ assert hass.states.get(DEMO_COVER_TILT) \
+ .attributes.get(ATTR_CURRENT_TILT_POSITION) == 80
diff --git a/tests/components/cover/test_mqtt.py b/tests/components/cover/test_mqtt.py
index ad68c2416ca..355f620520a 100644
--- a/tests/components/cover/test_mqtt.py
+++ b/tests/components/cover/test_mqtt.py
@@ -1,14 +1,21 @@
"""The tests for the MQTT cover platform."""
import unittest
-from homeassistant.setup import setup_component
-from homeassistant.const import STATE_OPEN, STATE_CLOSED, STATE_UNKNOWN, \
- STATE_UNAVAILABLE, ATTR_ASSUMED_STATE
-import homeassistant.components.cover as cover
+from homeassistant.components import cover, mqtt
+from homeassistant.components.cover import (ATTR_POSITION, ATTR_TILT_POSITION)
from homeassistant.components.cover.mqtt import MqttCover
+from homeassistant.components.mqtt.discovery import async_start
+from homeassistant.const import (
+ ATTR_ASSUMED_STATE, ATTR_ENTITY_ID,
+ SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT, SERVICE_OPEN_COVER,
+ SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION,
+ SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER,
+ STATE_CLOSED, STATE_OPEN, STATE_UNAVAILABLE, STATE_UNKNOWN)
+from homeassistant.setup import setup_component, async_setup_component
from tests.common import (
- get_test_home_assistant, mock_mqtt_component, fire_mqtt_message)
+ get_test_home_assistant, mock_mqtt_component, async_fire_mqtt_message,
+ fire_mqtt_message, MockConfigEntry, async_mock_mqtt_component)
class TestCoverMQTT(unittest.TestCase):
@@ -115,7 +122,9 @@ class TestCoverMQTT(unittest.TestCase):
self.assertEqual(STATE_UNKNOWN, state.state)
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
- cover.open_cover(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -124,7 +133,9 @@ class TestCoverMQTT(unittest.TestCase):
state = self.hass.states.get('cover.test')
self.assertEqual(STATE_OPEN, state.state)
- cover.close_cover(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -147,7 +158,9 @@ class TestCoverMQTT(unittest.TestCase):
state = self.hass.states.get('cover.test')
self.assertEqual(STATE_UNKNOWN, state.state)
- cover.open_cover(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -170,7 +183,9 @@ class TestCoverMQTT(unittest.TestCase):
state = self.hass.states.get('cover.test')
self.assertEqual(STATE_UNKNOWN, state.state)
- cover.close_cover(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -193,7 +208,9 @@ class TestCoverMQTT(unittest.TestCase):
state = self.hass.states.get('cover.test')
self.assertEqual(STATE_UNKNOWN, state.state)
- cover.stop_cover(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_STOP_COVER,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -295,7 +312,9 @@ class TestCoverMQTT(unittest.TestCase):
}
}))
- cover.set_cover_position(self.hass, 100, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_SET_COVER_POSITION,
+ {ATTR_ENTITY_ID: 'cover.test', ATTR_POSITION: 100}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -316,7 +335,9 @@ class TestCoverMQTT(unittest.TestCase):
}
}))
- cover.set_cover_position(self.hass, 62, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_SET_COVER_POSITION,
+ {ATTR_ENTITY_ID: 'cover.test', ATTR_POSITION: 62}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -401,14 +422,18 @@ class TestCoverMQTT(unittest.TestCase):
}
}))
- cover.open_cover_tilt(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'tilt-command-topic', 100, 0, False)
self.mock_publish.async_publish.reset_mock()
- cover.close_cover_tilt(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -433,14 +458,18 @@ class TestCoverMQTT(unittest.TestCase):
}
}))
- cover.open_cover_tilt(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'tilt-command-topic', 400, 0, False)
self.mock_publish.async_publish.reset_mock()
- cover.close_cover_tilt(self.hass, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: 'cover.test'}, blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -540,7 +569,10 @@ class TestCoverMQTT(unittest.TestCase):
}
}))
- cover.set_cover_tilt_position(self.hass, 50, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_SET_COVER_TILT_POSITION,
+ {ATTR_ENTITY_ID: 'cover.test', ATTR_TILT_POSITION: 50},
+ blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -567,7 +599,10 @@ class TestCoverMQTT(unittest.TestCase):
}
}))
- cover.set_cover_tilt_position(self.hass, 50, 'cover.test')
+ self.hass.services.call(
+ cover.DOMAIN, SERVICE_SET_COVER_TILT_POSITION,
+ {ATTR_ENTITY_ID: 'cover.test', ATTR_TILT_POSITION: 50},
+ blocking=True)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -579,7 +614,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 100, 0, 0, 100, False, False, None, None)
+ False, None, 100, 0, 0, 100, False, False, None, None, None,
+ None)
self.assertEqual(44, mqtt_cover.find_percentage_in_range(44))
@@ -589,7 +625,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 180, 80, 80, 180, False, False, None, None)
+ False, None, 180, 80, 80, 180, False, False, None, None, None,
+ None)
self.assertEqual(40, mqtt_cover.find_percentage_in_range(120))
@@ -599,7 +636,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 100, 0, 0, 100, False, True, None, None)
+ False, None, 100, 0, 0, 100, False, True, None, None, None,
+ None)
self.assertEqual(56, mqtt_cover.find_percentage_in_range(44))
@@ -609,7 +647,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 180, 80, 80, 180, False, True, None, None)
+ False, None, 180, 80, 80, 180, False, True, None, None, None,
+ None)
self.assertEqual(60, mqtt_cover.find_percentage_in_range(120))
@@ -619,7 +658,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 100, 0, 0, 100, False, False, None, None)
+ False, None, 100, 0, 0, 100, False, False, None, None, None,
+ None)
self.assertEqual(44, mqtt_cover.find_in_range_from_percent(44))
@@ -629,7 +669,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 180, 80, 80, 180, False, False, None, None)
+ False, None, 180, 80, 80, 180, False, False, None, None, None,
+ None)
self.assertEqual(120, mqtt_cover.find_in_range_from_percent(40))
@@ -639,7 +680,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 100, 0, 0, 100, False, True, None, None)
+ False, None, 100, 0, 0, 100, False, True, None, None, None,
+ None)
self.assertEqual(44, mqtt_cover.find_in_range_from_percent(56))
@@ -649,7 +691,8 @@ class TestCoverMQTT(unittest.TestCase):
'cover.test', 'state-topic', 'command-topic', None,
'tilt-command-topic', 'tilt-status-topic', 0, False,
'OPEN', 'CLOSE', 'OPEN', 'CLOSE', 'STOP', None, None,
- False, None, 180, 80, 80, 180, False, True, None, None)
+ False, None, 180, 80, 80, 180, False, True, None, None, None,
+ None)
self.assertEqual(120, mqtt_cover.find_in_range_from_percent(60))
@@ -722,3 +765,48 @@ class TestCoverMQTT(unittest.TestCase):
state = self.hass.states.get('cover.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
+
+
+async def test_discovery_removal_cover(hass, mqtt_mock, caplog):
+ """Test removal of discovered cover."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+ data = (
+ '{ "name": "Beer",'
+ ' "command_topic": "test_topic" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/cover/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('cover.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/cover/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('cover.beer')
+ assert state is None
+
+
+async def test_unique_id(hass):
+ """Test unique_id option only creates one cover per id."""
+ await async_mock_mqtt_component(hass)
+ assert await async_setup_component(hass, cover.DOMAIN, {
+ cover.DOMAIN: [{
+ 'platform': 'mqtt',
+ 'name': 'Test 1',
+ 'state_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }, {
+ 'platform': 'mqtt',
+ 'name': 'Test 2',
+ 'state_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }]
+ })
+
+ async_fire_mqtt_message(hass, 'test-topic', 'payload')
+ await hass.async_block_till_done()
+
+ assert len(hass.states.async_entity_ids(cover.DOMAIN)) == 1
diff --git a/tests/components/cover/test_template.py b/tests/components/cover/test_template.py
index b786b463dad..3c820f1a0ac 100644
--- a/tests/components/cover/test_template.py
+++ b/tests/components/cover/test_template.py
@@ -1,17 +1,24 @@
"""The tests the cover command line platform."""
-
import logging
import unittest
-from homeassistant.core import callback
from homeassistant import setup
-import homeassistant.components.cover as cover
-from homeassistant.const import STATE_OPEN, STATE_CLOSED
+from homeassistant.core import callback
+from homeassistant.components.cover import (
+ ATTR_POSITION, ATTR_TILT_POSITION, DOMAIN)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_CLOSE_COVER, SERVICE_CLOSE_COVER_TILT,
+ SERVICE_OPEN_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_SET_COVER_POSITION,
+ SERVICE_SET_COVER_TILT_POSITION, SERVICE_STOP_COVER,
+ STATE_CLOSED, STATE_OPEN)
from tests.common import (
get_test_home_assistant, assert_setup_component)
+
_LOGGER = logging.getLogger(__name__)
+ENTITY_COVER = 'cover.test_template_cover'
+
class TestTemplateCover(unittest.TestCase):
"""Test the cover command line platform."""
@@ -362,7 +369,9 @@ class TestTemplateCover(unittest.TestCase):
state = self.hass.states.get('cover.test_template_cover')
assert state.state == STATE_CLOSED
- cover.open_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -398,10 +407,14 @@ class TestTemplateCover(unittest.TestCase):
state = self.hass.states.get('cover.test_template_cover')
assert state.state == STATE_OPEN
- cover.close_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
- cover.stop_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_STOP_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
assert len(self.calls) == 2
@@ -445,18 +458,23 @@ class TestTemplateCover(unittest.TestCase):
state = self.hass.states.get('cover.test_template_cover')
assert state.state == STATE_OPEN
- cover.open_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_position') == 100.0
- cover.close_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_position') == 0.0
- cover.set_cover_position(self.hass, 25,
- 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_SET_COVER_POSITION,
+ {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_POSITION: 25}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_position') == 25.0
@@ -490,8 +508,10 @@ class TestTemplateCover(unittest.TestCase):
self.hass.start()
self.hass.block_till_done()
- cover.set_cover_tilt_position(self.hass, 42,
- 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_SET_COVER_TILT_POSITION,
+ {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_TILT_POSITION: 42},
+ blocking=True)
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -525,7 +545,9 @@ class TestTemplateCover(unittest.TestCase):
self.hass.start()
self.hass.block_till_done()
- cover.open_cover_tilt(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -559,7 +581,9 @@ class TestTemplateCover(unittest.TestCase):
self.hass.start()
self.hass.block_till_done()
- cover.close_cover_tilt(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -585,18 +609,23 @@ class TestTemplateCover(unittest.TestCase):
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_position') is None
- cover.set_cover_position(self.hass, 42,
- 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_SET_COVER_POSITION,
+ {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_POSITION: 42}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_position') == 42.0
- cover.close_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_CLOSE_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.state == STATE_CLOSED
- cover.open_cover(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_OPEN_COVER,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.state == STATE_OPEN
@@ -627,18 +656,24 @@ class TestTemplateCover(unittest.TestCase):
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_tilt_position') is None
- cover.set_cover_tilt_position(self.hass, 42,
- 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_SET_COVER_TILT_POSITION,
+ {ATTR_ENTITY_ID: ENTITY_COVER, ATTR_TILT_POSITION: 42},
+ blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_tilt_position') == 42.0
- cover.close_cover_tilt(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_CLOSE_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_tilt_position') == 0.0
- cover.open_cover_tilt(self.hass, 'cover.test_template_cover')
+ self.hass.services.call(
+ DOMAIN, SERVICE_OPEN_COVER_TILT,
+ {ATTR_ENTITY_ID: ENTITY_COVER}, blocking=True)
self.hass.block_till_done()
state = self.hass.states.get('cover.test_template_cover')
assert state.attributes.get('current_tilt_position') == 100.0
diff --git a/tests/components/emulated_hue/test_hue_api.py b/tests/components/emulated_hue/test_hue_api.py
index 3920a45ddf6..8582f5b38cf 100644
--- a/tests/components/emulated_hue/test_hue_api.py
+++ b/tests/components/emulated_hue/test_hue_api.py
@@ -1,6 +1,7 @@
"""The tests for the emulated Hue component."""
import asyncio
import json
+from ipaddress import ip_address
from unittest.mock import patch
from aiohttp.hdrs import CONTENT_TYPE
@@ -484,3 +485,12 @@ def perform_put_light_state(hass_hue, client, entity_id, is_on,
yield from hass_hue.async_block_till_done()
return result
+
+
+async def test_external_ip_blocked(hue_client):
+ """Test external IP blocked."""
+ with patch('homeassistant.components.http.real_ip.ip_address',
+ return_value=ip_address('45.45.45.45')):
+ result = await hue_client.get('/api/username/lights')
+
+ assert result.status == 400
diff --git a/tests/components/emulated_hue/test_init.py b/tests/components/emulated_hue/test_init.py
index 2f443eb5d6e..9b0a5cd9052 100644
--- a/tests/components/emulated_hue/test_init.py
+++ b/tests/components/emulated_hue/test_init.py
@@ -1,14 +1,16 @@
"""Test the Emulated Hue component."""
import json
-from unittest.mock import patch, Mock, mock_open
+from unittest.mock import patch, Mock, mock_open, MagicMock
from homeassistant.components.emulated_hue import Config
def test_config_google_home_entity_id_to_number():
"""Test config adheres to the type."""
- conf = Config(Mock(), {
+ mock_hass = Mock()
+ mock_hass.config.path = MagicMock("path", return_value="test_path")
+ conf = Config(mock_hass, {
'type': 'google_home'
})
@@ -16,29 +18,33 @@ def test_config_google_home_entity_id_to_number():
handle = mop()
with patch('homeassistant.util.json.open', mop, create=True):
- number = conf.entity_id_to_number('light.test')
- assert number == '2'
- assert handle.write.call_count == 1
- assert json.loads(handle.write.mock_calls[0][1][0]) == {
- '1': 'light.test2',
- '2': 'light.test',
- }
+ with patch('homeassistant.util.json.os.open', return_value=0):
+ with patch('homeassistant.util.json.os.replace'):
+ number = conf.entity_id_to_number('light.test')
+ assert number == '2'
+ assert handle.write.call_count == 1
+ assert json.loads(handle.write.mock_calls[0][1][0]) == {
+ '1': 'light.test2',
+ '2': 'light.test',
+ }
- number = conf.entity_id_to_number('light.test')
- assert number == '2'
- assert handle.write.call_count == 1
+ number = conf.entity_id_to_number('light.test')
+ assert number == '2'
+ assert handle.write.call_count == 1
- number = conf.entity_id_to_number('light.test2')
- assert number == '1'
- assert handle.write.call_count == 1
+ number = conf.entity_id_to_number('light.test2')
+ assert number == '1'
+ assert handle.write.call_count == 1
- entity_id = conf.number_to_entity_id('1')
- assert entity_id == 'light.test2'
+ entity_id = conf.number_to_entity_id('1')
+ assert entity_id == 'light.test2'
def test_config_google_home_entity_id_to_number_altered():
"""Test config adheres to the type."""
- conf = Config(Mock(), {
+ mock_hass = Mock()
+ mock_hass.config.path = MagicMock("path", return_value="test_path")
+ conf = Config(mock_hass, {
'type': 'google_home'
})
@@ -46,29 +52,33 @@ def test_config_google_home_entity_id_to_number_altered():
handle = mop()
with patch('homeassistant.util.json.open', mop, create=True):
- number = conf.entity_id_to_number('light.test')
- assert number == '22'
- assert handle.write.call_count == 1
- assert json.loads(handle.write.mock_calls[0][1][0]) == {
- '21': 'light.test2',
- '22': 'light.test',
- }
+ with patch('homeassistant.util.json.os.open', return_value=0):
+ with patch('homeassistant.util.json.os.replace'):
+ number = conf.entity_id_to_number('light.test')
+ assert number == '22'
+ assert handle.write.call_count == 1
+ assert json.loads(handle.write.mock_calls[0][1][0]) == {
+ '21': 'light.test2',
+ '22': 'light.test',
+ }
- number = conf.entity_id_to_number('light.test')
- assert number == '22'
- assert handle.write.call_count == 1
+ number = conf.entity_id_to_number('light.test')
+ assert number == '22'
+ assert handle.write.call_count == 1
- number = conf.entity_id_to_number('light.test2')
- assert number == '21'
- assert handle.write.call_count == 1
+ number = conf.entity_id_to_number('light.test2')
+ assert number == '21'
+ assert handle.write.call_count == 1
- entity_id = conf.number_to_entity_id('21')
- assert entity_id == 'light.test2'
+ entity_id = conf.number_to_entity_id('21')
+ assert entity_id == 'light.test2'
def test_config_google_home_entity_id_to_number_empty():
"""Test config adheres to the type."""
- conf = Config(Mock(), {
+ mock_hass = Mock()
+ mock_hass.config.path = MagicMock("path", return_value="test_path")
+ conf = Config(mock_hass, {
'type': 'google_home'
})
@@ -76,23 +86,25 @@ def test_config_google_home_entity_id_to_number_empty():
handle = mop()
with patch('homeassistant.util.json.open', mop, create=True):
- number = conf.entity_id_to_number('light.test')
- assert number == '1'
- assert handle.write.call_count == 1
- assert json.loads(handle.write.mock_calls[0][1][0]) == {
- '1': 'light.test',
- }
+ with patch('homeassistant.util.json.os.open', return_value=0):
+ with patch('homeassistant.util.json.os.replace'):
+ number = conf.entity_id_to_number('light.test')
+ assert number == '1'
+ assert handle.write.call_count == 1
+ assert json.loads(handle.write.mock_calls[0][1][0]) == {
+ '1': 'light.test',
+ }
- number = conf.entity_id_to_number('light.test')
- assert number == '1'
- assert handle.write.call_count == 1
+ number = conf.entity_id_to_number('light.test')
+ assert number == '1'
+ assert handle.write.call_count == 1
- number = conf.entity_id_to_number('light.test2')
- assert number == '2'
- assert handle.write.call_count == 2
+ number = conf.entity_id_to_number('light.test2')
+ assert number == '2'
+ assert handle.write.call_count == 2
- entity_id = conf.number_to_entity_id('2')
- assert entity_id == 'light.test2'
+ entity_id = conf.number_to_entity_id('2')
+ assert entity_id == 'light.test2'
def test_config_alexa_entity_id_to_number():
diff --git a/tests/components/fan/__init__.py b/tests/components/fan/__init__.py
index 28ae7f4e249..e7cc83217ef 100644
--- a/tests/components/fan/__init__.py
+++ b/tests/components/fan/__init__.py
@@ -1,39 +1 @@
"""Tests for fan platforms."""
-
-import unittest
-
-from homeassistant.components.fan import FanEntity
-
-
-class BaseFan(FanEntity):
- """Implementation of the abstract FanEntity."""
-
- def __init__(self):
- """Initialize the fan."""
- pass
-
-
-class TestFanEntity(unittest.TestCase):
- """Test coverage for base fan entity class."""
-
- def setUp(self):
- """Set up test data."""
- self.fan = BaseFan()
-
- def tearDown(self):
- """Tear down unit test data."""
- self.fan = None
-
- def test_fanentity(self):
- """Test fan entity methods."""
- self.assertIsNone(self.fan.state)
- self.assertEqual(0, len(self.fan.speed_list))
- self.assertEqual(0, self.fan.supported_features)
- self.assertEqual({}, self.fan.state_attributes)
- # Test set_speed not required
- self.fan.set_speed()
- self.fan.oscillate()
- with self.assertRaises(NotImplementedError):
- self.fan.turn_on()
- with self.assertRaises(NotImplementedError):
- self.fan.turn_off()
diff --git a/tests/components/fan/common.py b/tests/components/fan/common.py
new file mode 100644
index 00000000000..60e1cab1ac0
--- /dev/null
+++ b/tests/components/fan/common.py
@@ -0,0 +1,72 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.fan import (
+ ATTR_DIRECTION, ATTR_SPEED, ATTR_OSCILLATING, DOMAIN,
+ SERVICE_OSCILLATE, SERVICE_SET_DIRECTION, SERVICE_SET_SPEED)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, entity_id: str = None, speed: str = None) -> None:
+ """Turn all or specified fan on."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_SPEED, speed),
+ ] if value is not None
+ }
+
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
+
+
+@bind_hass
+def turn_off(hass, entity_id: str = None) -> None:
+ """Turn all or specified fan off."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+def oscillate(hass, entity_id: str = None,
+ should_oscillate: bool = True) -> None:
+ """Set oscillation on all or specified fan."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_OSCILLATING, should_oscillate),
+ ] if value is not None
+ }
+
+ hass.services.call(DOMAIN, SERVICE_OSCILLATE, data)
+
+
+@bind_hass
+def set_speed(hass, entity_id: str = None, speed: str = None) -> None:
+ """Set speed for all or specified fan."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_SPEED, speed),
+ ] if value is not None
+ }
+
+ hass.services.call(DOMAIN, SERVICE_SET_SPEED, data)
+
+
+@bind_hass
+def set_direction(hass, entity_id: str = None, direction: str = None) -> None:
+ """Set direction for all or specified fan."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_DIRECTION, direction),
+ ] if value is not None
+ }
+
+ hass.services.call(DOMAIN, SERVICE_SET_DIRECTION, data)
diff --git a/tests/components/fan/test_demo.py b/tests/components/fan/test_demo.py
index 69680fb1cfd..48704ca4464 100644
--- a/tests/components/fan/test_demo.py
+++ b/tests/components/fan/test_demo.py
@@ -7,6 +7,7 @@ from homeassistant.components import fan
from homeassistant.const import STATE_OFF, STATE_ON
from tests.common import get_test_home_assistant
+from tests.components.fan import common
FAN_ENTITY_ID = 'fan.living_room_fan'
@@ -34,11 +35,11 @@ class TestDemoFan(unittest.TestCase):
"""Test turning on the device."""
self.assertEqual(STATE_OFF, self.get_entity().state)
- fan.turn_on(self.hass, FAN_ENTITY_ID)
+ common.turn_on(self.hass, FAN_ENTITY_ID)
self.hass.block_till_done()
self.assertNotEqual(STATE_OFF, self.get_entity().state)
- fan.turn_on(self.hass, FAN_ENTITY_ID, fan.SPEED_HIGH)
+ common.turn_on(self.hass, FAN_ENTITY_ID, fan.SPEED_HIGH)
self.hass.block_till_done()
self.assertEqual(STATE_ON, self.get_entity().state)
self.assertEqual(fan.SPEED_HIGH,
@@ -48,11 +49,11 @@ class TestDemoFan(unittest.TestCase):
"""Test turning off the device."""
self.assertEqual(STATE_OFF, self.get_entity().state)
- fan.turn_on(self.hass, FAN_ENTITY_ID)
+ common.turn_on(self.hass, FAN_ENTITY_ID)
self.hass.block_till_done()
self.assertNotEqual(STATE_OFF, self.get_entity().state)
- fan.turn_off(self.hass, FAN_ENTITY_ID)
+ common.turn_off(self.hass, FAN_ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(STATE_OFF, self.get_entity().state)
@@ -60,11 +61,11 @@ class TestDemoFan(unittest.TestCase):
"""Test turning off all fans."""
self.assertEqual(STATE_OFF, self.get_entity().state)
- fan.turn_on(self.hass, FAN_ENTITY_ID)
+ common.turn_on(self.hass, FAN_ENTITY_ID)
self.hass.block_till_done()
self.assertNotEqual(STATE_OFF, self.get_entity().state)
- fan.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
self.assertEqual(STATE_OFF, self.get_entity().state)
@@ -72,7 +73,7 @@ class TestDemoFan(unittest.TestCase):
"""Test setting the direction of the device."""
self.assertEqual(STATE_OFF, self.get_entity().state)
- fan.set_direction(self.hass, FAN_ENTITY_ID, fan.DIRECTION_REVERSE)
+ common.set_direction(self.hass, FAN_ENTITY_ID, fan.DIRECTION_REVERSE)
self.hass.block_till_done()
self.assertEqual(fan.DIRECTION_REVERSE,
self.get_entity().attributes.get('direction'))
@@ -81,7 +82,7 @@ class TestDemoFan(unittest.TestCase):
"""Test setting the speed of the device."""
self.assertEqual(STATE_OFF, self.get_entity().state)
- fan.set_speed(self.hass, FAN_ENTITY_ID, fan.SPEED_LOW)
+ common.set_speed(self.hass, FAN_ENTITY_ID, fan.SPEED_LOW)
self.hass.block_till_done()
self.assertEqual(fan.SPEED_LOW,
self.get_entity().attributes.get('speed'))
@@ -90,11 +91,11 @@ class TestDemoFan(unittest.TestCase):
"""Test oscillating the fan."""
self.assertFalse(self.get_entity().attributes.get('oscillating'))
- fan.oscillate(self.hass, FAN_ENTITY_ID, True)
+ common.oscillate(self.hass, FAN_ENTITY_ID, True)
self.hass.block_till_done()
self.assertTrue(self.get_entity().attributes.get('oscillating'))
- fan.oscillate(self.hass, FAN_ENTITY_ID, False)
+ common.oscillate(self.hass, FAN_ENTITY_ID, False)
self.hass.block_till_done()
self.assertFalse(self.get_entity().attributes.get('oscillating'))
@@ -102,6 +103,6 @@ class TestDemoFan(unittest.TestCase):
"""Test is on service call."""
self.assertFalse(fan.is_on(self.hass, FAN_ENTITY_ID))
- fan.turn_on(self.hass, FAN_ENTITY_ID)
+ common.turn_on(self.hass, FAN_ENTITY_ID)
self.hass.block_till_done()
self.assertTrue(fan.is_on(self.hass, FAN_ENTITY_ID))
diff --git a/tests/components/fan/test_init.py b/tests/components/fan/test_init.py
new file mode 100644
index 00000000000..15f9a79d2d2
--- /dev/null
+++ b/tests/components/fan/test_init.py
@@ -0,0 +1,40 @@
+"""Tests for fan platforms."""
+
+import unittest
+
+from homeassistant.components.fan import FanEntity
+
+
+class BaseFan(FanEntity):
+ """Implementation of the abstract FanEntity."""
+
+ def __init__(self):
+ """Initialize the fan."""
+ pass
+
+
+class TestFanEntity(unittest.TestCase):
+ """Test coverage for base fan entity class."""
+
+ def setUp(self):
+ """Set up test data."""
+ self.fan = BaseFan()
+
+ def tearDown(self):
+ """Tear down unit test data."""
+ self.fan = None
+
+ def test_fanentity(self):
+ """Test fan entity methods."""
+ self.assertEqual('on', self.fan.state)
+ self.assertEqual(0, len(self.fan.speed_list))
+ self.assertEqual(0, self.fan.supported_features)
+ self.assertEqual({'speed_list': []}, self.fan.state_attributes)
+ # Test set_speed not required
+ self.fan.oscillate(True)
+ with self.assertRaises(NotImplementedError):
+ self.fan.set_speed('slow')
+ with self.assertRaises(NotImplementedError):
+ self.fan.turn_on()
+ with self.assertRaises(NotImplementedError):
+ self.fan.turn_off()
diff --git a/tests/components/fan/test_mqtt.py b/tests/components/fan/test_mqtt.py
index 7f69e56218b..7434e5aa1c9 100644
--- a/tests/components/fan/test_mqtt.py
+++ b/tests/components/fan/test_mqtt.py
@@ -1,12 +1,14 @@
"""Test MQTT fans."""
import unittest
-from homeassistant.setup import setup_component
+from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components import fan
+from homeassistant.components.mqtt.discovery import async_start
from homeassistant.const import ATTR_ASSUMED_STATE, STATE_UNAVAILABLE
from tests.common import (
- mock_mqtt_component, fire_mqtt_message, get_test_home_assistant)
+ mock_mqtt_component, async_fire_mqtt_message, fire_mqtt_message,
+ get_test_home_assistant, async_mock_mqtt_component)
class TestMqttFan(unittest.TestCase):
@@ -102,3 +104,49 @@ class TestMqttFan(unittest.TestCase):
state = self.hass.states.get('fan.test')
self.assertNotEqual(STATE_UNAVAILABLE, state.state)
+
+
+async def test_discovery_removal_fan(hass, mqtt_mock, caplog):
+ """Test removal of discovered fan."""
+ await async_start(hass, 'homeassistant', {})
+ data = (
+ '{ "name": "Beer",'
+ ' "command_topic": "test_topic" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/fan/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('fan.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/fan/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('fan.beer')
+ assert state is None
+
+
+async def test_unique_id(hass):
+ """Test unique_id option only creates one fan per id."""
+ await async_mock_mqtt_component(hass)
+ assert await async_setup_component(hass, fan.DOMAIN, {
+ fan.DOMAIN: [{
+ 'platform': 'mqtt',
+ 'name': 'Test 1',
+ 'state_topic': 'test-topic',
+ 'command_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }, {
+ 'platform': 'mqtt',
+ 'name': 'Test 2',
+ 'state_topic': 'test-topic',
+ 'command_topic': 'test-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }]
+ })
+
+ async_fire_mqtt_message(hass, 'test-topic', 'payload')
+ await hass.async_block_till_done()
+
+ assert len(hass.states.async_entity_ids(fan.DOMAIN)) == 1
diff --git a/tests/components/fan/test_template.py b/tests/components/fan/test_template.py
index e229083069d..09d3603e004 100644
--- a/tests/components/fan/test_template.py
+++ b/tests/components/fan/test_template.py
@@ -3,7 +3,6 @@ import logging
from homeassistant.core import callback
from homeassistant import setup
-import homeassistant.components as components
from homeassistant.const import STATE_ON, STATE_OFF
from homeassistant.components.fan import (
ATTR_SPEED, ATTR_OSCILLATING, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH,
@@ -11,6 +10,8 @@ from homeassistant.components.fan import (
from tests.common import (
get_test_home_assistant, assert_setup_component)
+from tests.components.fan import common
+
_LOGGER = logging.getLogger(__name__)
@@ -288,7 +289,7 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# verify
@@ -296,7 +297,7 @@ class TestTemplateFan:
self._verify(STATE_ON, None, None, None)
# Turn off fan
- components.fan.turn_off(self.hass, _TEST_FAN)
+ common.turn_off(self.hass, _TEST_FAN)
self.hass.block_till_done()
# verify
@@ -308,7 +309,7 @@ class TestTemplateFan:
self._register_components()
# Turn on fan with high speed
- components.fan.turn_on(self.hass, _TEST_FAN, SPEED_HIGH)
+ common.turn_on(self.hass, _TEST_FAN, SPEED_HIGH)
self.hass.block_till_done()
# verify
@@ -321,11 +322,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's speed to high
- components.fan.set_speed(self.hass, _TEST_FAN, SPEED_HIGH)
+ common.set_speed(self.hass, _TEST_FAN, SPEED_HIGH)
self.hass.block_till_done()
# verify
@@ -333,7 +334,7 @@ class TestTemplateFan:
self._verify(STATE_ON, SPEED_HIGH, None, None)
# Set fan's speed to medium
- components.fan.set_speed(self.hass, _TEST_FAN, SPEED_MEDIUM)
+ common.set_speed(self.hass, _TEST_FAN, SPEED_MEDIUM)
self.hass.block_till_done()
# verify
@@ -345,11 +346,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's speed to 'invalid'
- components.fan.set_speed(self.hass, _TEST_FAN, 'invalid')
+ common.set_speed(self.hass, _TEST_FAN, 'invalid')
self.hass.block_till_done()
# verify speed is unchanged
@@ -361,11 +362,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's speed to high
- components.fan.set_speed(self.hass, _TEST_FAN, SPEED_HIGH)
+ common.set_speed(self.hass, _TEST_FAN, SPEED_HIGH)
self.hass.block_till_done()
# verify
@@ -373,7 +374,7 @@ class TestTemplateFan:
self._verify(STATE_ON, SPEED_HIGH, None, None)
# Set fan's speed to 'invalid'
- components.fan.set_speed(self.hass, _TEST_FAN, 'invalid')
+ common.set_speed(self.hass, _TEST_FAN, 'invalid')
self.hass.block_till_done()
# verify speed is unchanged
@@ -385,11 +386,11 @@ class TestTemplateFan:
self._register_components(['1', '2', '3'])
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's speed to '1'
- components.fan.set_speed(self.hass, _TEST_FAN, '1')
+ common.set_speed(self.hass, _TEST_FAN, '1')
self.hass.block_till_done()
# verify
@@ -397,7 +398,7 @@ class TestTemplateFan:
self._verify(STATE_ON, '1', None, None)
# Set fan's speed to 'medium' which is invalid
- components.fan.set_speed(self.hass, _TEST_FAN, SPEED_MEDIUM)
+ common.set_speed(self.hass, _TEST_FAN, SPEED_MEDIUM)
self.hass.block_till_done()
# verify that speed is unchanged
@@ -409,11 +410,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's osc to True
- components.fan.oscillate(self.hass, _TEST_FAN, True)
+ common.oscillate(self.hass, _TEST_FAN, True)
self.hass.block_till_done()
# verify
@@ -421,7 +422,7 @@ class TestTemplateFan:
self._verify(STATE_ON, None, True, None)
# Set fan's osc to False
- components.fan.oscillate(self.hass, _TEST_FAN, False)
+ common.oscillate(self.hass, _TEST_FAN, False)
self.hass.block_till_done()
# verify
@@ -433,11 +434,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's osc to 'invalid'
- components.fan.oscillate(self.hass, _TEST_FAN, 'invalid')
+ common.oscillate(self.hass, _TEST_FAN, 'invalid')
self.hass.block_till_done()
# verify
@@ -449,11 +450,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's osc to True
- components.fan.oscillate(self.hass, _TEST_FAN, True)
+ common.oscillate(self.hass, _TEST_FAN, True)
self.hass.block_till_done()
# verify
@@ -461,7 +462,7 @@ class TestTemplateFan:
self._verify(STATE_ON, None, True, None)
# Set fan's osc to False
- components.fan.oscillate(self.hass, _TEST_FAN, None)
+ common.oscillate(self.hass, _TEST_FAN, None)
self.hass.block_till_done()
# verify osc is unchanged
@@ -473,11 +474,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's direction to forward
- components.fan.set_direction(self.hass, _TEST_FAN, DIRECTION_FORWARD)
+ common.set_direction(self.hass, _TEST_FAN, DIRECTION_FORWARD)
self.hass.block_till_done()
# verify
@@ -486,7 +487,7 @@ class TestTemplateFan:
self._verify(STATE_ON, None, None, DIRECTION_FORWARD)
# Set fan's direction to reverse
- components.fan.set_direction(self.hass, _TEST_FAN, DIRECTION_REVERSE)
+ common.set_direction(self.hass, _TEST_FAN, DIRECTION_REVERSE)
self.hass.block_till_done()
# verify
@@ -499,11 +500,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's direction to 'invalid'
- components.fan.set_direction(self.hass, _TEST_FAN, 'invalid')
+ common.set_direction(self.hass, _TEST_FAN, 'invalid')
self.hass.block_till_done()
# verify direction is unchanged
@@ -515,11 +516,11 @@ class TestTemplateFan:
self._register_components()
# Turn on fan
- components.fan.turn_on(self.hass, _TEST_FAN)
+ common.turn_on(self.hass, _TEST_FAN)
self.hass.block_till_done()
# Set fan's direction to forward
- components.fan.set_direction(self.hass, _TEST_FAN, DIRECTION_FORWARD)
+ common.set_direction(self.hass, _TEST_FAN, DIRECTION_FORWARD)
self.hass.block_till_done()
# verify
@@ -528,7 +529,7 @@ class TestTemplateFan:
self._verify(STATE_ON, None, None, DIRECTION_FORWARD)
# Set fan's direction to 'invalid'
- components.fan.set_direction(self.hass, _TEST_FAN, 'invalid')
+ common.set_direction(self.hass, _TEST_FAN, 'invalid')
self.hass.block_till_done()
# verify direction is unchanged
diff --git a/tests/components/frontend/test_init.py b/tests/components/frontend/test_init.py
index 17bf3d953ef..2e78e0441a3 100644
--- a/tests/components/frontend/test_init.py
+++ b/tests/components/frontend/test_init.py
@@ -5,12 +5,11 @@ from unittest.mock import patch
import pytest
-from homeassistant.exceptions import HomeAssistantError
from homeassistant.setup import async_setup_component
from homeassistant.components.frontend import (
DOMAIN, CONF_JS_VERSION, CONF_THEMES, CONF_EXTRA_HTML_URL,
CONF_EXTRA_HTML_URL_ES5)
-from homeassistant.components import websocket_api as wapi
+from homeassistant.components.websocket_api.const import TYPE_RESULT
from tests.common import mock_coro
@@ -214,7 +213,7 @@ async def test_missing_themes(hass, hass_ws_client):
msg = await client.receive_json()
assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
assert msg['result']['default_theme'] == 'default'
assert msg['result']['themes'] == {}
@@ -253,7 +252,7 @@ async def test_get_panels(hass, hass_ws_client):
msg = await client.receive_json()
assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
assert msg['result']['map']['component_name'] == 'map'
assert msg['result']['map']['url_path'] == 'map'
@@ -276,68 +275,11 @@ async def test_get_translations(hass, hass_ws_client):
msg = await client.receive_json()
assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
assert msg['result'] == {'resources': {'lang': 'nl'}}
-async def test_lovelace_ui(hass, hass_ws_client):
- """Test lovelace_ui command."""
- await async_setup_component(hass, 'frontend')
- client = await hass_ws_client(hass)
-
- with patch('homeassistant.components.frontend.load_yaml',
- return_value={'hello': 'world'}):
- await client.send_json({
- 'id': 5,
- 'type': 'frontend/lovelace_config',
- })
- msg = await client.receive_json()
-
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
- assert msg['result'] == {'hello': 'world'}
-
-
-async def test_lovelace_ui_not_found(hass, hass_ws_client):
- """Test lovelace_ui command cannot find file."""
- await async_setup_component(hass, 'frontend')
- client = await hass_ws_client(hass)
-
- with patch('homeassistant.components.frontend.load_yaml',
- side_effect=FileNotFoundError):
- await client.send_json({
- 'id': 5,
- 'type': 'frontend/lovelace_config',
- })
- msg = await client.receive_json()
-
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success'] is False
- assert msg['error']['code'] == 'file_not_found'
-
-
-async def test_lovelace_ui_load_err(hass, hass_ws_client):
- """Test lovelace_ui command cannot find file."""
- await async_setup_component(hass, 'frontend')
- client = await hass_ws_client(hass)
-
- with patch('homeassistant.components.frontend.load_yaml',
- side_effect=HomeAssistantError):
- await client.send_json({
- 'id': 5,
- 'type': 'frontend/lovelace_config',
- })
- msg = await client.receive_json()
-
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success'] is False
- assert msg['error']['code'] == 'load_error'
-
-
async def test_auth_load(mock_http_client):
"""Test auth component loaded by default."""
resp = await mock_http_client.get('/auth/providers')
@@ -352,10 +294,18 @@ async def test_onboarding_load(mock_http_client):
async def test_auth_authorize(mock_http_client):
"""Test the authorize endpoint works."""
- resp = await mock_http_client.get('/auth/authorize?hello=world')
- assert resp.url.query_string == 'hello=world'
- assert resp.url.path == '/frontend_es5/authorize.html'
+ resp = await mock_http_client.get(
+ '/auth/authorize?response_type=code&client_id=https://localhost/&'
+ 'redirect_uri=https://localhost/&state=123%23456')
- resp = await mock_http_client.get('/auth/authorize?latest&hello=world')
- assert resp.url.query_string == 'latest&hello=world'
- assert resp.url.path == '/frontend_latest/authorize.html'
+ assert str(resp.url.relative()) == (
+ '/frontend_es5/authorize.html?response_type=code&client_id='
+ 'https://localhost/&redirect_uri=https://localhost/&state=123%23456')
+
+ resp = await mock_http_client.get(
+ '/auth/authorize?latest&response_type=code&client_id='
+ 'https://localhost/&redirect_uri=https://localhost/&state=123%23456')
+
+ assert str(resp.url.relative()) == (
+ '/frontend_latest/authorize.html?latest&response_type=code&client_id='
+ 'https://localhost/&redirect_uri=https://localhost/&state=123%23456')
diff --git a/tests/components/google_assistant/test_google_assistant.py b/tests/components/google_assistant/test_google_assistant.py
index d9682940bdc..2ebfa5cc9ed 100644
--- a/tests/components/google_assistant/test_google_assistant.py
+++ b/tests/components/google_assistant/test_google_assistant.py
@@ -23,7 +23,12 @@ HA_HEADERS = {
PROJECT_ID = 'hasstest-1234'
CLIENT_ID = 'helloworld'
ACCESS_TOKEN = 'superdoublesecret'
-AUTH_HEADER = {AUTHORIZATION: 'Bearer {}'.format(ACCESS_TOKEN)}
+
+
+@pytest.fixture
+def auth_header(hass_access_token):
+ """Generate an HTTP header with bearer token authorization."""
+ return {AUTHORIZATION: 'Bearer {}'.format(hass_access_token)}
@pytest.fixture
@@ -33,8 +38,6 @@ def assistant_client(loop, hass, aiohttp_client):
setup.async_setup_component(hass, 'google_assistant', {
'google_assistant': {
'project_id': PROJECT_ID,
- 'client_id': CLIENT_ID,
- 'access_token': ACCESS_TOKEN,
'entity_config': {
'light.ceiling_lights': {
'aliases': ['top lights', 'ceiling lights'],
@@ -97,31 +100,14 @@ def hass_fixture(loop, hass):
@asyncio.coroutine
-def test_auth(assistant_client):
- """Test the auth process."""
- result = yield from assistant_client.get(
- ga.const.GOOGLE_ASSISTANT_API_ENDPOINT + '/auth',
- params={
- 'redirect_uri':
- 'http://testurl/r/{}'.format(PROJECT_ID),
- 'client_id': CLIENT_ID,
- 'state': 'random1234',
- },
- allow_redirects=False)
- assert result.status == 301
- loc = result.headers.get('Location')
- assert ACCESS_TOKEN in loc
-
-
-@asyncio.coroutine
-def test_sync_request(hass_fixture, assistant_client):
+def test_sync_request(hass_fixture, assistant_client, auth_header):
"""Test a sync request."""
reqid = '5711642932632160983'
data = {'requestId': reqid, 'inputs': [{'intent': 'action.devices.SYNC'}]}
result = yield from assistant_client.post(
ga.const.GOOGLE_ASSISTANT_API_ENDPOINT,
data=json.dumps(data),
- headers=AUTH_HEADER)
+ headers=auth_header)
assert result.status == 200
body = yield from result.json()
assert body.get('requestId') == reqid
@@ -141,7 +127,7 @@ def test_sync_request(hass_fixture, assistant_client):
@asyncio.coroutine
-def test_query_request(hass_fixture, assistant_client):
+def test_query_request(hass_fixture, assistant_client, auth_header):
"""Test a query request."""
reqid = '5711642932632160984'
data = {
@@ -165,7 +151,7 @@ def test_query_request(hass_fixture, assistant_client):
result = yield from assistant_client.post(
ga.const.GOOGLE_ASSISTANT_API_ENDPOINT,
data=json.dumps(data),
- headers=AUTH_HEADER)
+ headers=auth_header)
assert result.status == 200
body = yield from result.json()
assert body.get('requestId') == reqid
@@ -180,7 +166,7 @@ def test_query_request(hass_fixture, assistant_client):
@asyncio.coroutine
-def test_query_climate_request(hass_fixture, assistant_client):
+def test_query_climate_request(hass_fixture, assistant_client, auth_header):
"""Test a query request."""
reqid = '5711642932632160984'
data = {
@@ -200,7 +186,7 @@ def test_query_climate_request(hass_fixture, assistant_client):
result = yield from assistant_client.post(
ga.const.GOOGLE_ASSISTANT_API_ENDPOINT,
data=json.dumps(data),
- headers=AUTH_HEADER)
+ headers=auth_header)
assert result.status == 200
body = yield from result.json()
assert body.get('requestId') == reqid
@@ -229,7 +215,7 @@ def test_query_climate_request(hass_fixture, assistant_client):
@asyncio.coroutine
-def test_query_climate_request_f(hass_fixture, assistant_client):
+def test_query_climate_request_f(hass_fixture, assistant_client, auth_header):
"""Test a query request."""
# Mock demo devices as fahrenheit to see if we convert to celsius
hass_fixture.config.units.temperature_unit = const.TEMP_FAHRENHEIT
@@ -256,7 +242,7 @@ def test_query_climate_request_f(hass_fixture, assistant_client):
result = yield from assistant_client.post(
ga.const.GOOGLE_ASSISTANT_API_ENDPOINT,
data=json.dumps(data),
- headers=AUTH_HEADER)
+ headers=auth_header)
assert result.status == 200
body = yield from result.json()
assert body.get('requestId') == reqid
@@ -286,7 +272,7 @@ def test_query_climate_request_f(hass_fixture, assistant_client):
@asyncio.coroutine
-def test_execute_request(hass_fixture, assistant_client):
+def test_execute_request(hass_fixture, assistant_client, auth_header):
"""Test an execute request."""
reqid = '5711642932632160985'
data = {
@@ -358,7 +344,7 @@ def test_execute_request(hass_fixture, assistant_client):
result = yield from assistant_client.post(
ga.const.GOOGLE_ASSISTANT_API_ENDPOINT,
data=json.dumps(data),
- headers=AUTH_HEADER)
+ headers=auth_header)
assert result.status == 200
body = yield from result.json()
assert body.get('requestId') == reqid
diff --git a/tests/components/google_assistant/test_init.py b/tests/components/google_assistant/test_init.py
index 9ced9fc329d..3f6a799b423 100644
--- a/tests/components/google_assistant/test_init.py
+++ b/tests/components/google_assistant/test_init.py
@@ -5,7 +5,6 @@ from homeassistant.setup import async_setup_component
from homeassistant.components import google_assistant as ga
GA_API_KEY = "Agdgjsj399sdfkosd932ksd"
-GA_AGENT_USER_ID = "testid"
@asyncio.coroutine
@@ -17,9 +16,6 @@ def test_request_sync_service(aioclient_mock, hass):
yield from async_setup_component(hass, 'google_assistant', {
'google_assistant': {
'project_id': 'test_project',
- 'client_id': 'r7328kwdsdfsdf03223409',
- 'access_token': '8wdsfjsf932492342349234',
- 'agent_user_id': GA_AGENT_USER_ID,
'api_key': GA_API_KEY
}})
diff --git a/tests/components/group/common.py b/tests/components/group/common.py
new file mode 100644
index 00000000000..380586e3854
--- /dev/null
+++ b/tests/components/group/common.py
@@ -0,0 +1,70 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.group import (
+ ATTR_ADD_ENTITIES, ATTR_CONTROL, ATTR_ENTITIES, ATTR_OBJECT_ID, ATTR_VIEW,
+ ATTR_VISIBLE, DOMAIN, SERVICE_REMOVE, SERVICE_SET, SERVICE_SET_VISIBILITY)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, ATTR_ICON, ATTR_NAME, SERVICE_RELOAD)
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def reload(hass):
+ """Reload the automation from config."""
+ hass.add_job(async_reload, hass)
+
+
+@callback
+@bind_hass
+def async_reload(hass):
+ """Reload the automation from config."""
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RELOAD))
+
+
+@bind_hass
+def set_group(hass, object_id, name=None, entity_ids=None, visible=None,
+ icon=None, view=None, control=None, add=None):
+ """Create/Update a group."""
+ hass.add_job(
+ async_set_group, hass, object_id, name, entity_ids, visible, icon,
+ view, control, add)
+
+
+@callback
+@bind_hass
+def async_set_group(hass, object_id, name=None, entity_ids=None, visible=None,
+ icon=None, view=None, control=None, add=None):
+ """Create/Update a group."""
+ data = {
+ key: value for key, value in [
+ (ATTR_OBJECT_ID, object_id),
+ (ATTR_NAME, name),
+ (ATTR_ENTITIES, entity_ids),
+ (ATTR_VISIBLE, visible),
+ (ATTR_ICON, icon),
+ (ATTR_VIEW, view),
+ (ATTR_CONTROL, control),
+ (ATTR_ADD_ENTITIES, add),
+ ] if value is not None
+ }
+
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SET, data))
+
+
+@callback
+@bind_hass
+def async_remove(hass, object_id):
+ """Remove a user group."""
+ data = {ATTR_OBJECT_ID: object_id}
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_REMOVE, data))
+
+
+@bind_hass
+def set_visibility(hass, entity_id=None, visible=True):
+ """Hide or shows a group."""
+ data = {ATTR_ENTITY_ID: entity_id, ATTR_VISIBLE: visible}
+ hass.services.call(DOMAIN, SERVICE_SET_VISIBILITY, data)
diff --git a/tests/components/group/test_init.py b/tests/components/group/test_init.py
index 47101dd415a..55c8a7778cb 100644
--- a/tests/components/group/test_init.py
+++ b/tests/components/group/test_init.py
@@ -12,6 +12,7 @@ from homeassistant.const import (
import homeassistant.components.group as group
from tests.common import get_test_home_assistant, assert_setup_component
+from tests.components.group import common
class TestComponentsGroup(unittest.TestCase):
@@ -367,7 +368,7 @@ class TestComponentsGroup(unittest.TestCase):
}}}):
with patch('homeassistant.config.find_config_file',
return_value=''):
- group.reload(self.hass)
+ common.reload(self.hass)
self.hass.block_till_done()
assert sorted(self.hass.states.entity_ids()) == \
@@ -385,13 +386,13 @@ class TestComponentsGroup(unittest.TestCase):
group_entity_id = group.ENTITY_ID_FORMAT.format('test_group')
# Hide the group
- group.set_visibility(self.hass, group_entity_id, False)
+ common.set_visibility(self.hass, group_entity_id, False)
self.hass.block_till_done()
group_state = self.hass.states.get(group_entity_id)
self.assertTrue(group_state.attributes.get(ATTR_HIDDEN))
# Show it again
- group.set_visibility(self.hass, group_entity_id, True)
+ common.set_visibility(self.hass, group_entity_id, True)
self.hass.block_till_done()
group_state = self.hass.states.get(group_entity_id)
self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
@@ -408,7 +409,7 @@ class TestComponentsGroup(unittest.TestCase):
# The old way would create a new group modify_group1 because
# internally it didn't know anything about those created in the config
- group.set_group(self.hass, 'modify_group', icon="mdi:play")
+ common.set_group(self.hass, 'modify_group', icon="mdi:play")
self.hass.block_till_done()
group_state = self.hass.states.get(
@@ -441,7 +442,7 @@ def test_service_group_set_group_remove_group(hass):
'group': {}
})
- group.async_set_group(hass, 'user_test_group', name="Test")
+ common.async_set_group(hass, 'user_test_group', name="Test")
yield from hass.async_block_till_done()
group_state = hass.states.get('group.user_test_group')
@@ -449,7 +450,7 @@ def test_service_group_set_group_remove_group(hass):
assert group_state.attributes[group.ATTR_AUTO]
assert group_state.attributes['friendly_name'] == "Test"
- group.async_set_group(
+ common.async_set_group(
hass, 'user_test_group', view=True, visible=False,
entity_ids=['test.entity_bla1'])
yield from hass.async_block_till_done()
@@ -462,7 +463,7 @@ def test_service_group_set_group_remove_group(hass):
assert group_state.attributes['friendly_name'] == "Test"
assert list(group_state.attributes['entity_id']) == ['test.entity_bla1']
- group.async_set_group(
+ common.async_set_group(
hass, 'user_test_group', icon="mdi:camera", name="Test2",
control="hidden", add=['test.entity_id2'])
yield from hass.async_block_till_done()
@@ -478,7 +479,7 @@ def test_service_group_set_group_remove_group(hass):
assert sorted(list(group_state.attributes['entity_id'])) == sorted([
'test.entity_bla1', 'test.entity_id2'])
- group.async_remove(hass, 'user_test_group')
+ common.async_remove(hass, 'user_test_group')
yield from hass.async_block_till_done()
group_state = hass.states.get('group.user_test_group')
diff --git a/tests/components/hassio/conftest.py b/tests/components/hassio/conftest.py
index 9f20efc08a5..f9ad1c578de 100644
--- a/tests/components/hassio/conftest.py
+++ b/tests/components/hassio/conftest.py
@@ -4,8 +4,9 @@ from unittest.mock import patch, Mock
import pytest
+from homeassistant.core import CoreState
from homeassistant.setup import async_setup_component
-from homeassistant.components.hassio.handler import HassIO
+from homeassistant.components.hassio.handler import HassIO, HassioAPIError
from tests.common import mock_coro
from . import API_PASSWORD, HASSIO_TOKEN
@@ -21,7 +22,7 @@ def hassio_env():
patch.dict(os.environ, {'HASSIO_TOKEN': "123456"}), \
patch('homeassistant.components.hassio.HassIO.'
'get_homeassistant_info',
- Mock(return_value=mock_coro(None))):
+ Mock(side_effect=HassioAPIError())):
yield
@@ -32,7 +33,8 @@ def hassio_client(hassio_env, hass, aiohttp_client):
Mock(return_value=mock_coro({"result": "ok"}))), \
patch('homeassistant.components.hassio.HassIO.'
'get_homeassistant_info',
- Mock(return_value=mock_coro(None))):
+ Mock(side_effect=HassioAPIError())):
+ hass.state = CoreState.starting
hass.loop.run_until_complete(async_setup_component(hass, 'hassio', {
'http': {
'api_password': API_PASSWORD
diff --git a/tests/components/hassio/test_auth.py b/tests/components/hassio/test_auth.py
new file mode 100644
index 00000000000..fdf3230dedc
--- /dev/null
+++ b/tests/components/hassio/test_auth.py
@@ -0,0 +1,123 @@
+"""The tests for the hassio component."""
+from unittest.mock import patch, Mock
+
+from homeassistant.const import HTTP_HEADER_HA_AUTH
+from homeassistant.exceptions import HomeAssistantError
+
+from tests.common import mock_coro, register_auth_provider
+from . import API_PASSWORD
+
+
+async def test_login_success(hass, hassio_client):
+ """Test no auth needed for ."""
+ await register_auth_provider(hass, {'type': 'homeassistant'})
+
+ with patch('homeassistant.auth.providers.homeassistant.'
+ 'HassAuthProvider.async_validate_login',
+ Mock(return_value=mock_coro())) as mock_login:
+ resp = await hassio_client.post(
+ '/api/hassio_auth',
+ json={
+ "username": "test",
+ "password": "123456",
+ "addon": "samba",
+ },
+ headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ }
+ )
+
+ # Check we got right response
+ assert resp.status == 200
+ mock_login.assert_called_with("test", "123456")
+
+
+async def test_login_error(hass, hassio_client):
+ """Test no auth needed for error."""
+ await register_auth_provider(hass, {'type': 'homeassistant'})
+
+ with patch('homeassistant.auth.providers.homeassistant.'
+ 'HassAuthProvider.async_validate_login',
+ Mock(side_effect=HomeAssistantError())) as mock_login:
+ resp = await hassio_client.post(
+ '/api/hassio_auth',
+ json={
+ "username": "test",
+ "password": "123456",
+ "addon": "samba",
+ },
+ headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ }
+ )
+
+ # Check we got right response
+ assert resp.status == 403
+ mock_login.assert_called_with("test", "123456")
+
+
+async def test_login_no_data(hass, hassio_client):
+ """Test auth with no data -> error."""
+ await register_auth_provider(hass, {'type': 'homeassistant'})
+
+ with patch('homeassistant.auth.providers.homeassistant.'
+ 'HassAuthProvider.async_validate_login',
+ Mock(side_effect=HomeAssistantError())) as mock_login:
+ resp = await hassio_client.post(
+ '/api/hassio_auth',
+ headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ }
+ )
+
+ # Check we got right response
+ assert resp.status == 400
+ assert not mock_login.called
+
+
+async def test_login_no_username(hass, hassio_client):
+ """Test auth with no username in data -> error."""
+ await register_auth_provider(hass, {'type': 'homeassistant'})
+
+ with patch('homeassistant.auth.providers.homeassistant.'
+ 'HassAuthProvider.async_validate_login',
+ Mock(side_effect=HomeAssistantError())) as mock_login:
+ resp = await hassio_client.post(
+ '/api/hassio_auth',
+ json={
+ "password": "123456",
+ "addon": "samba",
+ },
+ headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ }
+ )
+
+ # Check we got right response
+ assert resp.status == 400
+ assert not mock_login.called
+
+
+async def test_login_success_extra(hass, hassio_client):
+ """Test auth with extra data."""
+ await register_auth_provider(hass, {'type': 'homeassistant'})
+
+ with patch('homeassistant.auth.providers.homeassistant.'
+ 'HassAuthProvider.async_validate_login',
+ Mock(return_value=mock_coro())) as mock_login:
+ resp = await hassio_client.post(
+ '/api/hassio_auth',
+ json={
+ "username": "test",
+ "password": "123456",
+ "addon": "samba",
+ "path": "/share",
+ },
+ headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ }
+ )
+
+ # Check we got right response
+ assert resp.status == 200
+ mock_login.assert_called_with("test", "123456")
diff --git a/tests/components/hassio/test_discovery.py b/tests/components/hassio/test_discovery.py
new file mode 100644
index 00000000000..c8926a1cd18
--- /dev/null
+++ b/tests/components/hassio/test_discovery.py
@@ -0,0 +1,141 @@
+"""Test config flow."""
+from unittest.mock import patch, Mock
+
+from homeassistant.setup import async_setup_component
+from homeassistant.components.hassio.handler import HassioAPIError
+from homeassistant.const import EVENT_HOMEASSISTANT_START, HTTP_HEADER_HA_AUTH
+
+from tests.common import mock_coro
+from . import API_PASSWORD
+
+
+async def test_hassio_discovery_startup(hass, aioclient_mock, hassio_client):
+ """Test startup and discovery after event."""
+ aioclient_mock.get(
+ "http://127.0.0.1/discovery", json={
+ 'result': 'ok', 'data': {'discovery': [
+ {
+ "service": "mqtt", "uuid": "test",
+ "addon": "mosquitto", "config":
+ {
+ 'broker': 'mock-broker',
+ 'port': 1883,
+ 'username': 'mock-user',
+ 'password': 'mock-pass',
+ 'protocol': '3.1.1'
+ }
+ }
+ ]}})
+ aioclient_mock.get(
+ "http://127.0.0.1/addons/mosquitto/info", json={
+ 'result': 'ok', 'data': {'name': "Mosquitto Test"}
+ })
+
+ assert aioclient_mock.call_count == 0
+
+ with patch('homeassistant.components.mqtt.'
+ 'config_flow.FlowHandler.async_step_hassio',
+ Mock(return_value=mock_coro({"type": "abort"}))) as mock_mqtt:
+ hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
+ await hass.async_block_till_done()
+
+ assert aioclient_mock.call_count == 2
+ assert mock_mqtt.called
+ mock_mqtt.assert_called_with({
+ 'broker': 'mock-broker', 'port': 1883, 'username': 'mock-user',
+ 'password': 'mock-pass', 'protocol': '3.1.1',
+ 'addon': 'Mosquitto Test',
+ })
+
+
+async def test_hassio_discovery_startup_done(hass, aioclient_mock,
+ hassio_client):
+ """Test startup and discovery with hass discovery."""
+ aioclient_mock.get(
+ "http://127.0.0.1/discovery", json={
+ 'result': 'ok', 'data': {'discovery': [
+ {
+ "service": "mqtt", "uuid": "test",
+ "addon": "mosquitto", "config":
+ {
+ 'broker': 'mock-broker',
+ 'port': 1883,
+ 'username': 'mock-user',
+ 'password': 'mock-pass',
+ 'protocol': '3.1.1'
+ }
+ }
+ ]}})
+ aioclient_mock.get(
+ "http://127.0.0.1/addons/mosquitto/info", json={
+ 'result': 'ok', 'data': {'name': "Mosquitto Test"}
+ })
+
+ with patch('homeassistant.components.hassio.HassIO.update_hass_api',
+ Mock(return_value=mock_coro({"result": "ok"}))), \
+ patch('homeassistant.components.hassio.HassIO.'
+ 'get_homeassistant_info',
+ Mock(side_effect=HassioAPIError())), \
+ patch('homeassistant.components.mqtt.'
+ 'config_flow.FlowHandler.async_step_hassio',
+ Mock(return_value=mock_coro({"type": "abort"}))
+ ) as mock_mqtt:
+ await hass.async_start()
+ await async_setup_component(hass, 'hassio', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+ await hass.async_block_till_done()
+
+ assert aioclient_mock.call_count == 2
+ assert mock_mqtt.called
+ mock_mqtt.assert_called_with({
+ 'broker': 'mock-broker', 'port': 1883, 'username': 'mock-user',
+ 'password': 'mock-pass', 'protocol': '3.1.1',
+ 'addon': 'Mosquitto Test',
+ })
+
+
+async def test_hassio_discovery_webhook(hass, aioclient_mock, hassio_client):
+ """Test discovery webhook."""
+ aioclient_mock.get(
+ "http://127.0.0.1/discovery/testuuid", json={
+ 'result': 'ok', 'data':
+ {
+ "service": "mqtt", "uuid": "test",
+ "addon": "mosquitto", "config":
+ {
+ 'broker': 'mock-broker',
+ 'port': 1883,
+ 'username': 'mock-user',
+ 'password': 'mock-pass',
+ 'protocol': '3.1.1'
+ }
+ }
+ })
+ aioclient_mock.get(
+ "http://127.0.0.1/addons/mosquitto/info", json={
+ 'result': 'ok', 'data': {'name': "Mosquitto Test"}
+ })
+
+ with patch('homeassistant.components.mqtt.'
+ 'config_flow.FlowHandler.async_step_hassio',
+ Mock(return_value=mock_coro({"type": "abort"}))) as mock_mqtt:
+ resp = await hassio_client.post(
+ '/api/hassio_push/discovery/testuuid', headers={
+ HTTP_HEADER_HA_AUTH: API_PASSWORD
+ }, json={
+ "addon": "mosquitto", "service": "mqtt", "uuid": "testuuid"
+ }
+ )
+ await hass.async_block_till_done()
+
+ assert resp.status == 200
+ assert aioclient_mock.call_count == 2
+ assert mock_mqtt.called
+ mock_mqtt.assert_called_with({
+ 'broker': 'mock-broker', 'port': 1883, 'username': 'mock-user',
+ 'password': 'mock-pass', 'protocol': '3.1.1',
+ 'addon': 'Mosquitto Test',
+ })
diff --git a/tests/components/hassio/test_handler.py b/tests/components/hassio/test_handler.py
index 78745489a78..db3917a2201 100644
--- a/tests/components/hassio/test_handler.py
+++ b/tests/components/hassio/test_handler.py
@@ -1,90 +1,118 @@
"""The tests for the hassio component."""
-import asyncio
import aiohttp
+import pytest
+
+from homeassistant.components.hassio.handler import HassioAPIError
-@asyncio.coroutine
-def test_api_ping(hassio_handler, aioclient_mock):
+async def test_api_ping(hassio_handler, aioclient_mock):
"""Test setup with API ping."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'ok'})
- assert (yield from hassio_handler.is_connected())
+ assert (await hassio_handler.is_connected())
assert aioclient_mock.call_count == 1
-@asyncio.coroutine
-def test_api_ping_error(hassio_handler, aioclient_mock):
+async def test_api_ping_error(hassio_handler, aioclient_mock):
"""Test setup with API ping error."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", json={'result': 'error'})
- assert not (yield from hassio_handler.is_connected())
+ assert not (await hassio_handler.is_connected())
assert aioclient_mock.call_count == 1
-@asyncio.coroutine
-def test_api_ping_exeption(hassio_handler, aioclient_mock):
+async def test_api_ping_exeption(hassio_handler, aioclient_mock):
"""Test setup with API ping exception."""
aioclient_mock.get(
"http://127.0.0.1/supervisor/ping", exc=aiohttp.ClientError())
- assert not (yield from hassio_handler.is_connected())
+ assert not (await hassio_handler.is_connected())
assert aioclient_mock.call_count == 1
-@asyncio.coroutine
-def test_api_homeassistant_info(hassio_handler, aioclient_mock):
+async def test_api_homeassistant_info(hassio_handler, aioclient_mock):
"""Test setup with API homeassistant info."""
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'ok', 'data': {'last_version': '10.0'}})
- data = yield from hassio_handler.get_homeassistant_info()
+ data = await hassio_handler.get_homeassistant_info()
assert aioclient_mock.call_count == 1
assert data['last_version'] == "10.0"
-@asyncio.coroutine
-def test_api_homeassistant_info_error(hassio_handler, aioclient_mock):
+async def test_api_homeassistant_info_error(hassio_handler, aioclient_mock):
"""Test setup with API homeassistant info error."""
aioclient_mock.get(
"http://127.0.0.1/homeassistant/info", json={
'result': 'error', 'message': None})
- data = yield from hassio_handler.get_homeassistant_info()
+ with pytest.raises(HassioAPIError):
+ await hassio_handler.get_homeassistant_info()
+
assert aioclient_mock.call_count == 1
- assert data is None
-@asyncio.coroutine
-def test_api_homeassistant_stop(hassio_handler, aioclient_mock):
+async def test_api_homeassistant_stop(hassio_handler, aioclient_mock):
"""Test setup with API HomeAssistant stop."""
aioclient_mock.post(
"http://127.0.0.1/homeassistant/stop", json={'result': 'ok'})
- assert (yield from hassio_handler.stop_homeassistant())
+ assert (await hassio_handler.stop_homeassistant())
assert aioclient_mock.call_count == 1
-@asyncio.coroutine
-def test_api_homeassistant_restart(hassio_handler, aioclient_mock):
+async def test_api_homeassistant_restart(hassio_handler, aioclient_mock):
"""Test setup with API HomeAssistant restart."""
aioclient_mock.post(
"http://127.0.0.1/homeassistant/restart", json={'result': 'ok'})
- assert (yield from hassio_handler.restart_homeassistant())
+ assert (await hassio_handler.restart_homeassistant())
assert aioclient_mock.call_count == 1
-@asyncio.coroutine
-def test_api_homeassistant_config(hassio_handler, aioclient_mock):
- """Test setup with API HomeAssistant restart."""
+async def test_api_homeassistant_config(hassio_handler, aioclient_mock):
+ """Test setup with API HomeAssistant config."""
aioclient_mock.post(
"http://127.0.0.1/homeassistant/check", json={
'result': 'ok', 'data': {'test': 'bla'}})
- data = yield from hassio_handler.check_homeassistant_config()
+ data = await hassio_handler.check_homeassistant_config()
assert data['data']['test'] == 'bla'
assert aioclient_mock.call_count == 1
+
+
+async def test_api_addon_info(hassio_handler, aioclient_mock):
+ """Test setup with API Add-on info."""
+ aioclient_mock.get(
+ "http://127.0.0.1/addons/test/info", json={
+ 'result': 'ok', 'data': {'name': 'bla'}})
+
+ data = await hassio_handler.get_addon_info("test")
+ assert data['name'] == 'bla'
+ assert aioclient_mock.call_count == 1
+
+
+async def test_api_discovery_message(hassio_handler, aioclient_mock):
+ """Test setup with API discovery message."""
+ aioclient_mock.get(
+ "http://127.0.0.1/discovery/test", json={
+ 'result': 'ok', 'data': {"service": "mqtt"}})
+
+ data = await hassio_handler.get_discovery_message("test")
+ assert data['service'] == "mqtt"
+ assert aioclient_mock.call_count == 1
+
+
+async def test_api_retrieve_discovery(hassio_handler, aioclient_mock):
+ """Test setup with API discovery message."""
+ aioclient_mock.get(
+ "http://127.0.0.1/discovery", json={
+ 'result': 'ok', 'data': {'discovery': [{"service": "mqtt"}]}})
+
+ data = await hassio_handler.retrieve_discovery_messages()
+ assert data['discovery'][-1]['service'] == "mqtt"
+ assert aioclient_mock.call_count == 1
diff --git a/tests/components/homekit/__init__.py b/tests/components/homekit/__init__.py
new file mode 100644
index 00000000000..1734367bcdb
--- /dev/null
+++ b/tests/components/homekit/__init__.py
@@ -0,0 +1 @@
+"""Tests for the HomeKit component."""
diff --git a/tests/components/homekit/test_get_accessories.py b/tests/components/homekit/test_get_accessories.py
index 5b76618d460..d5552cce82c 100644
--- a/tests/components/homekit/test_get_accessories.py
+++ b/tests/components/homekit/test_get_accessories.py
@@ -9,7 +9,8 @@ import homeassistant.components.climate as climate
import homeassistant.components.media_player as media_player
from homeassistant.components.homekit import get_accessory, TYPES
from homeassistant.components.homekit.const import (
- CONF_FEATURE_LIST, FEATURE_ON_OFF, TYPE_OUTLET, TYPE_SWITCH)
+ CONF_FEATURE_LIST, FEATURE_ON_OFF, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER,
+ TYPE_SPRINKLER, TYPE_SWITCH, TYPE_VALVE)
from homeassistant.const import (
ATTR_CODE, ATTR_DEVICE_CLASS, ATTR_SUPPORTED_FEATURES,
ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, CONF_TYPE, TEMP_CELSIUS,
@@ -140,6 +141,10 @@ def test_type_sensors(type_name, entity_id, state, attrs):
('Switch', 'script.test', 'on', {}, {}),
('Switch', 'switch.test', 'on', {}, {}),
('Switch', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SWITCH}),
+ ('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_FAUCET}),
+ ('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_VALVE}),
+ ('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SHOWER}),
+ ('Valve', 'switch.test', 'on', {}, {CONF_TYPE: TYPE_SPRINKLER}),
])
def test_type_switches(type_name, entity_id, state, attrs, config):
"""Test if switch types are associated correctly."""
diff --git a/tests/components/homekit/test_homekit.py b/tests/components/homekit/test_homekit.py
index f8afb4a49ab..a831a7e9e5d 100644
--- a/tests/components/homekit/test_homekit.py
+++ b/tests/components/homekit/test_homekit.py
@@ -5,8 +5,8 @@ import pytest
from homeassistant import setup
from homeassistant.components.homekit import (
- generate_aid, HomeKit, STATUS_READY, STATUS_RUNNING,
- STATUS_STOPPED, STATUS_WAIT)
+ generate_aid, HomeKit, MAX_DEVICES, STATUS_READY,
+ STATUS_RUNNING, STATUS_STOPPED, STATUS_WAIT)
from homeassistant.components.homekit.accessories import HomeBridge
from homeassistant.components.homekit.const import (
CONF_AUTO_START, BRIDGE_NAME, DEFAULT_PORT, DOMAIN, HOMEKIT_FILE,
@@ -173,7 +173,8 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher):
"""Test HomeKit start method."""
pin = b'123-45-678'
homekit = HomeKit(hass, None, None, None, {}, {'cover.demo': {}})
- homekit.bridge = 'bridge'
+ homekit.bridge = Mock()
+ homekit.bridge.accessories = []
homekit.driver = hk_driver
hass.states.async_set('light.demo', 'on')
@@ -190,7 +191,7 @@ async def test_homekit_start(hass, hk_driver, debounce_patcher):
mock_add_acc.assert_called_with(state)
mock_setup_msg.assert_called_with(hass, pin)
- hk_driver_add_acc.assert_called_with('bridge')
+ hk_driver_add_acc.assert_called_with(homekit.bridge)
assert hk_driver_start.called
assert homekit.status == STATUS_RUNNING
@@ -217,3 +218,18 @@ async def test_homekit_stop(hass):
homekit.status = STATUS_RUNNING
await hass.async_add_job(homekit.stop)
assert homekit.driver.stop.called is True
+
+
+async def test_homekit_too_many_accessories(hass, hk_driver):
+ """Test adding too many accessories to HomeKit."""
+ homekit = HomeKit(hass, None, None, None, None, None)
+ homekit.bridge = Mock()
+ homekit.bridge.accessories = range(MAX_DEVICES + 1)
+ homekit.driver = hk_driver
+
+ with patch('pyhap.accessory_driver.AccessoryDriver.start'), \
+ patch('pyhap.accessory_driver.AccessoryDriver.add_accessory'), \
+ patch('homeassistant.components.homekit._LOGGER.warning') \
+ as mock_warn:
+ await hass.async_add_job(homekit.start)
+ assert mock_warn.called is True
diff --git a/tests/components/homekit/test_type_switches.py b/tests/components/homekit/test_type_switches.py
index c2b80226508..bc44a93884a 100644
--- a/tests/components/homekit/test_type_switches.py
+++ b/tests/components/homekit/test_type_switches.py
@@ -1,8 +1,11 @@
"""Test different accessory types: Switches."""
import pytest
-from homeassistant.components.homekit.type_switches import Outlet, Switch
-from homeassistant.const import ATTR_ENTITY_ID, STATE_OFF, STATE_ON
+from homeassistant.components.homekit.const import (
+ TYPE_FAUCET, TYPE_SHOWER, TYPE_SPRINKLER, TYPE_VALVE)
+from homeassistant.components.homekit.type_switches import (
+ Outlet, Switch, Valve)
+from homeassistant.const import ATTR_ENTITY_ID, CONF_TYPE, STATE_OFF, STATE_ON
from homeassistant.core import split_entity_id
from tests.common import async_mock_service
@@ -90,3 +93,70 @@ async def test_switch_set_state(hass, hk_driver, entity_id):
await hass.async_block_till_done()
assert call_turn_off
assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
+
+
+async def test_valve_set_state(hass, hk_driver):
+ """Test if Valve accessory and HA are updated accordingly."""
+ entity_id = 'switch.valve_test'
+
+ hass.states.async_set(entity_id, None)
+ await hass.async_block_till_done()
+
+ acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
+ {CONF_TYPE: TYPE_FAUCET})
+ await hass.async_add_job(acc.run)
+ await hass.async_block_till_done()
+ assert acc.category == 29 # Faucet
+ assert acc.char_valve_type.value == 3 # Water faucet
+
+ acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
+ {CONF_TYPE: TYPE_SHOWER})
+ await hass.async_add_job(acc.run)
+ await hass.async_block_till_done()
+ assert acc.category == 30 # Shower
+ assert acc.char_valve_type.value == 2 # Shower head
+
+ acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
+ {CONF_TYPE: TYPE_SPRINKLER})
+ await hass.async_add_job(acc.run)
+ await hass.async_block_till_done()
+ assert acc.category == 28 # Sprinkler
+ assert acc.char_valve_type.value == 1 # Irrigation
+
+ acc = Valve(hass, hk_driver, 'Valve', entity_id, 2,
+ {CONF_TYPE: TYPE_VALVE})
+ await hass.async_add_job(acc.run)
+ await hass.async_block_till_done()
+
+ assert acc.aid == 2
+ assert acc.category == 29 # Faucet
+
+ assert acc.char_active.value is False
+ assert acc.char_in_use.value is False
+ assert acc.char_valve_type.value == 0 # Generic Valve
+
+ hass.states.async_set(entity_id, STATE_ON)
+ await hass.async_block_till_done()
+ assert acc.char_active.value is True
+ assert acc.char_in_use.value is True
+
+ hass.states.async_set(entity_id, STATE_OFF)
+ await hass.async_block_till_done()
+ assert acc.char_active.value is False
+ assert acc.char_in_use.value is False
+
+ # Set from HomeKit
+ call_turn_on = async_mock_service(hass, 'switch', 'turn_on')
+ call_turn_off = async_mock_service(hass, 'switch', 'turn_off')
+
+ await hass.async_add_job(acc.char_active.client_update_value, True)
+ await hass.async_block_till_done()
+ assert acc.char_in_use.value is True
+ assert call_turn_on
+ assert call_turn_on[0].data[ATTR_ENTITY_ID] == entity_id
+
+ await hass.async_add_job(acc.char_active.client_update_value, False)
+ await hass.async_block_till_done()
+ assert acc.char_in_use.value is False
+ assert call_turn_off
+ assert call_turn_off[0].data[ATTR_ENTITY_ID] == entity_id
diff --git a/tests/components/homekit/test_util.py b/tests/components/homekit/test_util.py
index 9be92b817be..0368dfa642e 100644
--- a/tests/components/homekit/test_util.py
+++ b/tests/components/homekit/test_util.py
@@ -4,7 +4,8 @@ import voluptuous as vol
from homeassistant.components.homekit.const import (
CONF_FEATURE, CONF_FEATURE_LIST, HOMEKIT_NOTIFY_ID, FEATURE_ON_OFF,
- FEATURE_PLAY_PAUSE, TYPE_OUTLET)
+ FEATURE_PLAY_PAUSE, TYPE_FAUCET, TYPE_OUTLET, TYPE_SHOWER, TYPE_SPRINKLER,
+ TYPE_SWITCH, TYPE_VALVE)
from homeassistant.components.homekit.util import (
convert_to_float, density_to_air_quality, dismiss_setup_message,
show_setup_message, temperature_to_homekit, temperature_to_states,
@@ -23,7 +24,8 @@ from tests.common import async_mock_service
def test_validate_entity_config():
"""Test validate entities."""
- configs = [{'invalid_entity_id': {}}, {'demo.test': 1},
+ configs = [None, [], 'string', 12345,
+ {'invalid_entity_id': {}}, {'demo.test': 1},
{'demo.test': 'test'}, {'demo.test': [1, 2]},
{'demo.test': None}, {'demo.test': {CONF_NAME: None}},
{'media_player.test': {CONF_FEATURE_LIST: [
@@ -57,8 +59,19 @@ def test_validate_entity_config():
assert vec({'media_player.demo': config}) == \
{'media_player.demo': {CONF_FEATURE_LIST:
{FEATURE_ON_OFF: {}, FEATURE_PLAY_PAUSE: {}}}}
+
+ assert vec({'switch.demo': {CONF_TYPE: TYPE_FAUCET}}) == \
+ {'switch.demo': {CONF_TYPE: TYPE_FAUCET}}
assert vec({'switch.demo': {CONF_TYPE: TYPE_OUTLET}}) == \
{'switch.demo': {CONF_TYPE: TYPE_OUTLET}}
+ assert vec({'switch.demo': {CONF_TYPE: TYPE_SHOWER}}) == \
+ {'switch.demo': {CONF_TYPE: TYPE_SHOWER}}
+ assert vec({'switch.demo': {CONF_TYPE: TYPE_SPRINKLER}}) == \
+ {'switch.demo': {CONF_TYPE: TYPE_SPRINKLER}}
+ assert vec({'switch.demo': {CONF_TYPE: TYPE_SWITCH}}) == \
+ {'switch.demo': {CONF_TYPE: TYPE_SWITCH}}
+ assert vec({'switch.demo': {CONF_TYPE: TYPE_VALVE}}) == \
+ {'switch.demo': {CONF_TYPE: TYPE_VALVE}}
def test_validate_media_player_features():
diff --git a/tests/components/hue/test_bridge.py b/tests/components/hue/test_bridge.py
index c20cee0d0e8..ceb30091970 100644
--- a/tests/components/hue/test_bridge.py
+++ b/tests/components/hue/test_bridge.py
@@ -34,7 +34,7 @@ async def test_bridge_setup_invalid_username():
side_effect=errors.AuthenticationRequired):
assert await hue_bridge.async_setup() is False
- assert len(hass.async_add_job.mock_calls) == 1
+ assert len(hass.async_create_task.mock_calls) == 1
assert len(hass.config_entries.flow.async_init.mock_calls) == 1
assert hass.config_entries.flow.async_init.mock_calls[0][2]['data'] == {
'host': '1.2.3.4'
@@ -87,7 +87,7 @@ async def test_reset_if_entry_had_wrong_auth():
side_effect=errors.AuthenticationRequired):
assert await hue_bridge.async_setup() is False
- assert len(hass.async_add_job.mock_calls) == 1
+ assert len(hass.async_create_task.mock_calls) == 1
assert await hue_bridge.async_reset()
diff --git a/tests/components/hue/test_init.py b/tests/components/hue/test_init.py
index 5da6d5b709a..1fcc092dd30 100644
--- a/tests/components/hue/test_init.py
+++ b/tests/components/hue/test_init.py
@@ -20,62 +20,6 @@ async def test_setup_with_no_config(hass):
assert hass.data[hue.DOMAIN] == {}
-async def test_setup_with_discovery_no_known_auth(hass, aioclient_mock):
- """Test discovering a bridge and not having known auth."""
- aioclient_mock.get(hue.API_NUPNP, json=[
- {
- 'internalipaddress': '0.0.0.0',
- 'id': 'abcd1234'
- }
- ])
-
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(hue, 'configured_hosts', return_value=[]):
- mock_config_entries.flow.async_init.return_value = mock_coro()
- assert await async_setup_component(hass, hue.DOMAIN, {
- hue.DOMAIN: {}
- }) is True
-
- # Flow started for discovered bridge
- assert len(mock_config_entries.flow.mock_calls) == 1
- assert mock_config_entries.flow.mock_calls[0][2]['data'] == {
- 'host': '0.0.0.0',
- 'path': '.hue_abcd1234.conf',
- }
-
- # Config stored for domain.
- assert hass.data[hue.DOMAIN] == {
- '0.0.0.0': {
- hue.CONF_HOST: '0.0.0.0',
- hue.CONF_FILENAME: '.hue_abcd1234.conf',
- hue.CONF_ALLOW_HUE_GROUPS: hue.DEFAULT_ALLOW_HUE_GROUPS,
- hue.CONF_ALLOW_UNREACHABLE: hue.DEFAULT_ALLOW_UNREACHABLE,
- }
- }
-
-
-async def test_setup_with_discovery_known_auth(hass, aioclient_mock):
- """Test we don't do anything if we discover already configured hub."""
- aioclient_mock.get(hue.API_NUPNP, json=[
- {
- 'internalipaddress': '0.0.0.0',
- 'id': 'abcd1234'
- }
- ])
-
- with patch.object(hass, 'config_entries') as mock_config_entries, \
- patch.object(hue, 'configured_hosts', return_value=['0.0.0.0']):
- assert await async_setup_component(hass, hue.DOMAIN, {
- hue.DOMAIN: {}
- }) is True
-
- # Flow started for discovered bridge
- assert len(mock_config_entries.flow.mock_calls) == 0
-
- # Config stored for domain.
- assert hass.data[hue.DOMAIN] == {}
-
-
async def test_setup_defined_hosts_known_auth(hass):
"""Test we don't initiate a config entry if config bridge is known."""
with patch.object(hass, 'config_entries') as mock_config_entries, \
diff --git a/tests/components/ifttt/__init__.py b/tests/components/ifttt/__init__.py
new file mode 100644
index 00000000000..2fe2f40276c
--- /dev/null
+++ b/tests/components/ifttt/__init__.py
@@ -0,0 +1 @@
+"""Tests for the IFTTT component."""
diff --git a/tests/components/ifttt/test_init.py b/tests/components/ifttt/test_init.py
new file mode 100644
index 00000000000..61d6654ba55
--- /dev/null
+++ b/tests/components/ifttt/test_init.py
@@ -0,0 +1,48 @@
+"""Test the init file of IFTTT."""
+from unittest.mock import Mock, patch
+
+from homeassistant import data_entry_flow
+from homeassistant.core import callback
+from homeassistant.components import ifttt
+
+
+async def test_config_flow_registers_webhook(hass, aiohttp_client):
+ """Test setting up IFTTT and sending webhook."""
+ with patch('homeassistant.util.get_local_ip', return_value='example.com'):
+ result = await hass.config_entries.flow.async_init('ifttt', context={
+ 'source': 'user'
+ })
+ assert result['type'] == data_entry_flow.RESULT_TYPE_FORM, result
+
+ result = await hass.config_entries.flow.async_configure(
+ result['flow_id'], {})
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ webhook_id = result['result'].data['webhook_id']
+
+ ifttt_events = []
+
+ @callback
+ def handle_event(event):
+ """Handle IFTTT event."""
+ ifttt_events.append(event)
+
+ hass.bus.async_listen(ifttt.EVENT_RECEIVED, handle_event)
+
+ client = await aiohttp_client(hass.http.app)
+ await client.post('/api/webhook/{}'.format(webhook_id), json={
+ 'hello': 'ifttt'
+ })
+
+ assert len(ifttt_events) == 1
+ assert ifttt_events[0].data['webhook_id'] == webhook_id
+ assert ifttt_events[0].data['hello'] == 'ifttt'
+
+
+async def test_config_flow_aborts_external_url(hass, aiohttp_client):
+ """Test setting up IFTTT and sending webhook."""
+ hass.config.api = Mock(base_url='http://192.168.1.10')
+ result = await hass.config_entries.flow.async_init('ifttt', context={
+ 'source': 'user'
+ })
+ assert result['type'] == data_entry_flow.RESULT_TYPE_ABORT
+ assert result['reason'] == 'not_internet_accessible'
diff --git a/tests/components/image_processing/common.py b/tests/components/image_processing/common.py
new file mode 100644
index 00000000000..b767884503d
--- /dev/null
+++ b/tests/components/image_processing/common.py
@@ -0,0 +1,23 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.image_processing import DOMAIN, SERVICE_SCAN
+from homeassistant.const import ATTR_ENTITY_ID
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def scan(hass, entity_id=None):
+ """Force process of all cameras or given entity."""
+ hass.add_job(async_scan, hass, entity_id)
+
+
+@callback
+@bind_hass
+def async_scan(hass, entity_id=None):
+ """Force process of all cameras or given entity."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SCAN, data))
diff --git a/tests/components/image_processing/test_init.py b/tests/components/image_processing/test_init.py
index 4240e173b26..7a31b2ffadf 100644
--- a/tests/components/image_processing/test_init.py
+++ b/tests/components/image_processing/test_init.py
@@ -10,6 +10,7 @@ import homeassistant.components.image_processing as ip
from tests.common import (
get_test_home_assistant, get_test_instance_port, assert_setup_component)
+from tests.components.image_processing import common
class TestSetupImageProcessing:
@@ -85,7 +86,7 @@ class TestImageProcessing:
"""Grab an image from camera entity."""
self.hass.start()
- ip.scan(self.hass, entity_id='image_processing.test')
+ common.scan(self.hass, entity_id='image_processing.test')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.test')
@@ -100,7 +101,7 @@ class TestImageProcessing:
"""Try to get image without exists camera."""
self.hass.states.remove('camera.demo_camera')
- ip.scan(self.hass, entity_id='image_processing.test')
+ common.scan(self.hass, entity_id='image_processing.test')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.test')
@@ -152,7 +153,7 @@ class TestImageProcessingAlpr:
"""Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
- ip.scan(self.hass, entity_id='image_processing.demo_alpr')
+ common.scan(self.hass, entity_id='image_processing.demo_alpr')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.demo_alpr')
@@ -171,8 +172,8 @@ class TestImageProcessingAlpr:
"""Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
- ip.scan(self.hass, entity_id='image_processing.demo_alpr')
- ip.scan(self.hass, entity_id='image_processing.demo_alpr')
+ common.scan(self.hass, entity_id='image_processing.demo_alpr')
+ common.scan(self.hass, entity_id='image_processing.demo_alpr')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.demo_alpr')
@@ -195,7 +196,7 @@ class TestImageProcessingAlpr:
"""Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
- ip.scan(self.hass, entity_id='image_processing.demo_alpr')
+ common.scan(self.hass, entity_id='image_processing.demo_alpr')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.demo_alpr')
@@ -254,7 +255,7 @@ class TestImageProcessingFace:
"""Set up and scan a picture and test faces from event."""
aioclient_mock.get(self.url, content=b'image')
- ip.scan(self.hass, entity_id='image_processing.demo_face')
+ common.scan(self.hass, entity_id='image_processing.demo_face')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.demo_face')
@@ -279,7 +280,7 @@ class TestImageProcessingFace:
"""Set up and scan a picture and test faces from event."""
aioclient_mock.get(self.url, content=b'image')
- ip.scan(self.hass, entity_id='image_processing.demo_face')
+ common.scan(self.hass, entity_id='image_processing.demo_face')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.demo_face')
diff --git a/tests/components/image_processing/test_microsoft_face_detect.py b/tests/components/image_processing/test_microsoft_face_detect.py
index 9047c5b8475..c7528c346ee 100644
--- a/tests/components/image_processing/test_microsoft_face_detect.py
+++ b/tests/components/image_processing/test_microsoft_face_detect.py
@@ -9,6 +9,7 @@ import homeassistant.components.microsoft_face as mf
from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
+from tests.components.image_processing import common
class TestMicrosoftFaceDetectSetup:
@@ -146,7 +147,7 @@ class TestMicrosoftFaceDetect:
params={'returnFaceAttributes': "age,gender"}
)
- ip.scan(self.hass, entity_id='image_processing.test_local')
+ common.scan(self.hass, entity_id='image_processing.test_local')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.test_local')
diff --git a/tests/components/image_processing/test_microsoft_face_identify.py b/tests/components/image_processing/test_microsoft_face_identify.py
index 6d3eae38728..892326e5bff 100644
--- a/tests/components/image_processing/test_microsoft_face_identify.py
+++ b/tests/components/image_processing/test_microsoft_face_identify.py
@@ -9,6 +9,7 @@ import homeassistant.components.microsoft_face as mf
from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
+from tests.components.image_processing import common
class TestMicrosoftFaceIdentifySetup:
@@ -150,7 +151,7 @@ class TestMicrosoftFaceIdentify:
text=load_fixture('microsoft_face_identify.json')
)
- ip.scan(self.hass, entity_id='image_processing.test_local')
+ common.scan(self.hass, entity_id='image_processing.test_local')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.test_local')
diff --git a/tests/components/image_processing/test_openalpr_cloud.py b/tests/components/image_processing/test_openalpr_cloud.py
index 2d6015e3fe7..8a71db7fb7b 100644
--- a/tests/components/image_processing/test_openalpr_cloud.py
+++ b/tests/components/image_processing/test_openalpr_cloud.py
@@ -10,6 +10,7 @@ from homeassistant.components.image_processing.openalpr_cloud import (
from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture, mock_coro)
+from tests.components.image_processing import common
class TestOpenAlprCloudSetup:
@@ -160,7 +161,7 @@ class TestOpenAlprCloud:
with patch('homeassistant.components.camera.async_get_image',
return_value=mock_coro(
camera.Image('image/jpeg', b'image'))):
- ip.scan(self.hass, entity_id='image_processing.test_local')
+ common.scan(self.hass, entity_id='image_processing.test_local')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.test_local')
@@ -188,7 +189,7 @@ class TestOpenAlprCloud:
with patch('homeassistant.components.camera.async_get_image',
return_value=mock_coro(
camera.Image('image/jpeg', b'image'))):
- ip.scan(self.hass, entity_id='image_processing.test_local')
+ common.scan(self.hass, entity_id='image_processing.test_local')
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 1
@@ -204,7 +205,7 @@ class TestOpenAlprCloud:
with patch('homeassistant.components.camera.async_get_image',
return_value=mock_coro(
camera.Image('image/jpeg', b'image'))):
- ip.scan(self.hass, entity_id='image_processing.test_local')
+ common.scan(self.hass, entity_id='image_processing.test_local')
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 1
diff --git a/tests/components/image_processing/test_openalpr_local.py b/tests/components/image_processing/test_openalpr_local.py
index 772d66670a0..6d860da3313 100644
--- a/tests/components/image_processing/test_openalpr_local.py
+++ b/tests/components/image_processing/test_openalpr_local.py
@@ -9,6 +9,7 @@ import homeassistant.components.image_processing as ip
from tests.common import (
get_test_home_assistant, assert_setup_component, load_fixture)
+from tests.components.image_processing import common
@asyncio.coroutine
@@ -146,7 +147,7 @@ class TestOpenAlprLocal:
"""Set up and scan a picture and test plates from event."""
aioclient_mock.get(self.url, content=b'image')
- ip.scan(self.hass, entity_id='image_processing.test_local')
+ common.scan(self.hass, entity_id='image_processing.test_local')
self.hass.block_till_done()
state = self.hass.states.get('image_processing.test_local')
diff --git a/tests/components/light/common.py b/tests/components/light/common.py
new file mode 100644
index 00000000000..906e0458dba
--- /dev/null
+++ b/tests/components/light/common.py
@@ -0,0 +1,97 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.light import (
+ ATTR_BRIGHTNESS, ATTR_BRIGHTNESS_PCT, ATTR_COLOR_NAME, ATTR_COLOR_TEMP,
+ ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR, ATTR_KELVIN, ATTR_PROFILE,
+ ATTR_RGB_COLOR, ATTR_TRANSITION, ATTR_WHITE_VALUE, ATTR_XY_COLOR, DOMAIN)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON)
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, entity_id=None, transition=None, brightness=None,
+ brightness_pct=None, rgb_color=None, xy_color=None, hs_color=None,
+ color_temp=None, kelvin=None, white_value=None,
+ profile=None, flash=None, effect=None, color_name=None):
+ """Turn all or specified light on."""
+ hass.add_job(
+ async_turn_on, hass, entity_id, transition, brightness, brightness_pct,
+ rgb_color, xy_color, hs_color, color_temp, kelvin, white_value,
+ profile, flash, effect, color_name)
+
+
+@callback
+@bind_hass
+def async_turn_on(hass, entity_id=None, transition=None, brightness=None,
+ brightness_pct=None, rgb_color=None, xy_color=None,
+ hs_color=None, color_temp=None, kelvin=None,
+ white_value=None, profile=None, flash=None, effect=None,
+ color_name=None):
+ """Turn all or specified light on."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_PROFILE, profile),
+ (ATTR_TRANSITION, transition),
+ (ATTR_BRIGHTNESS, brightness),
+ (ATTR_BRIGHTNESS_PCT, brightness_pct),
+ (ATTR_RGB_COLOR, rgb_color),
+ (ATTR_XY_COLOR, xy_color),
+ (ATTR_HS_COLOR, hs_color),
+ (ATTR_COLOR_TEMP, color_temp),
+ (ATTR_KELVIN, kelvin),
+ (ATTR_WHITE_VALUE, white_value),
+ (ATTR_FLASH, flash),
+ (ATTR_EFFECT, effect),
+ (ATTR_COLOR_NAME, color_name),
+ ] if value is not None
+ }
+
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
+
+
+@bind_hass
+def turn_off(hass, entity_id=None, transition=None):
+ """Turn all or specified light off."""
+ hass.add_job(async_turn_off, hass, entity_id, transition)
+
+
+@callback
+@bind_hass
+def async_turn_off(hass, entity_id=None, transition=None):
+ """Turn all or specified light off."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_TRANSITION, transition),
+ ] if value is not None
+ }
+
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_TURN_OFF, data))
+
+
+@bind_hass
+def toggle(hass, entity_id=None, transition=None):
+ """Toggle all or specified light."""
+ hass.add_job(async_toggle, hass, entity_id, transition)
+
+
+@callback
+@bind_hass
+def async_toggle(hass, entity_id=None, transition=None):
+ """Toggle all or specified light."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ENTITY_ID, entity_id),
+ (ATTR_TRANSITION, transition),
+ ] if value is not None
+ }
+
+ hass.async_add_job(hass.services.async_call(
+ DOMAIN, SERVICE_TOGGLE, data))
diff --git a/tests/components/light/test_demo.py b/tests/components/light/test_demo.py
index db575bba5ba..8711acaa318 100644
--- a/tests/components/light/test_demo.py
+++ b/tests/components/light/test_demo.py
@@ -1,81 +1,79 @@
"""The tests for the demo light component."""
-# pylint: disable=protected-access
-import unittest
+import pytest
-from homeassistant.setup import setup_component
-import homeassistant.components.light as light
+from homeassistant.setup import async_setup_component
+from homeassistant.components import light
-from tests.common import get_test_home_assistant
+from tests.components.light import common
ENTITY_LIGHT = 'light.bed_light'
-class TestDemoLight(unittest.TestCase):
- """Test the demo light."""
-
- # pylint: disable=invalid-name
- def setUp(self):
- """Set up things to be run when tests are started."""
- self.hass = get_test_home_assistant()
- self.assertTrue(setup_component(self.hass, light.DOMAIN, {'light': {
+@pytest.fixture(autouse=True)
+def setup_comp(hass):
+ """Set up demo component."""
+ hass.loop.run_until_complete(async_setup_component(hass, light.DOMAIN, {
+ 'light': {
'platform': 'demo',
}}))
- # pylint: disable=invalid-name
- def tearDown(self):
- """Stop down everything that was started."""
- self.hass.stop()
- def test_state_attributes(self):
- """Test light state attributes."""
- light.turn_on(
- self.hass, ENTITY_LIGHT, xy_color=(.4, .4), brightness=25)
- self.hass.block_till_done()
- state = self.hass.states.get(ENTITY_LIGHT)
- self.assertTrue(light.is_on(self.hass, ENTITY_LIGHT))
- self.assertEqual((0.4, 0.4), state.attributes.get(
- light.ATTR_XY_COLOR))
- self.assertEqual(25, state.attributes.get(light.ATTR_BRIGHTNESS))
- self.assertEqual(
- (255, 234, 164), state.attributes.get(light.ATTR_RGB_COLOR))
- self.assertEqual('rainbow', state.attributes.get(light.ATTR_EFFECT))
- light.turn_on(
- self.hass, ENTITY_LIGHT, rgb_color=(251, 253, 255),
- white_value=254)
- self.hass.block_till_done()
- state = self.hass.states.get(ENTITY_LIGHT)
- self.assertEqual(254, state.attributes.get(light.ATTR_WHITE_VALUE))
- self.assertEqual(
- (250, 252, 255), state.attributes.get(light.ATTR_RGB_COLOR))
- self.assertEqual(
- (0.319, 0.326), state.attributes.get(light.ATTR_XY_COLOR))
- light.turn_on(self.hass, ENTITY_LIGHT, color_temp=400, effect='none')
- self.hass.block_till_done()
- state = self.hass.states.get(ENTITY_LIGHT)
- self.assertEqual(400, state.attributes.get(light.ATTR_COLOR_TEMP))
- self.assertEqual(153, state.attributes.get(light.ATTR_MIN_MIREDS))
- self.assertEqual(500, state.attributes.get(light.ATTR_MAX_MIREDS))
- self.assertEqual('none', state.attributes.get(light.ATTR_EFFECT))
- light.turn_on(self.hass, ENTITY_LIGHT, kelvin=3000, brightness_pct=50)
- self.hass.block_till_done()
- state = self.hass.states.get(ENTITY_LIGHT)
- self.assertEqual(333, state.attributes.get(light.ATTR_COLOR_TEMP))
- self.assertEqual(127, state.attributes.get(light.ATTR_BRIGHTNESS))
+async def test_state_attributes(hass):
+ """Test light state attributes."""
+ common.async_turn_on(
+ hass, ENTITY_LIGHT, xy_color=(.4, .4), brightness=25)
+ await hass.async_block_till_done()
+ state = hass.states.get(ENTITY_LIGHT)
+ assert light.is_on(hass, ENTITY_LIGHT)
+ assert (0.4, 0.4) == state.attributes.get(light.ATTR_XY_COLOR)
+ assert 25 == state.attributes.get(light.ATTR_BRIGHTNESS)
+ assert (255, 234, 164) == state.attributes.get(light.ATTR_RGB_COLOR)
+ assert 'rainbow' == state.attributes.get(light.ATTR_EFFECT)
+ common.async_turn_on(
+ hass, ENTITY_LIGHT, rgb_color=(251, 253, 255),
+ white_value=254)
+ await hass.async_block_till_done()
+ state = hass.states.get(ENTITY_LIGHT)
+ assert 254 == state.attributes.get(light.ATTR_WHITE_VALUE)
+ assert (250, 252, 255) == state.attributes.get(light.ATTR_RGB_COLOR)
+ assert (0.319, 0.326) == state.attributes.get(light.ATTR_XY_COLOR)
+ common.async_turn_on(hass, ENTITY_LIGHT, color_temp=400, effect='none')
+ await hass.async_block_till_done()
+ state = hass.states.get(ENTITY_LIGHT)
+ assert 400 == state.attributes.get(light.ATTR_COLOR_TEMP)
+ assert 153 == state.attributes.get(light.ATTR_MIN_MIREDS)
+ assert 500 == state.attributes.get(light.ATTR_MAX_MIREDS)
+ assert 'none' == state.attributes.get(light.ATTR_EFFECT)
+ common.async_turn_on(hass, ENTITY_LIGHT, kelvin=3000, brightness_pct=50)
+ await hass.async_block_till_done()
+ state = hass.states.get(ENTITY_LIGHT)
+ assert 333 == state.attributes.get(light.ATTR_COLOR_TEMP)
+ assert 127 == state.attributes.get(light.ATTR_BRIGHTNESS)
- def test_turn_off(self):
- """Test light turn off method."""
- light.turn_on(self.hass, ENTITY_LIGHT)
- self.hass.block_till_done()
- self.assertTrue(light.is_on(self.hass, ENTITY_LIGHT))
- light.turn_off(self.hass, ENTITY_LIGHT)
- self.hass.block_till_done()
- self.assertFalse(light.is_on(self.hass, ENTITY_LIGHT))
- def test_turn_off_without_entity_id(self):
- """Test light turn off all lights."""
- light.turn_on(self.hass, ENTITY_LIGHT)
- self.hass.block_till_done()
- self.assertTrue(light.is_on(self.hass, ENTITY_LIGHT))
- light.turn_off(self.hass)
- self.hass.block_till_done()
- self.assertFalse(light.is_on(self.hass, ENTITY_LIGHT))
+async def test_turn_off(hass):
+ """Test light turn off method."""
+ await hass.services.async_call('light', 'turn_on', {
+ 'entity_id': ENTITY_LIGHT
+ }, blocking=True)
+
+ assert light.is_on(hass, ENTITY_LIGHT)
+
+ await hass.services.async_call('light', 'turn_off', {
+ 'entity_id': ENTITY_LIGHT
+ }, blocking=True)
+
+ assert not light.is_on(hass, ENTITY_LIGHT)
+
+
+async def test_turn_off_without_entity_id(hass):
+ """Test light turn off all lights."""
+ await hass.services.async_call('light', 'turn_on', {
+ }, blocking=True)
+
+ assert light.is_on(hass, ENTITY_LIGHT)
+
+ await hass.services.async_call('light', 'turn_off', {
+ }, blocking=True)
+
+ assert not light.is_on(hass, ENTITY_LIGHT)
diff --git a/tests/components/light/test_group.py b/tests/components/light/test_group.py
index 4619e9fb9bd..472bdf42385 100644
--- a/tests/components/light/test_group.py
+++ b/tests/components/light/test_group.py
@@ -3,10 +3,11 @@ from unittest.mock import MagicMock
import asynctest
-from homeassistant.components import light
from homeassistant.components.light import group
from homeassistant.setup import async_setup_component
+from tests.components.light import common
+
async def test_default_state(hass):
"""Test light group default state."""
@@ -300,29 +301,29 @@ async def test_service_calls(hass):
await hass.async_block_till_done()
assert hass.states.get('light.light_group').state == 'on'
- light.async_toggle(hass, 'light.light_group')
+ common.async_toggle(hass, 'light.light_group')
await hass.async_block_till_done()
assert hass.states.get('light.bed_light').state == 'off'
assert hass.states.get('light.ceiling_lights').state == 'off'
assert hass.states.get('light.kitchen_lights').state == 'off'
- light.async_turn_on(hass, 'light.light_group')
+ common.async_turn_on(hass, 'light.light_group')
await hass.async_block_till_done()
assert hass.states.get('light.bed_light').state == 'on'
assert hass.states.get('light.ceiling_lights').state == 'on'
assert hass.states.get('light.kitchen_lights').state == 'on'
- light.async_turn_off(hass, 'light.light_group')
+ common.async_turn_off(hass, 'light.light_group')
await hass.async_block_till_done()
assert hass.states.get('light.bed_light').state == 'off'
assert hass.states.get('light.ceiling_lights').state == 'off'
assert hass.states.get('light.kitchen_lights').state == 'off'
- light.async_turn_on(hass, 'light.light_group', brightness=128,
- effect='Random', rgb_color=(42, 255, 255))
+ common.async_turn_on(hass, 'light.light_group', brightness=128,
+ effect='Random', rgb_color=(42, 255, 255))
await hass.async_block_till_done()
state = hass.states.get('light.bed_light')
diff --git a/tests/components/light/test_init.py b/tests/components/light/test_init.py
index 66dbadb5c38..6253de8cbae 100644
--- a/tests/components/light/test_init.py
+++ b/tests/components/light/test_init.py
@@ -15,6 +15,7 @@ from homeassistant.helpers.intent import IntentHandleError
from tests.common import (
async_mock_service, mock_service, get_test_home_assistant, mock_storage)
+from tests.components.light import common
class TestLight(unittest.TestCase):
@@ -54,7 +55,7 @@ class TestLight(unittest.TestCase):
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- light.turn_on(
+ common.turn_on(
self.hass,
entity_id='entity_id_val',
transition='transition_val',
@@ -88,7 +89,7 @@ class TestLight(unittest.TestCase):
turn_off_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_OFF)
- light.turn_off(
+ common.turn_off(
self.hass, entity_id='entity_id_val', transition='transition_val')
self.hass.block_till_done()
@@ -105,7 +106,7 @@ class TestLight(unittest.TestCase):
toggle_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TOGGLE)
- light.toggle(
+ common.toggle(
self.hass, entity_id='entity_id_val', transition='transition_val')
self.hass.block_till_done()
@@ -135,8 +136,8 @@ class TestLight(unittest.TestCase):
self.assertFalse(light.is_on(self.hass, dev3.entity_id))
# Test basic turn_on, turn_off, toggle services
- light.turn_off(self.hass, entity_id=dev1.entity_id)
- light.turn_on(self.hass, entity_id=dev2.entity_id)
+ common.turn_off(self.hass, entity_id=dev1.entity_id)
+ common.turn_on(self.hass, entity_id=dev2.entity_id)
self.hass.block_till_done()
@@ -144,7 +145,7 @@ class TestLight(unittest.TestCase):
self.assertTrue(light.is_on(self.hass, dev2.entity_id))
# turn on all lights
- light.turn_on(self.hass)
+ common.turn_on(self.hass)
self.hass.block_till_done()
@@ -153,7 +154,7 @@ class TestLight(unittest.TestCase):
self.assertTrue(light.is_on(self.hass, dev3.entity_id))
# turn off all lights
- light.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
@@ -162,7 +163,7 @@ class TestLight(unittest.TestCase):
self.assertFalse(light.is_on(self.hass, dev3.entity_id))
# toggle all lights
- light.toggle(self.hass)
+ common.toggle(self.hass)
self.hass.block_till_done()
@@ -171,7 +172,7 @@ class TestLight(unittest.TestCase):
self.assertTrue(light.is_on(self.hass, dev3.entity_id))
# toggle all lights
- light.toggle(self.hass)
+ common.toggle(self.hass)
self.hass.block_till_done()
@@ -180,12 +181,12 @@ class TestLight(unittest.TestCase):
self.assertFalse(light.is_on(self.hass, dev3.entity_id))
# Ensure all attributes process correctly
- light.turn_on(self.hass, dev1.entity_id,
- transition=10, brightness=20, color_name='blue')
- light.turn_on(
+ common.turn_on(self.hass, dev1.entity_id,
+ transition=10, brightness=20, color_name='blue')
+ common.turn_on(
self.hass, dev2.entity_id, rgb_color=(255, 255, 255),
white_value=255)
- light.turn_on(self.hass, dev3.entity_id, xy_color=(.4, .6))
+ common.turn_on(self.hass, dev3.entity_id, xy_color=(.4, .6))
self.hass.block_till_done()
@@ -211,9 +212,9 @@ class TestLight(unittest.TestCase):
prof_name, prof_h, prof_s, prof_bri = 'relax', 35.932, 69.412, 144
# Test light profiles
- light.turn_on(self.hass, dev1.entity_id, profile=prof_name)
+ common.turn_on(self.hass, dev1.entity_id, profile=prof_name)
# Specify a profile and a brightness attribute to overwrite it
- light.turn_on(
+ common.turn_on(
self.hass, dev2.entity_id,
profile=prof_name, brightness=100)
@@ -232,10 +233,10 @@ class TestLight(unittest.TestCase):
}, data)
# Test bad data
- light.turn_on(self.hass)
- light.turn_on(self.hass, dev1.entity_id, profile="nonexisting")
- light.turn_on(self.hass, dev2.entity_id, xy_color=["bla-di-bla", 5])
- light.turn_on(self.hass, dev3.entity_id, rgb_color=[255, None, 2])
+ common.turn_on(self.hass)
+ common.turn_on(self.hass, dev1.entity_id, profile="nonexisting")
+ common.turn_on(self.hass, dev2.entity_id, xy_color=["bla-di-bla", 5])
+ common.turn_on(self.hass, dev3.entity_id, rgb_color=[255, None, 2])
self.hass.block_till_done()
@@ -249,13 +250,13 @@ class TestLight(unittest.TestCase):
self.assertEqual({}, data)
# faulty attributes will not trigger a service call
- light.turn_on(
+ common.turn_on(
self.hass, dev1.entity_id,
profile=prof_name, brightness='bright')
- light.turn_on(
+ common.turn_on(
self.hass, dev1.entity_id,
rgb_color='yellowish')
- light.turn_on(
+ common.turn_on(
self.hass, dev2.entity_id,
white_value='high')
@@ -299,7 +300,7 @@ class TestLight(unittest.TestCase):
dev1, _, _ = platform.DEVICES
- light.turn_on(self.hass, dev1.entity_id, profile='test')
+ common.turn_on(self.hass, dev1.entity_id, profile='test')
self.hass.block_till_done()
@@ -340,7 +341,7 @@ class TestLight(unittest.TestCase):
))
dev, _, _ = platform.DEVICES
- light.turn_on(self.hass, dev.entity_id)
+ common.turn_on(self.hass, dev.entity_id)
self.hass.block_till_done()
_, data = dev.last_call('turn_on')
self.assertEqual({
@@ -380,7 +381,7 @@ class TestLight(unittest.TestCase):
dev = next(filter(lambda x: x.entity_id == 'light.ceiling_2',
platform.DEVICES))
- light.turn_on(self.hass, dev.entity_id)
+ common.turn_on(self.hass, dev.entity_id)
self.hass.block_till_done()
_, data = dev.last_call('turn_on')
self.assertEqual({
diff --git a/tests/components/light/test_litejet.py b/tests/components/light/test_litejet.py
index 3040c95e0ac..1d7b8ea97fa 100644
--- a/tests/components/light/test_litejet.py
+++ b/tests/components/light/test_litejet.py
@@ -5,9 +5,11 @@ from unittest import mock
from homeassistant import setup
from homeassistant.components import litejet
-from tests.common import get_test_home_assistant
import homeassistant.components.light as light
+from tests.common import get_test_home_assistant
+from tests.components.light import common
+
_LOGGER = logging.getLogger(__name__)
ENTITY_LIGHT = 'light.mock_load_1'
@@ -78,7 +80,7 @@ class TestLiteJetLight(unittest.TestCase):
assert not light.is_on(self.hass, ENTITY_LIGHT)
- light.turn_on(self.hass, ENTITY_LIGHT, brightness=102)
+ common.turn_on(self.hass, ENTITY_LIGHT, brightness=102)
self.hass.block_till_done()
self.mock_lj.activate_load_at.assert_called_with(
ENTITY_LIGHT_NUMBER, 39, 0)
@@ -90,11 +92,11 @@ class TestLiteJetLight(unittest.TestCase):
assert not light.is_on(self.hass, ENTITY_LIGHT)
- light.turn_on(self.hass, ENTITY_LIGHT)
+ common.turn_on(self.hass, ENTITY_LIGHT)
self.hass.block_till_done()
self.mock_lj.activate_load.assert_called_with(ENTITY_LIGHT_NUMBER)
- light.turn_off(self.hass, ENTITY_LIGHT)
+ common.turn_off(self.hass, ENTITY_LIGHT)
self.hass.block_till_done()
self.mock_lj.deactivate_load.assert_called_with(ENTITY_LIGHT_NUMBER)
diff --git a/tests/components/light/test_mqtt.py b/tests/components/light/test_mqtt.py
index 1245411dcc4..118cdb3c995 100644
--- a/tests/components/light/test_mqtt.py
+++ b/tests/components/light/test_mqtt.py
@@ -145,11 +145,14 @@ from unittest.mock import patch
from homeassistant.setup import setup_component
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
-import homeassistant.components.light as light
+from homeassistant.components import light, mqtt
+from homeassistant.components.mqtt.discovery import async_start
import homeassistant.core as ha
+
from tests.common import (
assert_setup_component, get_test_home_assistant, mock_mqtt_component,
- fire_mqtt_message, mock_coro)
+ async_fire_mqtt_message, fire_mqtt_message, mock_coro, MockConfigEntry)
+from tests.components.light import common
class TestLightMQTT(unittest.TestCase):
@@ -505,7 +508,7 @@ class TestLightMQTT(unittest.TestCase):
self.assertEqual(50, state.attributes.get('white_value'))
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
- light.turn_on(self.hass, 'light.test')
+ common.turn_on(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -514,7 +517,7 @@ class TestLightMQTT(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -524,10 +527,10 @@ class TestLightMQTT(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
self.mock_publish.reset_mock()
- light.turn_on(self.hass, 'light.test',
- brightness=50, xy_color=[0.123, 0.123])
- light.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0],
- white_value=80)
+ common.turn_on(self.hass, 'light.test',
+ brightness=50, xy_color=[0.123, 0.123])
+ common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0],
+ white_value=80)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_has_calls([
@@ -565,7 +568,7 @@ class TestLightMQTT(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
- light.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 64])
+ common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 64])
self.hass.block_till_done()
self.mock_publish.async_publish.assert_has_calls([
@@ -713,7 +716,7 @@ class TestLightMQTT(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
- light.turn_on(self.hass, 'light.test', brightness=50)
+ common.turn_on(self.hass, 'light.test', brightness=50)
self.hass.block_till_done()
# Should get the following MQTT messages.
@@ -725,7 +728,7 @@ class TestLightMQTT(unittest.TestCase):
], any_order=True)
self.mock_publish.async_publish.reset_mock()
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -746,7 +749,7 @@ class TestLightMQTT(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
- light.turn_on(self.hass, 'light.test', brightness=50)
+ common.turn_on(self.hass, 'light.test', brightness=50)
self.hass.block_till_done()
# Should get the following MQTT messages.
@@ -758,7 +761,7 @@ class TestLightMQTT(unittest.TestCase):
], any_order=True)
self.mock_publish.async_publish.reset_mock()
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -782,7 +785,7 @@ class TestLightMQTT(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
# Turn on w/ no brightness - should set to max
- light.turn_on(self.hass, 'light.test')
+ common.turn_on(self.hass, 'light.test')
self.hass.block_till_done()
# Should get the following MQTT messages.
@@ -791,7 +794,7 @@ class TestLightMQTT(unittest.TestCase):
'test_light/bright', 255, 0, False)
self.mock_publish.async_publish.reset_mock()
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -799,19 +802,19 @@ class TestLightMQTT(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# Turn on w/ brightness
- light.turn_on(self.hass, 'light.test', brightness=50)
+ common.turn_on(self.hass, 'light.test', brightness=50)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'test_light/bright', 50, 0, False)
self.mock_publish.async_publish.reset_mock()
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
# Turn on w/ just a color to insure brightness gets
# added and sent.
- light.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0])
+ common.turn_on(self.hass, 'light.test', rgb_color=[255, 128, 0])
self.hass.block_till_done()
self.mock_publish.async_publish.assert_has_calls([
@@ -876,3 +879,31 @@ class TestLightMQTT(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
+
+
+async def test_discovery_removal_light(hass, mqtt_mock, caplog):
+ """Test removal of discovered light."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Beer",'
+ ' "status_topic": "test_topic",'
+ ' "command_topic": "test_topic" }'
+ )
+
+ async_fire_mqtt_message(hass, 'homeassistant/light/bla/config',
+ data)
+ await hass.async_block_till_done()
+
+ state = hass.states.get('light.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+
+ async_fire_mqtt_message(hass, 'homeassistant/light/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('light.beer')
+ assert state is None
diff --git a/tests/components/light/test_mqtt_json.py b/tests/components/light/test_mqtt_json.py
index 90875285f17..46db2f61fb3 100644
--- a/tests/components/light/test_mqtt_json.py
+++ b/tests/components/light/test_mqtt_json.py
@@ -97,10 +97,13 @@ from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE,
ATTR_SUPPORTED_FEATURES)
import homeassistant.components.light as light
+from homeassistant.components.mqtt.discovery import async_start
import homeassistant.core as ha
+
from tests.common import (
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message,
- assert_setup_component, mock_coro)
+ assert_setup_component, mock_coro, async_fire_mqtt_message)
+from tests.components.light import common
class TestLightMQTTJSON(unittest.TestCase):
@@ -315,7 +318,7 @@ class TestLightMQTTJSON(unittest.TestCase):
self.assertEqual(191, state.attributes.get(ATTR_SUPPORTED_FEATURES))
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
- light.turn_on(self.hass, 'light.test')
+ common.turn_on(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -324,7 +327,7 @@ class TestLightMQTTJSON(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_ON, state.state)
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -333,9 +336,9 @@ class TestLightMQTTJSON(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_OFF, state.state)
- light.turn_on(self.hass, 'light.test',
- brightness=50, color_temp=155, effect='colorloop',
- white_value=170)
+ common.turn_on(self.hass, 'light.test',
+ brightness=50, color_temp=155, effect='colorloop',
+ white_value=170)
self.hass.block_till_done()
self.assertEqual('test_light_rgb/set',
@@ -361,8 +364,8 @@ class TestLightMQTTJSON(unittest.TestCase):
self.assertEqual(170, state.attributes['white_value'])
# Test a color command
- light.turn_on(self.hass, 'light.test',
- brightness=50, hs_color=(125, 100))
+ common.turn_on(self.hass, 'light.test',
+ brightness=50, hs_color=(125, 100))
self.hass.block_till_done()
self.assertEqual('test_light_rgb/set',
@@ -398,7 +401,7 @@ class TestLightMQTTJSON(unittest.TestCase):
}
})
- light.turn_on(self.hass, 'light.test', hs_color=(180.0, 50.0))
+ common.turn_on(self.hass, 'light.test', hs_color=(180.0, 50.0))
self.hass.block_till_done()
message_json = json.loads(
@@ -427,7 +430,7 @@ class TestLightMQTTJSON(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
self.assertEqual(40, state.attributes.get(ATTR_SUPPORTED_FEATURES))
- light.turn_on(self.hass, 'light.test', flash="short")
+ common.turn_on(self.hass, 'light.test', flash="short")
self.hass.block_till_done()
self.assertEqual('test_light_rgb/set',
@@ -443,7 +446,7 @@ class TestLightMQTTJSON(unittest.TestCase):
self.assertEqual("ON", message_json["state"])
self.mock_publish.async_publish.reset_mock()
- light.turn_on(self.hass, 'light.test', flash="long")
+ common.turn_on(self.hass, 'light.test', flash="long")
self.hass.block_till_done()
self.assertEqual('test_light_rgb/set',
@@ -474,7 +477,7 @@ class TestLightMQTTJSON(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
self.assertEqual(40, state.attributes.get(ATTR_SUPPORTED_FEATURES))
- light.turn_on(self.hass, 'light.test', transition=10)
+ common.turn_on(self.hass, 'light.test', transition=10)
self.hass.block_till_done()
self.assertEqual('test_light_rgb/set',
@@ -490,7 +493,7 @@ class TestLightMQTTJSON(unittest.TestCase):
self.assertEqual("ON", message_json["state"])
# Transition back off
- light.turn_off(self.hass, 'light.test', transition=10)
+ common.turn_off(self.hass, 'light.test', transition=10)
self.hass.block_till_done()
self.assertEqual('test_light_rgb/set',
@@ -667,3 +670,25 @@ class TestLightMQTTJSON(unittest.TestCase):
state = self.hass.states.get('light.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
+
+
+async def test_discovery_removal(hass, mqtt_mock, caplog):
+ """Test removal of discovered mqtt_json lights."""
+ await async_start(hass, 'homeassistant', {})
+ data = (
+ '{ "name": "Beer",'
+ ' "platform": "mqtt_json",'
+ ' "command_topic": "test_topic" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/light/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('light.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/light/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('light.beer')
+ assert state is None
diff --git a/tests/components/light/test_mqtt_template.py b/tests/components/light/test_mqtt_template.py
index 8f92d659b9b..731e7cd4e45 100644
--- a/tests/components/light/test_mqtt_template.py
+++ b/tests/components/light/test_mqtt_template.py
@@ -34,9 +34,11 @@ from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
import homeassistant.components.light as light
import homeassistant.core as ha
+
from tests.common import (
get_test_home_assistant, mock_mqtt_component, fire_mqtt_message,
assert_setup_component, mock_coro)
+from tests.components.light import common
class TestLightMQTTTemplate(unittest.TestCase):
@@ -244,7 +246,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
# turn on the light
- light.turn_on(self.hass, 'light.test')
+ common.turn_on(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -254,7 +256,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.assertEqual(STATE_ON, state.state)
# turn the light off
- light.turn_off(self.hass, 'light.test')
+ common.turn_off(self.hass, 'light.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -264,8 +266,8 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
# turn on the light with brightness, color
- light.turn_on(self.hass, 'light.test', brightness=50,
- rgb_color=[75, 75, 75])
+ common.turn_on(self.hass, 'light.test', brightness=50,
+ rgb_color=[75, 75, 75])
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -273,7 +275,8 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# turn on the light with color temp and white val
- light.turn_on(self.hass, 'light.test', color_temp=200, white_value=139)
+ common.turn_on(self.hass, 'light.test',
+ color_temp=200, white_value=139)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -305,7 +308,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
# short flash
- light.turn_on(self.hass, 'light.test', flash='short')
+ common.turn_on(self.hass, 'light.test', flash='short')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -313,7 +316,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# long flash
- light.turn_on(self.hass, 'light.test', flash='long')
+ common.turn_on(self.hass, 'light.test', flash='long')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -336,7 +339,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.assertEqual(STATE_OFF, state.state)
# transition on
- light.turn_on(self.hass, 'light.test', transition=10)
+ common.turn_on(self.hass, 'light.test', transition=10)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -344,7 +347,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
self.mock_publish.async_publish.reset_mock()
# transition off
- light.turn_off(self.hass, 'light.test', transition=4)
+ common.turn_off(self.hass, 'light.test', transition=4)
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
diff --git a/tests/components/light/test_template.py b/tests/components/light/test_template.py
index cc481fabb5c..5e4dd8555ae 100644
--- a/tests/components/light/test_template.py
+++ b/tests/components/light/test_template.py
@@ -3,12 +3,13 @@ import logging
from homeassistant.core import callback
from homeassistant import setup
-import homeassistant.components as core
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import STATE_ON, STATE_OFF
from tests.common import (
get_test_home_assistant, assert_setup_component)
+from tests.components.light import common
+
_LOGGER = logging.getLogger(__name__)
@@ -378,7 +379,7 @@ class TestTemplateLight:
state = self.hass.states.get('light.test_template_light')
assert state.state == STATE_OFF
- core.light.turn_on(self.hass, 'light.test_template_light')
+ common.turn_on(self.hass, 'light.test_template_light')
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -418,7 +419,7 @@ class TestTemplateLight:
state = self.hass.states.get('light.test_template_light')
assert state.state == STATE_OFF
- core.light.turn_on(self.hass, 'light.test_template_light')
+ common.turn_on(self.hass, 'light.test_template_light')
self.hass.block_till_done()
state = self.hass.states.get('light.test_template_light')
@@ -461,7 +462,7 @@ class TestTemplateLight:
state = self.hass.states.get('light.test_template_light')
assert state.state == STATE_ON
- core.light.turn_off(self.hass, 'light.test_template_light')
+ common.turn_off(self.hass, 'light.test_template_light')
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -498,7 +499,7 @@ class TestTemplateLight:
state = self.hass.states.get('light.test_template_light')
assert state.state == STATE_OFF
- core.light.turn_off(self.hass, 'light.test_template_light')
+ common.turn_off(self.hass, 'light.test_template_light')
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -538,7 +539,7 @@ class TestTemplateLight:
state = self.hass.states.get('light.test_template_light')
assert state.attributes.get('brightness') is None
- core.light.turn_on(
+ common.turn_on(
self.hass, 'light.test_template_light', **{ATTR_BRIGHTNESS: 124})
self.hass.block_till_done()
assert len(self.calls) == 1
diff --git a/tests/components/lock/common.py b/tests/components/lock/common.py
new file mode 100644
index 00000000000..2150b3cb894
--- /dev/null
+++ b/tests/components/lock/common.py
@@ -0,0 +1,45 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.lock import DOMAIN
+from homeassistant.const import (
+ ATTR_CODE, ATTR_ENTITY_ID, SERVICE_LOCK, SERVICE_UNLOCK, SERVICE_OPEN)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def lock(hass, entity_id=None, code=None):
+ """Lock all or specified locks."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_LOCK, data)
+
+
+@bind_hass
+def unlock(hass, entity_id=None, code=None):
+ """Unlock all or specified locks."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_UNLOCK, data)
+
+
+@bind_hass
+def open_lock(hass, entity_id=None, code=None):
+ """Open all or specified locks."""
+ data = {}
+ if code:
+ data[ATTR_CODE] = code
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_OPEN, data)
diff --git a/tests/components/lock/test_demo.py b/tests/components/lock/test_demo.py
index 500cc7f9a6a..255e5307f7a 100644
--- a/tests/components/lock/test_demo.py
+++ b/tests/components/lock/test_demo.py
@@ -5,6 +5,8 @@ from homeassistant.setup import setup_component
from homeassistant.components import lock
from tests.common import get_test_home_assistant, mock_service
+from tests.components.lock import common
+
FRONT = 'lock.front_door'
KITCHEN = 'lock.kitchen_door'
OPENABLE_LOCK = 'lock.openable_lock'
@@ -36,14 +38,14 @@ class TestLockDemo(unittest.TestCase):
def test_locking(self):
"""Test the locking of a lock."""
- lock.lock(self.hass, KITCHEN)
+ common.lock(self.hass, KITCHEN)
self.hass.block_till_done()
self.assertTrue(lock.is_locked(self.hass, KITCHEN))
def test_unlocking(self):
"""Test the unlocking of a lock."""
- lock.unlock(self.hass, FRONT)
+ common.unlock(self.hass, FRONT)
self.hass.block_till_done()
self.assertFalse(lock.is_locked(self.hass, FRONT))
@@ -51,6 +53,6 @@ class TestLockDemo(unittest.TestCase):
def test_opening(self):
"""Test the opening of a lock."""
calls = mock_service(self.hass, lock.DOMAIN, lock.SERVICE_OPEN)
- lock.open_lock(self.hass, OPENABLE_LOCK)
+ common.open_lock(self.hass, OPENABLE_LOCK)
self.hass.block_till_done()
self.assertEqual(1, len(calls))
diff --git a/tests/components/lock/test_mqtt.py b/tests/components/lock/test_mqtt.py
index 4ef8532f39e..4d2378ff9fa 100644
--- a/tests/components/lock/test_mqtt.py
+++ b/tests/components/lock/test_mqtt.py
@@ -5,8 +5,12 @@ from homeassistant.setup import setup_component
from homeassistant.const import (
STATE_LOCKED, STATE_UNLOCKED, STATE_UNAVAILABLE, ATTR_ASSUMED_STATE)
import homeassistant.components.lock as lock
+from homeassistant.components.mqtt.discovery import async_start
+
from tests.common import (
- mock_mqtt_component, fire_mqtt_message, get_test_home_assistant)
+ mock_mqtt_component, async_fire_mqtt_message, fire_mqtt_message,
+ get_test_home_assistant)
+from tests.components.lock import common
class TestLockMQTT(unittest.TestCase):
@@ -67,7 +71,7 @@ class TestLockMQTT(unittest.TestCase):
self.assertEqual(STATE_UNLOCKED, state.state)
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
- lock.lock(self.hass, 'lock.test')
+ common.lock(self.hass, 'lock.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -76,7 +80,7 @@ class TestLockMQTT(unittest.TestCase):
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_LOCKED, state.state)
- lock.unlock(self.hass, 'lock.test')
+ common.unlock(self.hass, 'lock.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -172,3 +176,24 @@ class TestLockMQTT(unittest.TestCase):
state = self.hass.states.get('lock.test')
self.assertEqual(STATE_UNAVAILABLE, state.state)
+
+
+async def test_discovery_removal_lock(hass, mqtt_mock, caplog):
+ """Test removal of discovered lock."""
+ await async_start(hass, 'homeassistant', {})
+ data = (
+ '{ "name": "Beer",'
+ ' "command_topic": "test_topic" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/lock/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('lock.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/lock/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('lock.beer')
+ assert state is None
diff --git a/tests/components/lovelace/__init__.py b/tests/components/lovelace/__init__.py
new file mode 100644
index 00000000000..fea220146ca
--- /dev/null
+++ b/tests/components/lovelace/__init__.py
@@ -0,0 +1 @@
+"""Tests for Lovelace."""
diff --git a/tests/components/lovelace/test_init.py b/tests/components/lovelace/test_init.py
new file mode 100644
index 00000000000..0fde6de902c
--- /dev/null
+++ b/tests/components/lovelace/test_init.py
@@ -0,0 +1,120 @@
+"""Test the Lovelace initialization."""
+from unittest.mock import patch
+
+from homeassistant.exceptions import HomeAssistantError
+from homeassistant.setup import async_setup_component
+from homeassistant.components.websocket_api.const import TYPE_RESULT
+
+
+async def test_deprecated_lovelace_ui(hass, hass_ws_client):
+ """Test lovelace_ui command."""
+ await async_setup_component(hass, 'lovelace')
+ client = await hass_ws_client(hass)
+
+ with patch('homeassistant.components.lovelace.load_yaml',
+ return_value={'hello': 'world'}):
+ await client.send_json({
+ 'id': 5,
+ 'type': 'frontend/lovelace_config',
+ })
+ msg = await client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == TYPE_RESULT
+ assert msg['success']
+ assert msg['result'] == {'hello': 'world'}
+
+
+async def test_deprecated_lovelace_ui_not_found(hass, hass_ws_client):
+ """Test lovelace_ui command cannot find file."""
+ await async_setup_component(hass, 'lovelace')
+ client = await hass_ws_client(hass)
+
+ with patch('homeassistant.components.lovelace.load_yaml',
+ side_effect=FileNotFoundError):
+ await client.send_json({
+ 'id': 5,
+ 'type': 'frontend/lovelace_config',
+ })
+ msg = await client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == TYPE_RESULT
+ assert msg['success'] is False
+ assert msg['error']['code'] == 'file_not_found'
+
+
+async def test_deprecated_lovelace_ui_load_err(hass, hass_ws_client):
+ """Test lovelace_ui command cannot find file."""
+ await async_setup_component(hass, 'lovelace')
+ client = await hass_ws_client(hass)
+
+ with patch('homeassistant.components.lovelace.load_yaml',
+ side_effect=HomeAssistantError):
+ await client.send_json({
+ 'id': 5,
+ 'type': 'frontend/lovelace_config',
+ })
+ msg = await client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == TYPE_RESULT
+ assert msg['success'] is False
+ assert msg['error']['code'] == 'load_error'
+
+
+async def test_lovelace_ui(hass, hass_ws_client):
+ """Test lovelace_ui command."""
+ await async_setup_component(hass, 'lovelace')
+ client = await hass_ws_client(hass)
+
+ with patch('homeassistant.components.lovelace.load_yaml',
+ return_value={'hello': 'world'}):
+ await client.send_json({
+ 'id': 5,
+ 'type': 'lovelace/config',
+ })
+ msg = await client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == TYPE_RESULT
+ assert msg['success']
+ assert msg['result'] == {'hello': 'world'}
+
+
+async def test_lovelace_ui_not_found(hass, hass_ws_client):
+ """Test lovelace_ui command cannot find file."""
+ await async_setup_component(hass, 'lovelace')
+ client = await hass_ws_client(hass)
+
+ with patch('homeassistant.components.lovelace.load_yaml',
+ side_effect=FileNotFoundError):
+ await client.send_json({
+ 'id': 5,
+ 'type': 'lovelace/config',
+ })
+ msg = await client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == TYPE_RESULT
+ assert msg['success'] is False
+ assert msg['error']['code'] == 'file_not_found'
+
+
+async def test_lovelace_ui_load_err(hass, hass_ws_client):
+ """Test lovelace_ui command cannot find file."""
+ await async_setup_component(hass, 'lovelace')
+ client = await hass_ws_client(hass)
+
+ with patch('homeassistant.components.lovelace.load_yaml',
+ side_effect=HomeAssistantError):
+ await client.send_json({
+ 'id': 5,
+ 'type': 'lovelace/config',
+ })
+ msg = await client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == TYPE_RESULT
+ assert msg['success'] is False
+ assert msg['error']['code'] == 'load_error'
diff --git a/tests/components/media_player/common.py b/tests/components/media_player/common.py
new file mode 100644
index 00000000000..3f4d4cb9f24
--- /dev/null
+++ b/tests/components/media_player/common.py
@@ -0,0 +1,150 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.media_player import (
+ ATTR_INPUT_SOURCE, ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE,
+ ATTR_MEDIA_ENQUEUE, ATTR_MEDIA_SEEK_POSITION, ATTR_MEDIA_VOLUME_LEVEL,
+ ATTR_MEDIA_VOLUME_MUTED, DOMAIN, SERVICE_CLEAR_PLAYLIST,
+ SERVICE_PLAY_MEDIA, SERVICE_SELECT_SOURCE)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
+ SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PREVIOUS_TRACK,
+ SERVICE_MEDIA_SEEK, SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON,
+ SERVICE_VOLUME_DOWN, SERVICE_VOLUME_MUTE, SERVICE_VOLUME_SET,
+ SERVICE_VOLUME_UP)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, entity_id=None):
+ """Turn on specified media player or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
+
+
+@bind_hass
+def turn_off(hass, entity_id=None):
+ """Turn off specified media player or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+def toggle(hass, entity_id=None):
+ """Toggle specified media player or all."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
+
+
+@bind_hass
+def volume_up(hass, entity_id=None):
+ """Send the media player the command for volume up."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_VOLUME_UP, data)
+
+
+@bind_hass
+def volume_down(hass, entity_id=None):
+ """Send the media player the command for volume down."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_VOLUME_DOWN, data)
+
+
+@bind_hass
+def mute_volume(hass, mute, entity_id=None):
+ """Send the media player the command for muting the volume."""
+ data = {ATTR_MEDIA_VOLUME_MUTED: mute}
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_VOLUME_MUTE, data)
+
+
+@bind_hass
+def set_volume_level(hass, volume, entity_id=None):
+ """Send the media player the command for setting the volume."""
+ data = {ATTR_MEDIA_VOLUME_LEVEL: volume}
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_VOLUME_SET, data)
+
+
+@bind_hass
+def media_play_pause(hass, entity_id=None):
+ """Send the media player the command for play/pause."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY_PAUSE, data)
+
+
+@bind_hass
+def media_play(hass, entity_id=None):
+ """Send the media player the command for play/pause."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_MEDIA_PLAY, data)
+
+
+@bind_hass
+def media_pause(hass, entity_id=None):
+ """Send the media player the command for pause."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_MEDIA_PAUSE, data)
+
+
+@bind_hass
+def media_next_track(hass, entity_id=None):
+ """Send the media player the command for next track."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_MEDIA_NEXT_TRACK, data)
+
+
+@bind_hass
+def media_previous_track(hass, entity_id=None):
+ """Send the media player the command for prev track."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_MEDIA_PREVIOUS_TRACK, data)
+
+
+@bind_hass
+def media_seek(hass, position, entity_id=None):
+ """Send the media player the command to seek in current playing media."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ data[ATTR_MEDIA_SEEK_POSITION] = position
+ hass.services.call(DOMAIN, SERVICE_MEDIA_SEEK, data)
+
+
+@bind_hass
+def play_media(hass, media_type, media_id, entity_id=None, enqueue=None):
+ """Send the media player the command for playing media."""
+ data = {ATTR_MEDIA_CONTENT_TYPE: media_type,
+ ATTR_MEDIA_CONTENT_ID: media_id}
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ if enqueue:
+ data[ATTR_MEDIA_ENQUEUE] = enqueue
+
+ hass.services.call(DOMAIN, SERVICE_PLAY_MEDIA, data)
+
+
+@bind_hass
+def select_source(hass, source, entity_id=None):
+ """Send the media player the command to select input source."""
+ data = {ATTR_INPUT_SOURCE: source}
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_SELECT_SOURCE, data)
+
+
+@bind_hass
+def clear_playlist(hass, entity_id=None):
+ """Send the media player the command for clear playlist."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.services.call(DOMAIN, SERVICE_CLEAR_PLAYLIST, data)
diff --git a/tests/components/media_player/test_demo.py b/tests/components/media_player/test_demo.py
index 121018e7541..e986ac02065 100644
--- a/tests/components/media_player/test_demo.py
+++ b/tests/components/media_player/test_demo.py
@@ -12,6 +12,7 @@ from homeassistant.helpers.aiohttp_client import DATA_CLIENTSESSION
import requests
from tests.common import get_test_home_assistant, get_test_instance_port
+from tests.components.media_player import common
SERVER_PORT = get_test_instance_port()
HTTP_BASE_URL = 'http://127.0.0.1:{}'.format(SERVER_PORT)
@@ -42,12 +43,12 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(entity_id)
assert 'dvd' == state.attributes.get('source')
- mp.select_source(self.hass, None, entity_id)
+ common.select_source(self.hass, None, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 'dvd' == state.attributes.get('source')
- mp.select_source(self.hass, 'xbox', entity_id)
+ common.select_source(self.hass, 'xbox', entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 'xbox' == state.attributes.get('source')
@@ -59,7 +60,7 @@ class TestDemoMediaPlayer(unittest.TestCase):
{'media_player': {'platform': 'demo'}})
assert self.hass.states.is_state(entity_id, 'playing')
- mp.clear_playlist(self.hass, entity_id)
+ common.clear_playlist(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'off')
@@ -71,34 +72,34 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level')
- mp.set_volume_level(self.hass, None, entity_id)
+ common.set_volume_level(self.hass, None, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 1.0 == state.attributes.get('volume_level')
- mp.set_volume_level(self.hass, 0.5, entity_id)
+ common.set_volume_level(self.hass, 0.5, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 0.5 == state.attributes.get('volume_level')
- mp.volume_down(self.hass, entity_id)
+ common.volume_down(self.hass, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 0.4 == state.attributes.get('volume_level')
- mp.volume_up(self.hass, entity_id)
+ common.volume_up(self.hass, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 0.5 == state.attributes.get('volume_level')
assert False is state.attributes.get('is_volume_muted')
- mp.mute_volume(self.hass, None, entity_id)
+ common.mute_volume(self.hass, None, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert False is state.attributes.get('is_volume_muted')
- mp.mute_volume(self.hass, True, entity_id)
+ common.mute_volume(self.hass, True, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert True is state.attributes.get('is_volume_muted')
@@ -110,16 +111,16 @@ class TestDemoMediaPlayer(unittest.TestCase):
{'media_player': {'platform': 'demo'}})
assert self.hass.states.is_state(entity_id, 'playing')
- mp.turn_off(self.hass, entity_id)
+ common.turn_off(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'off')
assert not mp.is_on(self.hass, entity_id)
- mp.turn_on(self.hass, entity_id)
+ common.turn_on(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'playing')
- mp.toggle(self.hass, entity_id)
+ common.toggle(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'off')
assert not mp.is_on(self.hass, entity_id)
@@ -131,19 +132,19 @@ class TestDemoMediaPlayer(unittest.TestCase):
{'media_player': {'platform': 'demo'}})
assert self.hass.states.is_state(entity_id, 'playing')
- mp.media_pause(self.hass, entity_id)
+ common.media_pause(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'paused')
- mp.media_play_pause(self.hass, entity_id)
+ common.media_play_pause(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'playing')
- mp.media_play_pause(self.hass, entity_id)
+ common.media_play_pause(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'paused')
- mp.media_play(self.hass, entity_id)
+ common.media_play(self.hass, entity_id)
self.hass.block_till_done()
assert self.hass.states.is_state(entity_id, 'playing')
@@ -155,17 +156,17 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(entity_id)
assert 1 == state.attributes.get('media_track')
- mp.media_next_track(self.hass, entity_id)
+ common.media_next_track(self.hass, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 2 == state.attributes.get('media_track')
- mp.media_next_track(self.hass, entity_id)
+ common.media_next_track(self.hass, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 3 == state.attributes.get('media_track')
- mp.media_previous_track(self.hass, entity_id)
+ common.media_previous_track(self.hass, entity_id)
self.hass.block_till_done()
state = self.hass.states.get(entity_id)
assert 2 == state.attributes.get('media_track')
@@ -177,12 +178,12 @@ class TestDemoMediaPlayer(unittest.TestCase):
state = self.hass.states.get(ent_id)
assert 1 == state.attributes.get('media_episode')
- mp.media_next_track(self.hass, ent_id)
+ common.media_next_track(self.hass, ent_id)
self.hass.block_till_done()
state = self.hass.states.get(ent_id)
assert 2 == state.attributes.get('media_episode')
- mp.media_previous_track(self.hass, ent_id)
+ common.media_previous_track(self.hass, ent_id)
self.hass.block_till_done()
state = self.hass.states.get(ent_id)
assert 1 == state.attributes.get('media_episode')
@@ -200,14 +201,14 @@ class TestDemoMediaPlayer(unittest.TestCase):
state.attributes.get('supported_features'))
assert state.attributes.get('media_content_id') is not None
- mp.play_media(self.hass, None, 'some_id', ent_id)
+ common.play_media(self.hass, None, 'some_id', ent_id)
self.hass.block_till_done()
state = self.hass.states.get(ent_id)
assert 0 < (mp.SUPPORT_PLAY_MEDIA &
state.attributes.get('supported_features'))
assert not 'some_id' == state.attributes.get('media_content_id')
- mp.play_media(self.hass, 'youtube', 'some_id', ent_id)
+ common.play_media(self.hass, 'youtube', 'some_id', ent_id)
self.hass.block_till_done()
state = self.hass.states.get(ent_id)
assert 0 < (mp.SUPPORT_PLAY_MEDIA &
@@ -215,10 +216,10 @@ class TestDemoMediaPlayer(unittest.TestCase):
assert 'some_id' == state.attributes.get('media_content_id')
assert not mock_seek.called
- mp.media_seek(self.hass, None, ent_id)
+ common.media_seek(self.hass, None, ent_id)
self.hass.block_till_done()
assert not mock_seek.called
- mp.media_seek(self.hass, 100, ent_id)
+ common.media_seek(self.hass, 100, ent_id)
self.hass.block_till_done()
assert mock_seek.called
diff --git a/tests/components/media_player/test_init.py b/tests/components/media_player/test_init.py
index 5d632d4de0b..808c6e4f50f 100644
--- a/tests/components/media_player/test_init.py
+++ b/tests/components/media_player/test_init.py
@@ -3,7 +3,7 @@ import base64
from unittest.mock import patch
from homeassistant.setup import async_setup_component
-from homeassistant.components import websocket_api
+from homeassistant.components.websocket_api.const import TYPE_RESULT
from tests.common import mock_coro
@@ -30,7 +30,7 @@ async def test_get_panels(hass, hass_ws_client):
msg = await client.receive_json()
assert msg['id'] == 5
- assert msg['type'] == websocket_api.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
assert msg['result']['content_type'] == 'image/jpeg'
assert msg['result']['content'] == \
diff --git a/tests/components/media_player/test_samsungtv.py b/tests/components/media_player/test_samsungtv.py
index 2fedfb6a65e..5551a86df05 100644
--- a/tests/components/media_player/test_samsungtv.py
+++ b/tests/components/media_player/test_samsungtv.py
@@ -128,6 +128,23 @@ class TestSamsungTv(unittest.TestCase):
self.device.update()
self.assertEqual(STATE_OFF, self.device._state)
+ @mock.patch(
+ 'homeassistant.components.media_player.samsungtv.subprocess.Popen'
+ )
+ def test_timeout(self, mocked_popen):
+ """Test timeout use."""
+ ping = mock.Mock()
+ mocked_popen.return_value = ping
+ ping.returncode = 0
+ self.device.update()
+ expected_timeout = self.device._config['timeout']
+ timeout_arg = '-W{}'.format(expected_timeout)
+ ping_command = [
+ 'ping', '-n', '-q', '-c3', timeout_arg, 'fake']
+ expected_call = call(ping_command, stderr=-3, stdout=-1)
+ self.assertEqual(mocked_popen.call_args, expected_call)
+ self.assertEqual(STATE_ON, self.device._state)
+
def test_send_key(self):
"""Test for send key."""
self.device.send_key('KEY_POWER')
diff --git a/tests/components/media_player/test_sonos.py b/tests/components/media_player/test_sonos.py
index cb3da3ab899..cfe969a25c4 100644
--- a/tests/components/media_player/test_sonos.py
+++ b/tests/components/media_player/test_sonos.py
@@ -57,7 +57,7 @@ class SoCoMock():
self.is_visible = True
self.volume = 50
self.mute = False
- self.play_mode = 'NORMAL'
+ self.shuffle = False
self.night_mode = False
self.dialog_mode = False
self.music_library = MusicLibraryMock()
diff --git a/tests/components/mqtt/test_config_flow.py b/tests/components/mqtt/test_config_flow.py
index 9f6be60c68b..08bb4e54a39 100644
--- a/tests/components/mqtt/test_config_flow.py
+++ b/tests/components/mqtt/test_config_flow.py
@@ -5,7 +5,7 @@ import pytest
from homeassistant.setup import async_setup_component
-from tests.common import mock_coro
+from tests.common import mock_coro, MockConfigEntry
@pytest.fixture(autouse=True)
@@ -88,3 +88,67 @@ async def test_manual_config_set(hass, mock_try_connection,
result = await hass.config_entries.flow.async_init(
'mqtt', context={'source': 'user'})
assert result['type'] == 'abort'
+
+
+async def test_user_single_instance(hass):
+ """Test we only allow a single config flow."""
+ MockConfigEntry(domain='mqtt').add_to_hass(hass)
+
+ result = await hass.config_entries.flow.async_init(
+ 'mqtt', context={'source': 'user'})
+ assert result['type'] == 'abort'
+ assert result['reason'] == 'single_instance_allowed'
+
+
+async def test_hassio_single_instance(hass):
+ """Test we only allow a single config flow."""
+ MockConfigEntry(domain='mqtt').add_to_hass(hass)
+
+ result = await hass.config_entries.flow.async_init(
+ 'mqtt', context={'source': 'hassio'})
+ assert result['type'] == 'abort'
+ assert result['reason'] == 'single_instance_allowed'
+
+
+async def test_hassio_confirm(hass, mock_try_connection,
+ mock_finish_setup):
+ """Test we can finish a config flow."""
+ mock_try_connection.return_value = True
+
+ result = await hass.config_entries.flow.async_init(
+ 'mqtt',
+ data={
+ 'addon': 'Mock Addon',
+ 'broker': 'mock-broker',
+ 'port': 1883,
+ 'username': 'mock-user',
+ 'password': 'mock-pass',
+ 'protocol': '3.1.1'
+ },
+ context={'source': 'hassio'}
+ )
+ assert result['type'] == 'form'
+ assert result['step_id'] == 'hassio_confirm'
+ assert result['description_placeholders'] == {
+ 'addon': 'Mock Addon',
+ }
+
+ result = await hass.config_entries.flow.async_configure(
+ result['flow_id'], {
+ 'discovery': True,
+ }
+ )
+
+ assert result['type'] == 'create_entry'
+ assert result['result'].data == {
+ 'broker': 'mock-broker',
+ 'port': 1883,
+ 'username': 'mock-user',
+ 'password': 'mock-pass',
+ 'protocol': '3.1.1',
+ 'discovery': True,
+ }
+ # Check we tried the connection
+ assert len(mock_try_connection.mock_calls) == 1
+ # Check config entry got setup
+ assert len(mock_finish_setup.mock_calls) == 1
diff --git a/tests/components/mqtt/test_discovery.py b/tests/components/mqtt/test_discovery.py
index 6de277eb48d..36b022de7a6 100644
--- a/tests/components/mqtt/test_discovery.py
+++ b/tests/components/mqtt/test_discovery.py
@@ -2,18 +2,23 @@
import asyncio
from unittest.mock import patch
+from homeassistant.components import mqtt
from homeassistant.components.mqtt.discovery import async_start, \
ALREADY_DISCOVERED
-from tests.common import async_fire_mqtt_message, mock_coro
+from tests.common import async_fire_mqtt_message, mock_coro, MockConfigEntry
@asyncio.coroutine
def test_subscribing_config_topic(hass, mqtt_mock):
"""Test setting up discovery."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
+ mqtt.CONF_BROKER: 'test-broker'
+ })
+
hass_config = {}
discovery_topic = 'homeassistant'
- yield from async_start(hass, discovery_topic, hass_config)
+ yield from async_start(hass, discovery_topic, hass_config, entry)
assert mqtt_mock.async_subscribe.called
call_args = mqtt_mock.async_subscribe.mock_calls[0][1]
@@ -25,8 +30,12 @@ def test_subscribing_config_topic(hass, mqtt_mock):
@asyncio.coroutine
def test_invalid_topic(mock_load_platform, hass, mqtt_mock):
"""Test sending to invalid topic."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
+ mqtt.CONF_BROKER: 'test-broker'
+ })
+
mock_load_platform.return_value = mock_coro()
- yield from async_start(hass, 'homeassistant', {})
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/not_config',
'{}')
@@ -38,8 +47,12 @@ def test_invalid_topic(mock_load_platform, hass, mqtt_mock):
@asyncio.coroutine
def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog):
"""Test sending in invalid JSON."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN, data={
+ mqtt.CONF_BROKER: 'test-broker'
+ })
+
mock_load_platform.return_value = mock_coro()
- yield from async_start(hass, 'homeassistant', {})
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'not json')
@@ -52,10 +65,12 @@ def test_invalid_json(mock_load_platform, hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog):
"""Test for a valid component."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
invalid_component = "timer"
mock_load_platform.return_value = mock_coro()
- yield from async_start(hass, 'homeassistant', {})
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/{}/bla/config'.format(
invalid_component
@@ -73,7 +88,9 @@ def test_only_valid_components(mock_load_platform, hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_correct_config_discovery(hass, mqtt_mock, caplog):
"""Test sending in correct JSON."""
- yield from async_start(hass, 'homeassistant', {})
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }')
@@ -89,7 +106,9 @@ def test_correct_config_discovery(hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_discover_fan(hass, mqtt_mock, caplog):
"""Test discovering an MQTT fan."""
- yield from async_start(hass, 'homeassistant', {})
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/fan/bla/config',
('{ "name": "Beer",'
@@ -106,7 +125,9 @@ def test_discover_fan(hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_discover_climate(hass, mqtt_mock, caplog):
"""Test discovering an MQTT climate component."""
- yield from async_start(hass, 'homeassistant', {})
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ yield from async_start(hass, 'homeassistant', {}, entry)
data = (
'{ "name": "ClimateTest",'
@@ -127,7 +148,9 @@ def test_discover_climate(hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
"""Test discovering an MQTT alarm control panel component."""
- yield from async_start(hass, 'homeassistant', {})
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ yield from async_start(hass, 'homeassistant', {}, entry)
data = (
'{ "name": "AlarmControlPanelTest",'
@@ -149,7 +172,9 @@ def test_discover_alarm_control_panel(hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
"""Test sending in correct JSON with optional node_id included."""
- yield from async_start(hass, 'homeassistant', {})
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/my_node_id/bla'
'/config', '{ "name": "Beer" }')
@@ -165,7 +190,9 @@ def test_discovery_incl_nodeid(hass, mqtt_mock, caplog):
@asyncio.coroutine
def test_non_duplicate_discovery(hass, mqtt_mock, caplog):
"""Test for a non duplicate component."""
- yield from async_start(hass, 'homeassistant', {})
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+
+ yield from async_start(hass, 'homeassistant', {}, entry)
async_fire_mqtt_message(hass, 'homeassistant/binary_sensor/bla/config',
'{ "name": "Beer" }')
@@ -181,31 +208,3 @@ def test_non_duplicate_discovery(hass, mqtt_mock, caplog):
assert state_duplicate is None
assert 'Component has already been discovered: ' \
'binary_sensor bla' in caplog.text
-
-
-@asyncio.coroutine
-def test_discovery_removal(hass, mqtt_mock, caplog):
- """Test expansion of abbreviated discovery payload."""
- yield from async_start(hass, 'homeassistant', {})
-
- data = (
- '{ "name": "Beer",'
- ' "status_topic": "test_topic",'
- ' "command_topic": "test_topic" }'
- )
-
- async_fire_mqtt_message(hass, 'homeassistant/switch/bla/config',
- data)
- yield from hass.async_block_till_done()
-
- state = hass.states.get('switch.beer')
- assert state is not None
- assert state.name == 'Beer'
-
- async_fire_mqtt_message(hass, 'homeassistant/switch/bla/config',
- '')
- yield from hass.async_block_till_done()
- yield from hass.async_block_till_done()
-
- state = hass.states.get('switch.beer')
- assert state is None
diff --git a/tests/components/notify/common.py b/tests/components/notify/common.py
new file mode 100644
index 00000000000..42b4b35b63f
--- /dev/null
+++ b/tests/components/notify/common.py
@@ -0,0 +1,24 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.notify import (
+ ATTR_DATA, ATTR_MESSAGE, ATTR_TITLE, DOMAIN, SERVICE_NOTIFY)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def send_message(hass, message, title=None, data=None):
+ """Send a notification message."""
+ info = {
+ ATTR_MESSAGE: message
+ }
+
+ if title is not None:
+ info[ATTR_TITLE] = title
+
+ if data is not None:
+ info[ATTR_DATA] = data
+
+ hass.services.call(DOMAIN, SERVICE_NOTIFY, info)
diff --git a/tests/components/notify/test_demo.py b/tests/components/notify/test_demo.py
index f9c107a447e..3be2ddcb86b 100644
--- a/tests/components/notify/test_demo.py
+++ b/tests/components/notify/test_demo.py
@@ -7,7 +7,9 @@ from homeassistant.setup import setup_component
from homeassistant.components.notify import demo
from homeassistant.core import callback
from homeassistant.helpers import discovery, script
+
from tests.common import assert_setup_component, get_test_home_assistant
+from tests.components.notify import common
CONFIG = {
notify.DOMAIN: {
@@ -79,7 +81,7 @@ class TestNotifyDemo(unittest.TestCase):
def test_sending_none_message(self):
"""Test send with None as message."""
self._setup_notify()
- notify.send_message(self.hass, None)
+ common.send_message(self.hass, None)
self.hass.block_till_done()
self.assertTrue(len(self.events) == 0)
@@ -87,7 +89,7 @@ class TestNotifyDemo(unittest.TestCase):
"""Send a templated message."""
self._setup_notify()
self.hass.states.set('sensor.temperature', 10)
- notify.send_message(self.hass, '{{ states.sensor.temperature.state }}',
+ common.send_message(self.hass, '{{ states.sensor.temperature.state }}',
'{{ states.sensor.temperature.name }}')
self.hass.block_till_done()
last_event = self.events[-1]
@@ -97,7 +99,7 @@ class TestNotifyDemo(unittest.TestCase):
def test_method_forwards_correct_data(self):
"""Test that all data from the service gets forwarded to service."""
self._setup_notify()
- notify.send_message(self.hass, 'my message', 'my title',
+ common.send_message(self.hass, 'my message', 'my title',
{'hello': 'world'})
self.hass.block_till_done()
self.assertTrue(len(self.events) == 1)
diff --git a/tests/components/notify/test_yessssms.py b/tests/components/notify/test_yessssms.py
new file mode 100644
index 00000000000..42dd7be1aca
--- /dev/null
+++ b/tests/components/notify/test_yessssms.py
@@ -0,0 +1,208 @@
+"""The tests for the notify yessssms platform."""
+import unittest
+import requests_mock
+from homeassistant.components.notify import yessssms
+
+
+class TestNotifyYesssSMS(unittest.TestCase):
+ """Test the yessssms notify."""
+
+ def setUp(self): # pylint: disable=invalid-name
+ """Set up things to be run when tests are started."""
+ login = "06641234567"
+ passwd = "testpasswd"
+ recipient = "06501234567"
+ self.yessssms = yessssms.YesssSMSNotificationService(
+ login, passwd, recipient)
+
+ @requests_mock.Mocker()
+ def test_login_error(self, mock):
+ """Test login that fails."""
+ mock.register_uri(
+ requests_mock.POST,
+ # pylint: disable=protected-access
+ self.yessssms.yesss._login_url,
+ status_code=200,
+ text="BlaBlaBlaLogin nicht erfolgreichBlaBla"
+ )
+
+ message = "Testing YesssSMS platform :)"
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR'):
+ self.yessssms.send_message(message)
+ self.assertTrue(mock.called)
+ self.assertEqual(mock.call_count, 1)
+
+ def test_empty_message_error(self):
+ """Test for an empty SMS message error."""
+ message = ""
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR'):
+ self.yessssms.send_message(message)
+
+ @requests_mock.Mocker()
+ def test_error_account_suspended(self, mock):
+ """Test login that fails after multiple attempts."""
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._login_url,
+ status_code=200,
+ text="BlaBlaBlaLogin nicht erfolgreichBlaBla"
+ )
+
+ message = "Testing YesssSMS platform :)"
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR'):
+ self.yessssms.send_message(message)
+ self.assertTrue(mock.called)
+ self.assertEqual(mock.call_count, 1)
+
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._login_url,
+ status_code=200,
+ text="Wegen 3 ungültigen Login-Versuchen ist Ihr Account für "
+ "eine Stunde gesperrt."
+ )
+
+ message = "Testing YesssSMS platform :)"
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR'):
+ self.yessssms.send_message(message)
+ self.assertTrue(mock.called)
+ self.assertEqual(mock.call_count, 2)
+
+ def test_error_account_suspended_2(self):
+ """Test login that fails after multiple attempts."""
+ message = "Testing YesssSMS platform :)"
+ # pylint: disable=protected-access
+ self.yessssms.yesss._suspended = True
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR') as context:
+ self.yessssms.send_message(message)
+ self.assertIn("Account is suspended, cannot send SMS.",
+ context.output[0])
+
+ @requests_mock.Mocker()
+ def test_send_message(self, mock):
+ """Test send message."""
+ message = "Testing YesssSMS platform :)"
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._login_url,
+ status_code=302,
+ # pylint: disable=protected-access
+ headers={'location': self.yessssms.yesss._kontomanager}
+ )
+ # pylint: disable=protected-access
+ login = self.yessssms.yesss._logindata['login_rufnummer']
+ mock.register_uri(
+ 'GET',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._kontomanager,
+ status_code=200,
+ text="test..." + login + ""
+ )
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._websms_url,
+ status_code=200,
+ text="Ihre SMS wurde erfolgreich verschickt!
"
+ )
+ mock.register_uri(
+ 'GET',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._logout_url,
+ status_code=200,
+ )
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='INFO') as context:
+ self.yessssms.send_message(message)
+ self.assertIn("SMS sent", context.output[0])
+ self.assertTrue(mock.called)
+ self.assertEqual(mock.call_count, 4)
+ self.assertIn(mock.last_request.scheme + "://" +
+ mock.last_request.hostname +
+ mock.last_request.path + "?" +
+ mock.last_request.query,
+ # pylint: disable=protected-access
+ self.yessssms.yesss._logout_url)
+
+ def test_no_recipient_error(self):
+ """Test for missing/empty recipient."""
+ message = "Testing YesssSMS platform :)"
+ # pylint: disable=protected-access
+ self.yessssms._recipient = ""
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR') as context:
+ self.yessssms.send_message(message)
+
+ self.assertIn("You need to provide a recipient for SMS notification",
+ context.output[0])
+
+ @requests_mock.Mocker()
+ def test_sms_sending_error(self, mock):
+ """Test sms sending error."""
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._login_url,
+ status_code=302,
+ # pylint: disable=protected-access
+ headers={'location': self.yessssms.yesss._kontomanager}
+ )
+ # pylint: disable=protected-access
+ login = self.yessssms.yesss._logindata['login_rufnummer']
+ mock.register_uri(
+ 'GET',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._kontomanager,
+ status_code=200,
+ text="test..." + login + ""
+ )
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._websms_url,
+ status_code=500
+ )
+
+ message = "Testing YesssSMS platform :)"
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR') as context:
+ self.yessssms.send_message(message)
+
+ self.assertTrue(mock.called)
+ self.assertEqual(mock.call_count, 3)
+ self.assertIn("YesssSMS: error sending SMS", context.output[0])
+
+ @requests_mock.Mocker()
+ def test_connection_error(self, mock):
+ """Test connection error."""
+ mock.register_uri(
+ 'POST',
+ # pylint: disable=protected-access
+ self.yessssms.yesss._login_url,
+ exc=ConnectionError
+ )
+
+ message = "Testing YesssSMS platform :)"
+
+ with self.assertLogs("homeassistant.components.notify",
+ level='ERROR') as context:
+ self.yessssms.send_message(message)
+
+ self.assertTrue(mock.called)
+ self.assertEqual(mock.call_count, 1)
+ self.assertIn("unable to connect", context.output[0])
diff --git a/tests/components/persistent_notification/test_init.py b/tests/components/persistent_notification/test_init.py
index 6acc796a108..43b91e2622a 100644
--- a/tests/components/persistent_notification/test_init.py
+++ b/tests/components/persistent_notification/test_init.py
@@ -1,5 +1,5 @@
"""The tests for the persistent notification component."""
-from homeassistant.components import websocket_api
+from homeassistant.components.websocket_api.const import TYPE_RESULT
from homeassistant.setup import setup_component, async_setup_component
import homeassistant.components.persistent_notification as pn
@@ -41,6 +41,7 @@ class TestPersistentNotification:
assert notification['status'] == pn.STATUS_UNREAD
assert notification['message'] == 'Hello World 2'
assert notification['title'] == '2 beers'
+ assert notification['created_at'] is not None
notifications.clear()
def test_create_notification_id(self):
@@ -151,7 +152,7 @@ async def test_ws_get_notifications(hass, hass_ws_client):
})
msg = await client.receive_json()
assert msg['id'] == 5
- assert msg['type'] == websocket_api.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
notifications = msg['result']
assert len(notifications) == 0
@@ -165,7 +166,7 @@ async def test_ws_get_notifications(hass, hass_ws_client):
})
msg = await client.receive_json()
assert msg['id'] == 6
- assert msg['type'] == websocket_api.TYPE_RESULT
+ assert msg['type'] == TYPE_RESULT
assert msg['success']
notifications = msg['result']
assert len(notifications) == 1
@@ -174,6 +175,7 @@ async def test_ws_get_notifications(hass, hass_ws_client):
assert notification['message'] == 'test'
assert notification['title'] is None
assert notification['status'] == pn.STATUS_UNREAD
+ assert notification['created_at'] is not None
# Mark Read
await hass.services.async_call(pn.DOMAIN, pn.SERVICE_MARK_READ, {
diff --git a/tests/components/remote/common.py b/tests/components/remote/common.py
new file mode 100644
index 00000000000..d03cf5d6d16
--- /dev/null
+++ b/tests/components/remote/common.py
@@ -0,0 +1,55 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.remote import (
+ ATTR_ACTIVITY, ATTR_COMMAND, ATTR_DELAY_SECS, ATTR_DEVICE,
+ ATTR_NUM_REPEATS, DOMAIN, SERVICE_SEND_COMMAND)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, activity=None, entity_id=None):
+ """Turn all or specified remote on."""
+ data = {
+ key: value for key, value in [
+ (ATTR_ACTIVITY, activity),
+ (ATTR_ENTITY_ID, entity_id),
+ ] if value is not None}
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
+
+
+@bind_hass
+def turn_off(hass, activity=None, entity_id=None):
+ """Turn all or specified remote off."""
+ data = {}
+ if activity:
+ data[ATTR_ACTIVITY] = activity
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+def send_command(hass, command, entity_id=None, device=None,
+ num_repeats=None, delay_secs=None):
+ """Send a command to a device."""
+ data = {ATTR_COMMAND: command}
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ if device:
+ data[ATTR_DEVICE] = device
+
+ if num_repeats:
+ data[ATTR_NUM_REPEATS] = num_repeats
+
+ if delay_secs:
+ data[ATTR_DELAY_SECS] = delay_secs
+
+ hass.services.call(DOMAIN, SERVICE_SEND_COMMAND, data)
diff --git a/tests/components/remote/test_demo.py b/tests/components/remote/test_demo.py
index a0290987ff2..fbf7230c237 100644
--- a/tests/components/remote/test_demo.py
+++ b/tests/components/remote/test_demo.py
@@ -5,7 +5,9 @@ import unittest
from homeassistant.setup import setup_component
import homeassistant.components.remote as remote
from homeassistant.const import STATE_ON, STATE_OFF
+
from tests.common import get_test_home_assistant
+from tests.components.remote import common
ENTITY_ID = 'remote.remote_one'
@@ -28,22 +30,22 @@ class TestDemoRemote(unittest.TestCase):
def test_methods(self):
"""Test if services call the entity methods as expected."""
- remote.turn_on(self.hass, entity_id=ENTITY_ID)
+ common.turn_on(self.hass, entity_id=ENTITY_ID)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ID)
self.assertEqual(state.state, STATE_ON)
- remote.turn_off(self.hass, entity_id=ENTITY_ID)
+ common.turn_off(self.hass, entity_id=ENTITY_ID)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ID)
self.assertEqual(state.state, STATE_OFF)
- remote.turn_on(self.hass, entity_id=ENTITY_ID)
+ common.turn_on(self.hass, entity_id=ENTITY_ID)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ID)
self.assertEqual(state.state, STATE_ON)
- remote.send_command(self.hass, 'test', entity_id=ENTITY_ID)
+ common.send_command(self.hass, 'test', entity_id=ENTITY_ID)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_ID)
self.assertEqual(
diff --git a/tests/components/remote/test_init.py b/tests/components/remote/test_init.py
index d98ec941f8b..21a083e3b9a 100644
--- a/tests/components/remote/test_init.py
+++ b/tests/components/remote/test_init.py
@@ -10,6 +10,8 @@ from homeassistant.const import (
import homeassistant.components.remote as remote
from tests.common import mock_service, get_test_home_assistant
+from tests.components.remote import common
+
TEST_PLATFORM = {remote.DOMAIN: {CONF_PLATFORM: 'test'}}
SERVICE_SEND_COMMAND = 'send_command'
@@ -46,7 +48,7 @@ class TestRemote(unittest.TestCase):
turn_on_calls = mock_service(
self.hass, remote.DOMAIN, SERVICE_TURN_ON)
- remote.turn_on(
+ common.turn_on(
self.hass,
entity_id='entity_id_val')
@@ -62,7 +64,7 @@ class TestRemote(unittest.TestCase):
turn_off_calls = mock_service(
self.hass, remote.DOMAIN, SERVICE_TURN_OFF)
- remote.turn_off(
+ common.turn_off(
self.hass, entity_id='entity_id_val')
self.hass.block_till_done()
@@ -79,7 +81,7 @@ class TestRemote(unittest.TestCase):
send_command_calls = mock_service(
self.hass, remote.DOMAIN, SERVICE_SEND_COMMAND)
- remote.send_command(
+ common.send_command(
self.hass, entity_id='entity_id_val',
device='test_device', command=['test_command'],
num_repeats='4', delay_secs='0.6')
diff --git a/tests/components/scene/common.py b/tests/components/scene/common.py
new file mode 100644
index 00000000000..4f8123ca638
--- /dev/null
+++ b/tests/components/scene/common.py
@@ -0,0 +1,19 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.scene import DOMAIN
+from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_ON
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def activate(hass, entity_id=None):
+ """Activate a scene."""
+ data = {}
+
+ if entity_id:
+ data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
diff --git a/tests/components/scene/test_init.py b/tests/components/scene/test_init.py
index 3298d7648d9..81ab5f89c25 100644
--- a/tests/components/scene/test_init.py
+++ b/tests/components/scene/test_init.py
@@ -8,6 +8,8 @@ from homeassistant.components import light, scene
from homeassistant.util import yaml
from tests.common import get_test_home_assistant
+from tests.components.light import common as common_light
+from tests.components.scene import common
class TestScene(unittest.TestCase):
@@ -25,7 +27,7 @@ class TestScene(unittest.TestCase):
self.light_1, self.light_2 = test_light.DEVICES[0:2]
- light.turn_off(
+ common_light.turn_off(
self.hass, [self.light_1.entity_id, self.light_2.entity_id])
self.hass.block_till_done()
@@ -68,7 +70,7 @@ class TestScene(unittest.TestCase):
}]
}))
- scene.activate(self.hass, 'scene.test')
+ common.activate(self.hass, 'scene.test')
self.hass.block_till_done()
self.assertTrue(self.light_1.is_on)
@@ -94,7 +96,7 @@ class TestScene(unittest.TestCase):
doc = yaml.yaml.safe_load(file)
self.assertTrue(setup_component(self.hass, scene.DOMAIN, doc))
- scene.activate(self.hass, 'scene.test')
+ common.activate(self.hass, 'scene.test')
self.hass.block_till_done()
self.assertTrue(self.light_1.is_on)
@@ -117,7 +119,7 @@ class TestScene(unittest.TestCase):
}]
}))
- scene.activate(self.hass, 'scene.test')
+ common.activate(self.hass, 'scene.test')
self.hass.block_till_done()
self.assertTrue(self.light_1.is_on)
diff --git a/tests/components/scene/test_litejet.py b/tests/components/scene/test_litejet.py
index 864ffc41735..57d3c178dbd 100644
--- a/tests/components/scene/test_litejet.py
+++ b/tests/components/scene/test_litejet.py
@@ -5,8 +5,9 @@ from unittest import mock
from homeassistant import setup
from homeassistant.components import litejet
+
from tests.common import get_test_home_assistant
-import homeassistant.components.scene as scene
+from tests.components.scene import common
_LOGGER = logging.getLogger(__name__)
@@ -59,7 +60,7 @@ class TestLiteJetScene(unittest.TestCase):
def test_activate(self):
"""Test activating the scene."""
- scene.activate(self.hass, ENTITY_SCENE)
+ common.activate(self.hass, ENTITY_SCENE)
self.hass.block_till_done()
self.mock_lj.activate_scene.assert_called_once_with(
ENTITY_SCENE_NUMBER)
diff --git a/tests/components/sensor/test_geo_rss_events.py b/tests/components/sensor/test_geo_rss_events.py
index cc57c801430..21538d458bc 100644
--- a/tests/components/sensor/test_geo_rss_events.py
+++ b/tests/components/sensor/test_geo_rss_events.py
@@ -2,26 +2,38 @@
import unittest
from unittest import mock
import sys
+from unittest.mock import MagicMock, patch
-import feedparser
import pytest
+from homeassistant.components import sensor
+from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, ATTR_FRIENDLY_NAME, \
+ EVENT_HOMEASSISTANT_START, ATTR_ICON
from homeassistant.setup import setup_component
-from tests.common import load_fixture, get_test_home_assistant
+from tests.common import get_test_home_assistant, \
+ assert_setup_component, fire_time_changed
import homeassistant.components.sensor.geo_rss_events as geo_rss_events
+import homeassistant.util.dt as dt_util
URL = 'http://geo.rss.local/geo_rss_events.xml'
VALID_CONFIG_WITH_CATEGORIES = {
- 'platform': 'geo_rss_events',
- geo_rss_events.CONF_URL: URL,
- geo_rss_events.CONF_CATEGORIES: [
- 'Category 1',
- 'Category 2'
+ sensor.DOMAIN: [
+ {
+ 'platform': 'geo_rss_events',
+ geo_rss_events.CONF_URL: URL,
+ geo_rss_events.CONF_CATEGORIES: [
+ 'Category 1'
+ ]
+ }
]
}
-VALID_CONFIG_WITHOUT_CATEGORIES = {
- 'platform': 'geo_rss_events',
- geo_rss_events.CONF_URL: URL
+VALID_CONFIG = {
+ sensor.DOMAIN: [
+ {
+ 'platform': 'geo_rss_events',
+ geo_rss_events.CONF_URL: URL
+ }
+ ]
}
@@ -34,119 +46,114 @@ class TestGeoRssServiceUpdater(unittest.TestCase):
def setUp(self):
"""Initialize values for this testcase class."""
self.hass = get_test_home_assistant()
- self.config = VALID_CONFIG_WITHOUT_CATEGORIES
+ # self.config = VALID_CONFIG_WITHOUT_CATEGORIES
def tearDown(self):
"""Stop everything that was started."""
self.hass.stop()
- @mock.patch('feedparser.parse', return_value=feedparser.parse(""))
- def test_setup_with_categories(self, mock_parse):
- """Test the general setup of this sensor."""
- self.config = VALID_CONFIG_WITH_CATEGORIES
- self.assertTrue(
- setup_component(self.hass, 'sensor', {'sensor': self.config}))
- self.assertIsNotNone(
- self.hass.states.get('sensor.event_service_category_1'))
- self.assertIsNotNone(
- self.hass.states.get('sensor.event_service_category_2'))
+ @staticmethod
+ def _generate_mock_feed_entry(external_id, title, distance_to_home,
+ coordinates, category):
+ """Construct a mock feed entry for testing purposes."""
+ feed_entry = MagicMock()
+ feed_entry.external_id = external_id
+ feed_entry.title = title
+ feed_entry.distance_to_home = distance_to_home
+ feed_entry.coordinates = coordinates
+ feed_entry.category = category
+ return feed_entry
- @mock.patch('feedparser.parse', return_value=feedparser.parse(""))
- def test_setup_without_categories(self, mock_parse):
- """Test the general setup of this sensor."""
- self.assertTrue(
- setup_component(self.hass, 'sensor', {'sensor': self.config}))
- self.assertIsNotNone(self.hass.states.get('sensor.event_service_any'))
+ @mock.patch('georss_client.generic_feed.GenericFeed')
+ def test_setup(self, mock_feed):
+ """Test the general setup of the platform."""
+ # Set up some mock feed entries for this test.
+ mock_entry_1 = self._generate_mock_feed_entry('1234', 'Title 1', 15.5,
+ (-31.0, 150.0),
+ 'Category 1')
+ mock_entry_2 = self._generate_mock_feed_entry('2345', 'Title 2', 20.5,
+ (-31.1, 150.1),
+ 'Category 1')
+ mock_feed.return_value.update.return_value = 'OK', [mock_entry_1,
+ mock_entry_2]
- def setup_data(self, url='url'):
- """Set up data object for use by sensors."""
- home_latitude = -33.865
- home_longitude = 151.209444
- radius_in_km = 500
- data = geo_rss_events.GeoRssServiceData(home_latitude,
- home_longitude, url,
- radius_in_km)
- return data
+ utcnow = dt_util.utcnow()
+ # Patching 'utcnow' to gain more control over the timed update.
+ with patch('homeassistant.util.dt.utcnow', return_value=utcnow):
+ with assert_setup_component(1, sensor.DOMAIN):
+ self.assertTrue(setup_component(self.hass, sensor.DOMAIN,
+ VALID_CONFIG))
+ # Artificially trigger update.
+ self.hass.bus.fire(EVENT_HOMEASSISTANT_START)
+ # Collect events.
+ self.hass.block_till_done()
- def test_update_sensor_with_category(self):
- """Test updating sensor object."""
- raw_data = load_fixture('geo_rss_events.xml')
- # Loading raw data from fixture and plug in to data object as URL
- # works since the third-party feedparser library accepts a URL
- # as well as the actual data.
- data = self.setup_data(raw_data)
- category = "Category 1"
- name = "Name 1"
- unit_of_measurement = "Unit 1"
- sensor = geo_rss_events.GeoRssServiceSensor(category,
- data, name,
- unit_of_measurement)
+ all_states = self.hass.states.all()
+ assert len(all_states) == 1
- sensor.update()
- assert sensor.name == "Name 1 Category 1"
- assert sensor.unit_of_measurement == "Unit 1"
- assert sensor.icon == "mdi:alert"
- assert len(sensor._data.events) == 4
- assert sensor.state == 1
- assert sensor.device_state_attributes == {'Title 1': "117km"}
- # Check entries of first hit
- assert sensor._data.events[0][geo_rss_events.ATTR_TITLE] == "Title 1"
- assert sensor._data.events[0][
- geo_rss_events.ATTR_CATEGORY] == "Category 1"
- self.assertAlmostEqual(sensor._data.events[0][
- geo_rss_events.ATTR_DISTANCE], 116.586, 0)
+ state = self.hass.states.get("sensor.event_service_any")
+ self.assertIsNotNone(state)
+ assert state.name == "Event Service Any"
+ assert int(state.state) == 2
+ assert state.attributes == {
+ ATTR_FRIENDLY_NAME: "Event Service Any",
+ ATTR_UNIT_OF_MEASUREMENT: "Events",
+ ATTR_ICON: "mdi:alert",
+ "Title 1": "16km", "Title 2": "20km"}
- def test_update_sensor_without_category(self):
- """Test updating sensor object."""
- raw_data = load_fixture('geo_rss_events.xml')
- data = self.setup_data(raw_data)
- category = None
- name = "Name 2"
- unit_of_measurement = "Unit 2"
- sensor = geo_rss_events.GeoRssServiceSensor(category,
- data, name,
- unit_of_measurement)
+ # Simulate an update - empty data, but successful update,
+ # so no changes to entities.
+ mock_feed.return_value.update.return_value = 'OK_NO_DATA', None
+ fire_time_changed(self.hass, utcnow +
+ geo_rss_events.SCAN_INTERVAL)
+ self.hass.block_till_done()
- sensor.update()
- assert sensor.name == "Name 2 Any"
- assert sensor.unit_of_measurement == "Unit 2"
- assert sensor.icon == "mdi:alert"
- assert len(sensor._data.events) == 4
- assert sensor.state == 4
- assert sensor.device_state_attributes == {'Title 1': "117km",
- 'Title 2': "302km",
- 'Title 3': "204km",
- 'Title 6': "48km"}
+ all_states = self.hass.states.all()
+ assert len(all_states) == 1
+ state = self.hass.states.get("sensor.event_service_any")
+ assert int(state.state) == 2
- def test_update_sensor_without_data(self):
- """Test updating sensor object."""
- data = self.setup_data()
- category = None
- name = "Name 3"
- unit_of_measurement = "Unit 3"
- sensor = geo_rss_events.GeoRssServiceSensor(category,
- data, name,
- unit_of_measurement)
+ # Simulate an update - empty data, removes all entities
+ mock_feed.return_value.update.return_value = 'ERROR', None
+ fire_time_changed(self.hass, utcnow +
+ 2 * geo_rss_events.SCAN_INTERVAL)
+ self.hass.block_till_done()
- sensor.update()
- assert sensor.name == "Name 3 Any"
- assert sensor.unit_of_measurement == "Unit 3"
- assert sensor.icon == "mdi:alert"
- assert len(sensor._data.events) == 0
- assert sensor.state == 0
+ all_states = self.hass.states.all()
+ assert len(all_states) == 1
+ state = self.hass.states.get("sensor.event_service_any")
+ assert int(state.state) == 0
- @mock.patch('feedparser.parse', return_value=None)
- def test_update_sensor_with_none_result(self, parse_function):
- """Test updating sensor object."""
- data = self.setup_data("http://invalid.url/")
- category = None
- name = "Name 4"
- unit_of_measurement = "Unit 4"
- sensor = geo_rss_events.GeoRssServiceSensor(category,
- data, name,
- unit_of_measurement)
+ @mock.patch('georss_client.generic_feed.GenericFeed')
+ def test_setup_with_categories(self, mock_feed):
+ """Test the general setup of the platform."""
+ # Set up some mock feed entries for this test.
+ mock_entry_1 = self._generate_mock_feed_entry('1234', 'Title 1', 15.5,
+ (-31.0, 150.0),
+ 'Category 1')
+ mock_entry_2 = self._generate_mock_feed_entry('2345', 'Title 2', 20.5,
+ (-31.1, 150.1),
+ 'Category 1')
+ mock_feed.return_value.update.return_value = 'OK', [mock_entry_1,
+ mock_entry_2]
- sensor.update()
- assert sensor.name == "Name 4 Any"
- assert sensor.unit_of_measurement == "Unit 4"
- assert sensor.state == 0
+ with assert_setup_component(1, sensor.DOMAIN):
+ self.assertTrue(setup_component(self.hass, sensor.DOMAIN,
+ VALID_CONFIG_WITH_CATEGORIES))
+ # Artificially trigger update.
+ self.hass.bus.fire(EVENT_HOMEASSISTANT_START)
+ # Collect events.
+ self.hass.block_till_done()
+
+ all_states = self.hass.states.all()
+ assert len(all_states) == 1
+
+ state = self.hass.states.get("sensor.event_service_category_1")
+ self.assertIsNotNone(state)
+ assert state.name == "Event Service Category 1"
+ assert int(state.state) == 2
+ assert state.attributes == {
+ ATTR_FRIENDLY_NAME: "Event Service Category 1",
+ ATTR_UNIT_OF_MEASUREMENT: "Events",
+ ATTR_ICON: "mdi:alert",
+ "Title 1": "16km", "Title 2": "20km"}
diff --git a/tests/components/sensor/test_jewish_calendar.py b/tests/components/sensor/test_jewish_calendar.py
index 990f26d6ea7..b67e340a9aa 100644
--- a/tests/components/sensor/test_jewish_calendar.py
+++ b/tests/components/sensor/test_jewish_calendar.py
@@ -1,5 +1,6 @@
"""The tests for the Jewish calendar sensor platform."""
import unittest
+from datetime import time
from datetime import datetime as dt
from unittest.mock import patch
@@ -64,7 +65,7 @@ class TestJewishCalenderSensor(unittest.TestCase):
sensor = JewishCalSensor(
name='test', language='english', sensor_type='date',
latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
- diaspora=False)
+ timezone="UTC", diaspora=False)
with patch('homeassistant.util.dt.now', return_value=test_time):
run_coroutine_threadsafe(
sensor.async_update(),
@@ -77,7 +78,7 @@ class TestJewishCalenderSensor(unittest.TestCase):
sensor = JewishCalSensor(
name='test', language='hebrew', sensor_type='date',
latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
- diaspora=False)
+ timezone="UTC", diaspora=False)
with patch('homeassistant.util.dt.now', return_value=test_time):
run_coroutine_threadsafe(
sensor.async_update(), self.hass.loop).result()
@@ -89,19 +90,31 @@ class TestJewishCalenderSensor(unittest.TestCase):
sensor = JewishCalSensor(
name='test', language='hebrew', sensor_type='holiday_name',
latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
- diaspora=False)
+ timezone="UTC", diaspora=False)
with patch('homeassistant.util.dt.now', return_value=test_time):
run_coroutine_threadsafe(
sensor.async_update(), self.hass.loop).result()
self.assertEqual(sensor.state, "א\' ראש השנה")
+ def test_jewish_calendar_sensor_holiday_name_english(self):
+ """Test Jewish calendar sensor date output in hebrew."""
+ test_time = dt(2018, 9, 10)
+ sensor = JewishCalSensor(
+ name='test', language='english', sensor_type='holiday_name',
+ latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
+ timezone="UTC", diaspora=False)
+ with patch('homeassistant.util.dt.now', return_value=test_time):
+ run_coroutine_threadsafe(
+ sensor.async_update(), self.hass.loop).result()
+ self.assertEqual(sensor.state, "Rosh Hashana I")
+
def test_jewish_calendar_sensor_holyness(self):
"""Test Jewish calendar sensor date output in hebrew."""
test_time = dt(2018, 9, 10)
sensor = JewishCalSensor(
name='test', language='hebrew', sensor_type='holyness',
latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
- diaspora=False)
+ timezone="UTC", diaspora=False)
with patch('homeassistant.util.dt.now', return_value=test_time):
run_coroutine_threadsafe(
sensor.async_update(), self.hass.loop).result()
@@ -113,8 +126,32 @@ class TestJewishCalenderSensor(unittest.TestCase):
sensor = JewishCalSensor(
name='test', language='hebrew', sensor_type='weekly_portion',
latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
- diaspora=False)
+ timezone="UTC", diaspora=False)
with patch('homeassistant.util.dt.now', return_value=test_time):
run_coroutine_threadsafe(
sensor.async_update(), self.hass.loop).result()
self.assertEqual(sensor.state, "פרשת נצבים")
+
+ def test_jewish_calendar_sensor_first_stars_ny(self):
+ """Test Jewish calendar sensor date output in hebrew."""
+ test_time = dt(2018, 9, 8)
+ sensor = JewishCalSensor(
+ name='test', language='hebrew', sensor_type='first_stars',
+ latitude=40.7128, longitude=-74.0060,
+ timezone="America/New_York", diaspora=False)
+ with patch('homeassistant.util.dt.now', return_value=test_time):
+ run_coroutine_threadsafe(
+ sensor.async_update(), self.hass.loop).result()
+ self.assertEqual(sensor.state, time(19, 48))
+
+ def test_jewish_calendar_sensor_first_stars_jerusalem(self):
+ """Test Jewish calendar sensor date output in hebrew."""
+ test_time = dt(2018, 9, 8)
+ sensor = JewishCalSensor(
+ name='test', language='hebrew', sensor_type='first_stars',
+ latitude=self.TEST_LATITUDE, longitude=self.TEST_LONGITUDE,
+ timezone="Asia/Jerusalem", diaspora=False)
+ with patch('homeassistant.util.dt.now', return_value=test_time):
+ run_coroutine_threadsafe(
+ sensor.async_update(), self.hass.loop).result()
+ self.assertEqual(sensor.state, time(19, 21))
diff --git a/tests/components/sensor/test_mqtt.py b/tests/components/sensor/test_mqtt.py
index feef647b7b7..4f70c37e04f 100644
--- a/tests/components/sensor/test_mqtt.py
+++ b/tests/components/sensor/test_mqtt.py
@@ -6,13 +6,15 @@ from unittest.mock import patch
import homeassistant.core as ha
from homeassistant.setup import setup_component, async_setup_component
+from homeassistant.components import mqtt
+from homeassistant.components.mqtt.discovery import async_start
import homeassistant.components.sensor as sensor
from homeassistant.const import EVENT_STATE_CHANGED, STATE_UNAVAILABLE
import homeassistant.util.dt as dt_util
from tests.common import mock_mqtt_component, fire_mqtt_message, \
assert_setup_component, async_fire_mqtt_message, \
- async_mock_mqtt_component
+ async_mock_mqtt_component, MockConfigEntry
from tests.common import get_test_home_assistant, mock_component
@@ -387,3 +389,25 @@ async def test_unique_id(hass):
await hass.async_block_till_done()
assert len(hass.states.async_all()) == 1
+
+
+async def test_discovery_removal_sensor(hass, mqtt_mock, caplog):
+ """Test removal of discovered sensor."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+ data = (
+ '{ "name": "Beer",'
+ ' "status_topic": "test_topic" }'
+ )
+ async_fire_mqtt_message(hass, 'homeassistant/sensor/bla/config',
+ data)
+ await hass.async_block_till_done()
+ state = hass.states.get('sensor.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+ async_fire_mqtt_message(hass, 'homeassistant/sensor/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+ state = hass.states.get('sensor.beer')
+ assert state is None
diff --git a/tests/components/switch/common.py b/tests/components/switch/common.py
new file mode 100644
index 00000000000..8db8e425ddb
--- /dev/null
+++ b/tests/components/switch/common.py
@@ -0,0 +1,39 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.switch import DOMAIN
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON)
+from homeassistant.core import callback
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, entity_id=None):
+ """Turn all or specified switch on."""
+ hass.add_job(async_turn_on, hass, entity_id)
+
+
+@callback
+@bind_hass
+def async_turn_on(hass, entity_id=None):
+ """Turn all or specified switch on."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
+
+
+@bind_hass
+def turn_off(hass, entity_id=None):
+ """Turn all or specified switch off."""
+ hass.add_job(async_turn_off, hass, entity_id)
+
+
+@callback
+@bind_hass
+def async_turn_off(hass, entity_id=None):
+ """Turn all or specified switch off."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.async_add_job(
+ hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
diff --git a/tests/components/switch/test_command_line.py b/tests/components/switch/test_command_line.py
index 9ec0507627d..a84281b4375 100644
--- a/tests/components/switch/test_command_line.py
+++ b/tests/components/switch/test_command_line.py
@@ -10,6 +10,7 @@ import homeassistant.components.switch as switch
import homeassistant.components.switch.command_line as command_line
from tests.common import get_test_home_assistant
+from tests.components.switch import common
# pylint: disable=invalid-name
@@ -44,13 +45,13 @@ class TestCommandSwitch(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
- switch.turn_on(self.hass, 'switch.test')
+ common.turn_on(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
- switch.turn_off(self.hass, 'switch.test')
+ common.turn_off(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
@@ -78,13 +79,13 @@ class TestCommandSwitch(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
- switch.turn_on(self.hass, 'switch.test')
+ common.turn_on(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
- switch.turn_off(self.hass, 'switch.test')
+ common.turn_off(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
@@ -114,13 +115,13 @@ class TestCommandSwitch(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
- switch.turn_on(self.hass, 'switch.test')
+ common.turn_on(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
- switch.turn_off(self.hass, 'switch.test')
+ common.turn_off(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
@@ -147,13 +148,13 @@ class TestCommandSwitch(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
- switch.turn_on(self.hass, 'switch.test')
+ common.turn_on(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
- switch.turn_off(self.hass, 'switch.test')
+ common.turn_off(self.hass, 'switch.test')
self.hass.block_till_done()
state = self.hass.states.get('switch.test')
diff --git a/tests/components/switch/test_flux.py b/tests/components/switch/test_flux.py
index f9ea88c5254..69e65e24659 100644
--- a/tests/components/switch/test_flux.py
+++ b/tests/components/switch/test_flux.py
@@ -11,6 +11,8 @@ import homeassistant.util.dt as dt_util
from tests.common import (
assert_setup_component, get_test_home_assistant, fire_time_changed,
mock_service)
+from tests.components.light import common as common_light
+from tests.components.switch import common
class TestSwitchFlux(unittest.TestCase):
@@ -147,7 +149,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -193,7 +195,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -240,7 +242,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -286,7 +288,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -334,7 +336,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -383,7 +385,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -434,7 +436,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -484,7 +486,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -534,7 +536,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -584,7 +586,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -633,7 +635,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -681,7 +683,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -698,9 +700,9 @@ class TestSwitchFlux(unittest.TestCase):
{light.DOMAIN: {CONF_PLATFORM: 'test'}}))
dev1, dev2, dev3 = platform.DEVICES
- light.turn_on(self.hass, entity_id=dev2.entity_id)
+ common_light.turn_on(self.hass, entity_id=dev2.entity_id)
self.hass.block_till_done()
- light.turn_on(self.hass, entity_id=dev3.entity_id)
+ common_light.turn_on(self.hass, entity_id=dev3.entity_id)
self.hass.block_till_done()
state = self.hass.states.get(dev1.entity_id)
@@ -743,7 +745,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -794,7 +796,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
@@ -838,7 +840,7 @@ class TestSwitchFlux(unittest.TestCase):
})
turn_on_calls = mock_service(
self.hass, light.DOMAIN, SERVICE_TURN_ON)
- switch.turn_on(self.hass, 'switch.flux')
+ common.turn_on(self.hass, 'switch.flux')
self.hass.block_till_done()
fire_time_changed(self.hass, test_time)
self.hass.block_till_done()
diff --git a/tests/components/switch/test_init.py b/tests/components/switch/test_init.py
index 579898437ca..a7462eecd42 100644
--- a/tests/components/switch/test_init.py
+++ b/tests/components/switch/test_init.py
@@ -8,6 +8,7 @@ from homeassistant.components import switch
from homeassistant.const import STATE_ON, STATE_OFF, CONF_PLATFORM
from tests.common import get_test_home_assistant
+from tests.components.switch import common
class TestSwitch(unittest.TestCase):
@@ -41,8 +42,8 @@ class TestSwitch(unittest.TestCase):
self.assertFalse(switch.is_on(self.hass, self.switch_2.entity_id))
self.assertFalse(switch.is_on(self.hass, self.switch_3.entity_id))
- switch.turn_off(self.hass, self.switch_1.entity_id)
- switch.turn_on(self.hass, self.switch_2.entity_id)
+ common.turn_off(self.hass, self.switch_1.entity_id)
+ common.turn_on(self.hass, self.switch_2.entity_id)
self.hass.block_till_done()
@@ -51,7 +52,7 @@ class TestSwitch(unittest.TestCase):
self.assertTrue(switch.is_on(self.hass, self.switch_2.entity_id))
# Turn all off
- switch.turn_off(self.hass)
+ common.turn_off(self.hass)
self.hass.block_till_done()
@@ -64,7 +65,7 @@ class TestSwitch(unittest.TestCase):
self.assertFalse(switch.is_on(self.hass, self.switch_3.entity_id))
# Turn all on
- switch.turn_on(self.hass)
+ common.turn_on(self.hass)
self.hass.block_till_done()
diff --git a/tests/components/switch/test_litejet.py b/tests/components/switch/test_litejet.py
index 45e5509c169..f1d23f48b86 100644
--- a/tests/components/switch/test_litejet.py
+++ b/tests/components/switch/test_litejet.py
@@ -5,9 +5,11 @@ from unittest import mock
from homeassistant import setup
from homeassistant.components import litejet
-from tests.common import get_test_home_assistant
import homeassistant.components.switch as switch
+from tests.common import get_test_home_assistant
+from tests.components.switch import common
+
_LOGGER = logging.getLogger(__name__)
ENTITY_SWITCH = 'switch.mock_switch_1'
@@ -88,11 +90,11 @@ class TestLiteJetSwitch(unittest.TestCase):
assert not switch.is_on(self.hass, ENTITY_SWITCH)
- switch.turn_on(self.hass, ENTITY_SWITCH)
+ common.turn_on(self.hass, ENTITY_SWITCH)
self.hass.block_till_done()
self.mock_lj.press_switch.assert_called_with(ENTITY_SWITCH_NUMBER)
- switch.turn_off(self.hass, ENTITY_SWITCH)
+ common.turn_off(self.hass, ENTITY_SWITCH)
self.hass.block_till_done()
self.mock_lj.release_switch.assert_called_with(ENTITY_SWITCH_NUMBER)
diff --git a/tests/components/switch/test_mqtt.py b/tests/components/switch/test_mqtt.py
index c9bfd02156f..5ad233de284 100644
--- a/tests/components/switch/test_mqtt.py
+++ b/tests/components/switch/test_mqtt.py
@@ -2,13 +2,17 @@
import unittest
from unittest.mock import patch
-from homeassistant.setup import setup_component
+from homeassistant.setup import setup_component, async_setup_component
from homeassistant.const import STATE_ON, STATE_OFF, STATE_UNAVAILABLE,\
ATTR_ASSUMED_STATE
import homeassistant.core as ha
-import homeassistant.components.switch as switch
+from homeassistant.components import switch, mqtt
+from homeassistant.components.mqtt.discovery import async_start
+
from tests.common import (
- mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, mock_coro)
+ mock_mqtt_component, fire_mqtt_message, get_test_home_assistant, mock_coro,
+ async_mock_mqtt_component, async_fire_mqtt_message, MockConfigEntry)
+from tests.components.switch import common
class TestSwitchMQTT(unittest.TestCase):
@@ -73,7 +77,7 @@ class TestSwitchMQTT(unittest.TestCase):
self.assertEqual(STATE_ON, state.state)
self.assertTrue(state.attributes.get(ATTR_ASSUMED_STATE))
- switch.turn_on(self.hass, 'switch.test')
+ common.turn_on(self.hass, 'switch.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -82,7 +86,7 @@ class TestSwitchMQTT(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_ON, state.state)
- switch.turn_off(self.hass, 'switch.test')
+ common.turn_off(self.hass, 'switch.test')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
@@ -280,25 +284,56 @@ class TestSwitchMQTT(unittest.TestCase):
state = self.hass.states.get('switch.test')
self.assertEqual(STATE_OFF, state.state)
- def test_unique_id(self):
- """Test unique id option only creates one switch per unique_id."""
- assert setup_component(self.hass, switch.DOMAIN, {
- switch.DOMAIN: [{
- 'platform': 'mqtt',
- 'name': 'Test 1',
- 'state_topic': 'test-topic',
- 'command_topic': 'command-topic',
- 'unique_id': 'TOTALLY_UNIQUE'
- }, {
- 'platform': 'mqtt',
- 'name': 'Test 2',
- 'state_topic': 'test-topic',
- 'command_topic': 'command-topic',
- 'unique_id': 'TOTALLY_UNIQUE'
- }]
- })
- fire_mqtt_message(self.hass, 'test-topic', 'payload')
- self.hass.block_till_done()
- assert len(self.hass.states.async_entity_ids()) == 2
- # all switches group is 1, unique id created is 1
+async def test_unique_id(hass):
+ """Test unique id option only creates one switch per unique_id."""
+ await async_mock_mqtt_component(hass)
+ assert await async_setup_component(hass, switch.DOMAIN, {
+ switch.DOMAIN: [{
+ 'platform': 'mqtt',
+ 'name': 'Test 1',
+ 'state_topic': 'test-topic',
+ 'command_topic': 'command-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }, {
+ 'platform': 'mqtt',
+ 'name': 'Test 2',
+ 'state_topic': 'test-topic',
+ 'command_topic': 'command-topic',
+ 'unique_id': 'TOTALLY_UNIQUE'
+ }]
+ })
+
+ async_fire_mqtt_message(hass, 'test-topic', 'payload')
+ await hass.async_block_till_done()
+
+ assert len(hass.states.async_entity_ids()) == 2
+ # all switches group is 1, unique id created is 1
+
+
+async def test_discovery_removal_switch(hass, mqtt_mock, caplog):
+ """Test expansion of discovered switch."""
+ entry = MockConfigEntry(domain=mqtt.DOMAIN)
+ await async_start(hass, 'homeassistant', {}, entry)
+
+ data = (
+ '{ "name": "Beer",'
+ ' "status_topic": "test_topic",'
+ ' "command_topic": "test_topic" }'
+ )
+
+ async_fire_mqtt_message(hass, 'homeassistant/switch/bla/config',
+ data)
+ await hass.async_block_till_done()
+
+ state = hass.states.get('switch.beer')
+ assert state is not None
+ assert state.name == 'Beer'
+
+ async_fire_mqtt_message(hass, 'homeassistant/switch/bla/config',
+ '')
+ await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
+ state = hass.states.get('switch.beer')
+ assert state is None
diff --git a/tests/components/switch/test_template.py b/tests/components/switch/test_template.py
index 47766e31f4d..1492aab250f 100644
--- a/tests/components/switch/test_template.py
+++ b/tests/components/switch/test_template.py
@@ -1,11 +1,11 @@
"""The tests for the Template switch platform."""
from homeassistant.core import callback
from homeassistant import setup
-import homeassistant.components as core
from homeassistant.const import STATE_ON, STATE_OFF
from tests.common import (
get_test_home_assistant, assert_setup_component)
+from tests.components.switch import common
class TestTemplateSwitch:
@@ -406,7 +406,7 @@ class TestTemplateSwitch:
state = self.hass.states.get('switch.test_template_switch')
assert state.state == STATE_OFF
- core.switch.turn_on(self.hass, 'switch.test_template_switch')
+ common.turn_on(self.hass, 'switch.test_template_switch')
self.hass.block_till_done()
assert len(self.calls) == 1
@@ -442,7 +442,7 @@ class TestTemplateSwitch:
state = self.hass.states.get('switch.test_template_switch')
assert state.state == STATE_ON
- core.switch.turn_off(self.hass, 'switch.test_template_switch')
+ common.turn_off(self.hass, 'switch.test_template_switch')
self.hass.block_till_done()
assert len(self.calls) == 1
diff --git a/tests/components/switch/test_wake_on_lan.py b/tests/components/switch/test_wake_on_lan.py
index abe1532cec7..42ebd1ff231 100644
--- a/tests/components/switch/test_wake_on_lan.py
+++ b/tests/components/switch/test_wake_on_lan.py
@@ -7,6 +7,7 @@ from homeassistant.const import STATE_ON, STATE_OFF
import homeassistant.components.switch as switch
from tests.common import get_test_home_assistant, mock_service
+from tests.components.switch import common
TEST_STATE = None
@@ -59,13 +60,13 @@ class TestWOLSwitch(unittest.TestCase):
TEST_STATE = True
- switch.turn_on(self.hass, 'switch.wake_on_lan')
+ common.turn_on(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
state = self.hass.states.get('switch.wake_on_lan')
self.assertEqual(STATE_ON, state.state)
- switch.turn_off(self.hass, 'switch.wake_on_lan')
+ common.turn_off(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
state = self.hass.states.get('switch.wake_on_lan')
@@ -91,7 +92,7 @@ class TestWOLSwitch(unittest.TestCase):
TEST_STATE = True
- switch.turn_on(self.hass, 'switch.wake_on_lan')
+ common.turn_on(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
state = self.hass.states.get('switch.wake_on_lan')
@@ -123,7 +124,7 @@ class TestWOLSwitch(unittest.TestCase):
state = self.hass.states.get('switch.wake_on_lan')
self.assertEqual(STATE_OFF, state.state)
- switch.turn_on(self.hass, 'switch.wake_on_lan')
+ common.turn_on(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
@patch('wakeonlan.send_magic_packet', new=send_magic_packet)
@@ -149,7 +150,7 @@ class TestWOLSwitch(unittest.TestCase):
TEST_STATE = True
- switch.turn_on(self.hass, 'switch.wake_on_lan')
+ common.turn_on(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
state = self.hass.states.get('switch.wake_on_lan')
@@ -158,7 +159,7 @@ class TestWOLSwitch(unittest.TestCase):
TEST_STATE = False
- switch.turn_off(self.hass, 'switch.wake_on_lan')
+ common.turn_off(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
state = self.hass.states.get('switch.wake_on_lan')
@@ -185,7 +186,7 @@ class TestWOLSwitch(unittest.TestCase):
TEST_STATE = True
- switch.turn_on(self.hass, 'switch.wake_on_lan')
+ common.turn_on(self.hass, 'switch.wake_on_lan')
self.hass.block_till_done()
state = self.hass.states.get('switch.wake_on_lan')
diff --git a/tests/components/test_alert.py b/tests/components/test_alert.py
index 90d732ac38e..44ece2fc38e 100644
--- a/tests/components/test_alert.py
+++ b/tests/components/test_alert.py
@@ -5,10 +5,12 @@ import unittest
from homeassistant.setup import setup_component
from homeassistant.core import callback
+from homeassistant.components.alert import DOMAIN
import homeassistant.components.alert as alert
import homeassistant.components.notify as notify
-from homeassistant.const import (CONF_ENTITY_ID, STATE_IDLE, CONF_NAME,
- CONF_STATE, STATE_ON, STATE_OFF)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE,
+ SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON, STATE_ON, STATE_OFF)
from tests.common import get_test_home_assistant
@@ -31,6 +33,63 @@ TEST_NOACK = [NAME, NAME, DONE_MESSAGE, "sensor.test",
ENTITY_ID = alert.ENTITY_ID_FORMAT.format(NAME)
+def turn_on(hass, entity_id):
+ """Reset the alert.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.add_job(async_turn_on, hass, entity_id)
+
+
+@callback
+def async_turn_on(hass, entity_id):
+ """Async reset the alert.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_ENTITY_ID: entity_id}
+ hass.async_create_task(
+ hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
+
+
+def turn_off(hass, entity_id):
+ """Acknowledge alert.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.add_job(async_turn_off, hass, entity_id)
+
+
+@callback
+def async_turn_off(hass, entity_id):
+ """Async acknowledge the alert.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_ENTITY_ID: entity_id}
+ hass.async_create_task(
+ hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
+
+
+def toggle(hass, entity_id):
+ """Toggle acknowledgment of alert.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.add_job(async_toggle, hass, entity_id)
+
+
+@callback
+def async_toggle(hass, entity_id):
+ """Async toggle acknowledgment of alert.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_ENTITY_ID: entity_id}
+ hass.async_create_task(
+ hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
+
+
# pylint: disable=invalid-name
class TestAlert(unittest.TestCase):
"""Test the alert module."""
@@ -69,7 +128,7 @@ class TestAlert(unittest.TestCase):
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
self.hass.states.set("sensor.test", STATE_ON)
self.hass.block_till_done()
- alert.turn_off(self.hass, ENTITY_ID)
+ turn_off(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state)
@@ -86,10 +145,10 @@ class TestAlert(unittest.TestCase):
assert setup_component(self.hass, alert.DOMAIN, TEST_CONFIG)
self.hass.states.set("sensor.test", STATE_ON)
self.hass.block_till_done()
- alert.turn_off(self.hass, ENTITY_ID)
+ turn_off(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state)
- alert.turn_on(self.hass, ENTITY_ID)
+ turn_on(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
@@ -99,10 +158,10 @@ class TestAlert(unittest.TestCase):
self.hass.states.set("sensor.test", STATE_ON)
self.hass.block_till_done()
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
- alert.toggle(self.hass, ENTITY_ID)
+ toggle(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(STATE_OFF, self.hass.states.get(ENTITY_ID).state)
- alert.toggle(self.hass, ENTITY_ID)
+ toggle(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(STATE_ON, self.hass.states.get(ENTITY_ID).state)
@@ -117,7 +176,7 @@ class TestAlert(unittest.TestCase):
hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
self.assertFalse(hidden)
- alert.turn_off(self.hass, ENTITY_ID)
+ turn_off(self.hass, ENTITY_ID)
hidden = self.hass.states.get(ENTITY_ID).attributes.get('hidden')
self.assertFalse(hidden)
@@ -199,7 +258,7 @@ class TestAlert(unittest.TestCase):
self.assertEqual(True, entity.hidden)
def test_done_message_state_tracker_reset_on_cancel(self):
- """Test that the done message is reset when cancelled."""
+ """Test that the done message is reset when canceled."""
entity = alert.Alert(self.hass, *TEST_NOACK)
entity._cancel = lambda *args: None
assert entity._send_done_message is False
diff --git a/tests/components/test_device_sun_light_trigger.py b/tests/components/test_device_sun_light_trigger.py
index 35d53f9a5c8..7107eee74fe 100644
--- a/tests/components/test_device_sun_light_trigger.py
+++ b/tests/components/test_device_sun_light_trigger.py
@@ -12,6 +12,7 @@ from homeassistant.components import (
from homeassistant.util import dt as dt_util
from tests.common import get_test_home_assistant, fire_time_changed
+from tests.components.light import common as common_light
class TestDeviceSunLightTrigger(unittest.TestCase):
@@ -68,7 +69,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
self.hass, device_sun_light_trigger.DOMAIN, {
device_sun_light_trigger.DOMAIN: {}}))
- light.turn_off(self.hass)
+ common_light.turn_off(self.hass)
self.hass.block_till_done()
@@ -81,7 +82,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
def test_lights_turn_off_when_everyone_leaves(self):
"""Test lights turn off when everyone leaves the house."""
- light.turn_on(self.hass)
+ common_light.turn_on(self.hass)
self.hass.block_till_done()
@@ -100,7 +101,7 @@ class TestDeviceSunLightTrigger(unittest.TestCase):
"""Test lights turn on when coming home after sun set."""
test_time = datetime(2017, 4, 5, 3, 2, 3, tzinfo=dt_util.UTC)
with patch('homeassistant.util.dt.utcnow', return_value=test_time):
- light.turn_off(self.hass)
+ common_light.turn_off(self.hass)
self.hass.block_till_done()
self.assertTrue(setup_component(
diff --git a/tests/components/test_duckdns.py b/tests/components/test_duckdns.py
index d64ffbca81f..c3ece8a70fd 100644
--- a/tests/components/test_duckdns.py
+++ b/tests/components/test_duckdns.py
@@ -4,6 +4,7 @@ from datetime import timedelta
import pytest
+from homeassistant.loader import bind_hass
from homeassistant.setup import async_setup_component
from homeassistant.components import duckdns
from homeassistant.util.dt import utcnow
@@ -14,6 +15,19 @@ DOMAIN = 'bla'
TOKEN = 'abcdefgh'
+@bind_hass
+@asyncio.coroutine
+def async_set_txt(hass, txt):
+ """Set the txt record. Pass in None to remove it.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ yield from hass.services.async_call(
+ duckdns.DOMAIN, duckdns.SERVICE_SET_TXT, {
+ duckdns.ATTR_TXT: txt
+ }, blocking=True)
+
+
@pytest.fixture
def setup_duckdns(hass, aioclient_mock):
"""Fixture that sets up DuckDNS."""
@@ -84,7 +98,7 @@ def test_service_set_txt(hass, aioclient_mock, setup_duckdns):
}, text='OK')
assert aioclient_mock.call_count == 0
- yield from hass.components.duckdns.async_set_txt('some-txt')
+ yield from async_set_txt(hass, 'some-txt')
assert aioclient_mock.call_count == 1
@@ -102,5 +116,5 @@ def test_service_clear_txt(hass, aioclient_mock, setup_duckdns):
}, text='OK')
assert aioclient_mock.call_count == 0
- yield from hass.components.duckdns.async_set_txt(None)
+ yield from async_set_txt(hass, None)
assert aioclient_mock.call_count == 1
diff --git a/tests/components/test_ffmpeg.py b/tests/components/test_ffmpeg.py
index 76b1300774b..774bb471f57 100644
--- a/tests/components/test_ffmpeg.py
+++ b/tests/components/test_ffmpeg.py
@@ -3,12 +3,46 @@ import asyncio
from unittest.mock import patch, MagicMock
import homeassistant.components.ffmpeg as ffmpeg
+from homeassistant.components.ffmpeg import (
+ DOMAIN, SERVICE_RESTART, SERVICE_START, SERVICE_STOP)
+from homeassistant.const import ATTR_ENTITY_ID
+from homeassistant.core import callback
from homeassistant.setup import setup_component, async_setup_component
from tests.common import (
get_test_home_assistant, assert_setup_component, mock_coro)
+@callback
+def async_start(hass, entity_id=None):
+ """Start a FFmpeg process on entity.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_START, data))
+
+
+@callback
+def async_stop(hass, entity_id=None):
+ """Stop a FFmpeg process on entity.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_STOP, data))
+
+
+@callback
+def async_restart(hass, entity_id=None):
+ """Restart a FFmpeg process on entity.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RESTART, data))
+
+
class MockFFmpegDev(ffmpeg.FFmpegBase):
"""FFmpeg device mock."""
@@ -106,7 +140,7 @@ def test_setup_component_test_service_start(hass):
ffmpeg_dev = MockFFmpegDev(hass, False)
yield from ffmpeg_dev.async_added_to_hass()
- ffmpeg.async_start(hass)
+ async_start(hass)
yield from hass.async_block_till_done()
assert ffmpeg_dev.called_start
@@ -122,7 +156,7 @@ def test_setup_component_test_service_stop(hass):
ffmpeg_dev = MockFFmpegDev(hass, False)
yield from ffmpeg_dev.async_added_to_hass()
- ffmpeg.async_stop(hass)
+ async_stop(hass)
yield from hass.async_block_till_done()
assert ffmpeg_dev.called_stop
@@ -138,7 +172,7 @@ def test_setup_component_test_service_restart(hass):
ffmpeg_dev = MockFFmpegDev(hass, False)
yield from ffmpeg_dev.async_added_to_hass()
- ffmpeg.async_restart(hass)
+ async_restart(hass)
yield from hass.async_block_till_done()
assert ffmpeg_dev.called_stop
@@ -155,7 +189,7 @@ def test_setup_component_test_service_start_with_entity(hass):
ffmpeg_dev = MockFFmpegDev(hass, False)
yield from ffmpeg_dev.async_added_to_hass()
- ffmpeg.async_start(hass, 'test.ffmpeg_device')
+ async_start(hass, 'test.ffmpeg_device')
yield from hass.async_block_till_done()
assert ffmpeg_dev.called_start
diff --git a/tests/components/test_init.py b/tests/components/test_init.py
index 355f3dc0e96..68396f5abcb 100644
--- a/tests/components/test_init.py
+++ b/tests/components/test_init.py
@@ -8,8 +8,12 @@ import yaml
import homeassistant.core as ha
from homeassistant import config
from homeassistant.const import (
- STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE)
+ ATTR_ENTITY_ID, STATE_ON, STATE_OFF, SERVICE_HOMEASSISTANT_RESTART,
+ SERVICE_HOMEASSISTANT_STOP, SERVICE_TURN_ON, SERVICE_TURN_OFF,
+ SERVICE_TOGGLE)
import homeassistant.components as comps
+from homeassistant.components import (
+ SERVICE_CHECK_CONFIG, SERVICE_RELOAD_CORE_CONFIG)
import homeassistant.helpers.intent as intent
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity
@@ -20,6 +24,71 @@ from tests.common import (
async_mock_service)
+def turn_on(hass, entity_id=None, **service_data):
+ """Turn specified entity on if possible.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ if entity_id is not None:
+ service_data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data)
+
+
+def turn_off(hass, entity_id=None, **service_data):
+ """Turn specified entity off.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ if entity_id is not None:
+ service_data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
+
+
+def toggle(hass, entity_id=None, **service_data):
+ """Toggle specified entity.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ if entity_id is not None:
+ service_data[ATTR_ENTITY_ID] = entity_id
+
+ hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
+
+
+def stop(hass):
+ """Stop Home Assistant.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
+
+
+def restart(hass):
+ """Stop Home Assistant.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
+
+
+def check_config(hass):
+ """Check the config files.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
+
+
+def reload_core_config(hass):
+ """Reload the core config.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
+
+
class TestComponentsCore(unittest.TestCase):
"""Test homeassistant.components module."""
@@ -49,28 +118,28 @@ class TestComponentsCore(unittest.TestCase):
def test_turn_on_without_entities(self):
"""Test turn_on method without entities."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
- comps.turn_on(self.hass)
+ turn_on(self.hass)
self.hass.block_till_done()
self.assertEqual(0, len(calls))
def test_turn_on(self):
"""Test turn_on method."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_ON)
- comps.turn_on(self.hass, 'light.Ceiling')
+ turn_on(self.hass, 'light.Ceiling')
self.hass.block_till_done()
self.assertEqual(1, len(calls))
def test_turn_off(self):
"""Test turn_off method."""
calls = mock_service(self.hass, 'light', SERVICE_TURN_OFF)
- comps.turn_off(self.hass, 'light.Bowl')
+ turn_off(self.hass, 'light.Bowl')
self.hass.block_till_done()
self.assertEqual(1, len(calls))
def test_toggle(self):
"""Test toggle method."""
calls = mock_service(self.hass, 'light', SERVICE_TOGGLE)
- comps.toggle(self.hass, 'light.Bowl')
+ toggle(self.hass, 'light.Bowl')
self.hass.block_till_done()
self.assertEqual(1, len(calls))
@@ -102,7 +171,7 @@ class TestComponentsCore(unittest.TestCase):
})
}
with patch_yaml_files(files, True):
- comps.reload_core_config(self.hass)
+ reload_core_config(self.hass)
self.hass.block_till_done()
assert self.hass.config.latitude == 10
@@ -125,7 +194,7 @@ class TestComponentsCore(unittest.TestCase):
config.YAML_CONFIG_FILE: yaml.dump(['invalid', 'config'])
}
with patch_yaml_files(files, True):
- comps.reload_core_config(self.hass)
+ reload_core_config(self.hass)
self.hass.block_till_done()
assert mock_error.called
@@ -135,7 +204,7 @@ class TestComponentsCore(unittest.TestCase):
return_value=mock_coro())
def test_stop_homeassistant(self, mock_stop):
"""Test stop service."""
- comps.stop(self.hass)
+ stop(self.hass)
self.hass.block_till_done()
assert mock_stop.called
@@ -145,7 +214,7 @@ class TestComponentsCore(unittest.TestCase):
return_value=mock_coro())
def test_restart_homeassistant(self, mock_check, mock_restart):
"""Test stop service."""
- comps.restart(self.hass)
+ restart(self.hass)
self.hass.block_till_done()
assert mock_restart.called
assert mock_check.called
@@ -156,7 +225,7 @@ class TestComponentsCore(unittest.TestCase):
side_effect=HomeAssistantError("Test error"))
def test_restart_homeassistant_wrong_conf(self, mock_check, mock_restart):
"""Test stop service."""
- comps.restart(self.hass)
+ restart(self.hass)
self.hass.block_till_done()
assert mock_check.called
assert not mock_restart.called
@@ -167,7 +236,7 @@ class TestComponentsCore(unittest.TestCase):
return_value=mock_coro())
def test_check_config(self, mock_check, mock_stop):
"""Test stop service."""
- comps.check_config(self.hass)
+ check_config(self.hass)
self.hass.block_till_done()
assert mock_check.called
assert not mock_stop.called
diff --git a/tests/components/test_input_boolean.py b/tests/components/test_input_boolean.py
index 999e7ac100f..b947155e6b2 100644
--- a/tests/components/test_input_boolean.py
+++ b/tests/components/test_input_boolean.py
@@ -7,9 +7,11 @@ import logging
from homeassistant.core import CoreState, State, Context
from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_boolean import (
- DOMAIN, is_on, toggle, turn_off, turn_on, CONF_INITIAL)
+ is_on, CONF_INITIAL, DOMAIN)
from homeassistant.const import (
- STATE_ON, STATE_OFF, ATTR_ICON, ATTR_FRIENDLY_NAME)
+ STATE_ON, STATE_OFF, ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON,
+ SERVICE_TOGGLE, SERVICE_TURN_OFF, SERVICE_TURN_ON)
+from homeassistant.loader import bind_hass
from tests.common import (
get_test_home_assistant, mock_component, mock_restore_cache)
@@ -17,6 +19,33 @@ from tests.common import (
_LOGGER = logging.getLogger(__name__)
+@bind_hass
+def toggle(hass, entity_id):
+ """Set input_boolean to False.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
+
+
+@bind_hass
+def turn_on(hass, entity_id):
+ """Set input_boolean to True.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, {ATTR_ENTITY_ID: entity_id})
+
+
+@bind_hass
+def turn_off(hass, entity_id):
+ """Set input_boolean to False.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
+
+
class TestInputBoolean(unittest.TestCase):
"""Test the input boolean module."""
diff --git a/tests/components/test_input_number.py b/tests/components/test_input_number.py
index 659aaa524d9..a53704e1d10 100644
--- a/tests/components/test_input_number.py
+++ b/tests/components/test_input_number.py
@@ -4,13 +4,50 @@ import asyncio
import unittest
from homeassistant.core import CoreState, State, Context
-from homeassistant.setup import setup_component, async_setup_component
from homeassistant.components.input_number import (
- DOMAIN, set_value, increment, decrement)
+ ATTR_VALUE, DOMAIN, SERVICE_DECREMENT, SERVICE_INCREMENT,
+ SERVICE_SET_VALUE)
+from homeassistant.const import ATTR_ENTITY_ID
+from homeassistant.loader import bind_hass
+from homeassistant.setup import setup_component, async_setup_component
from tests.common import get_test_home_assistant, mock_restore_cache
+@bind_hass
+def set_value(hass, entity_id, value):
+ """Set input_number to value.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_SET_VALUE, {
+ ATTR_ENTITY_ID: entity_id,
+ ATTR_VALUE: value,
+ })
+
+
+@bind_hass
+def increment(hass, entity_id):
+ """Increment value of entity.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_INCREMENT, {
+ ATTR_ENTITY_ID: entity_id
+ })
+
+
+@bind_hass
+def decrement(hass, entity_id):
+ """Decrement value of entity.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_DECREMENT, {
+ ATTR_ENTITY_ID: entity_id
+ })
+
+
class TestInputNumber(unittest.TestCase):
"""Test the input number component."""
diff --git a/tests/components/test_input_select.py b/tests/components/test_input_select.py
index 1c73abfbb94..25f6d1c7673 100644
--- a/tests/components/test_input_select.py
+++ b/tests/components/test_input_select.py
@@ -3,15 +3,50 @@
import asyncio
import unittest
-from tests.common import get_test_home_assistant, mock_restore_cache
-
+from homeassistant.loader import bind_hass
+from homeassistant.components.input_select import (
+ ATTR_OPTION, ATTR_OPTIONS, DOMAIN, SERVICE_SET_OPTIONS,
+ SERVICE_SELECT_NEXT, SERVICE_SELECT_OPTION, SERVICE_SELECT_PREVIOUS)
+from homeassistant.const import (
+ ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, ATTR_ICON)
from homeassistant.core import State, Context
from homeassistant.setup import setup_component, async_setup_component
-from homeassistant.components.input_select import (
- ATTR_OPTIONS, DOMAIN, SERVICE_SET_OPTIONS,
- select_option, select_next, select_previous)
-from homeassistant.const import (
- ATTR_ICON, ATTR_FRIENDLY_NAME)
+
+from tests.common import get_test_home_assistant, mock_restore_cache
+
+
+@bind_hass
+def select_option(hass, entity_id, option):
+ """Set value of input_select.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_SELECT_OPTION, {
+ ATTR_ENTITY_ID: entity_id,
+ ATTR_OPTION: option,
+ })
+
+
+@bind_hass
+def select_next(hass, entity_id):
+ """Set next value of input_select.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_SELECT_NEXT, {
+ ATTR_ENTITY_ID: entity_id,
+ })
+
+
+@bind_hass
+def select_previous(hass, entity_id):
+ """Set previous value of input_select.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_SELECT_PREVIOUS, {
+ ATTR_ENTITY_ID: entity_id,
+ })
class TestInputSelect(unittest.TestCase):
diff --git a/tests/components/test_input_text.py b/tests/components/test_input_text.py
index 7c8a0e65023..bea145390eb 100644
--- a/tests/components/test_input_text.py
+++ b/tests/components/test_input_text.py
@@ -3,13 +3,28 @@
import asyncio
import unittest
+from homeassistant.components.input_text import (
+ ATTR_VALUE, DOMAIN, SERVICE_SET_VALUE)
+from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import CoreState, State, Context
+from homeassistant.loader import bind_hass
from homeassistant.setup import setup_component, async_setup_component
-from homeassistant.components.input_text import (DOMAIN, set_value)
from tests.common import get_test_home_assistant, mock_restore_cache
+@bind_hass
+def set_value(hass, entity_id, value):
+ """Set input_text to value.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_SET_VALUE, {
+ ATTR_ENTITY_ID: entity_id,
+ ATTR_VALUE: value,
+ })
+
+
class TestInputText(unittest.TestCase):
"""Test the input slider component."""
diff --git a/tests/components/test_logbook.py b/tests/components/test_logbook.py
index cf78fbec352..3bb3ae57c68 100644
--- a/tests/components/test_logbook.py
+++ b/tests/components/test_logbook.py
@@ -1,7 +1,7 @@
"""The tests for the logbook component."""
# pylint: disable=protected-access,invalid-name
import logging
-from datetime import timedelta
+from datetime import (timedelta, datetime)
import unittest
from homeassistant.components import sun
@@ -11,6 +11,7 @@ from homeassistant.const import (
ATTR_HIDDEN, STATE_NOT_HOME, STATE_ON, STATE_OFF)
import homeassistant.util.dt as dt_util
from homeassistant.components import logbook, recorder
+from homeassistant.components.alexa.smart_home import EVENT_ALEXA_SMART_HOME
from homeassistant.setup import setup_component, async_setup_component
from tests.common import (
@@ -99,7 +100,7 @@ class TestComponentLogbook(unittest.TestCase):
eventB = self.create_state_changed_event(pointB, entity_id, 20)
eventC = self.create_state_changed_event(pointC, entity_id, 30)
- entries = list(logbook.humanify((eventA, eventB, eventC)))
+ entries = list(logbook.humanify(self.hass, (eventA, eventB, eventC)))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -116,7 +117,7 @@ class TestComponentLogbook(unittest.TestCase):
eventA = self.create_state_changed_event(
pointA, entity_id, 10, attributes)
- entries = list(logbook.humanify((eventA,)))
+ entries = list(logbook.humanify(self.hass, (eventA,)))
self.assertEqual(0, len(entries))
@@ -133,7 +134,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {})
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -155,7 +156,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {})
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -177,7 +178,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events((ha.Event(EVENT_HOMEASSISTANT_STOP),
eventA, eventB), {})
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -203,7 +204,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN])
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -229,7 +230,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB),
config[logbook.DOMAIN])
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started',
@@ -266,7 +267,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN])
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -292,7 +293,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_STOP), eventA, eventB),
config[logbook.DOMAIN])
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(
@@ -318,7 +319,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA, eventB),
config[logbook.DOMAIN])
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(2, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started',
@@ -352,7 +353,7 @@ class TestComponentLogbook(unittest.TestCase):
events = logbook._exclude_events(
(ha.Event(EVENT_HOMEASSISTANT_START), eventA1, eventA2, eventA3,
eventB1, eventB2), config[logbook.DOMAIN])
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(3, len(entries))
self.assert_entry(entries[0], name='Home Assistant', message='started',
@@ -373,7 +374,7 @@ class TestComponentLogbook(unittest.TestCase):
{'auto': True})
events = logbook._exclude_events((eventA, eventB), {})
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(1, len(entries))
self.assert_entry(entries[0], pointA, 'bla', domain='switch',
@@ -391,29 +392,18 @@ class TestComponentLogbook(unittest.TestCase):
pointA, entity_id2, 20, last_changed=pointA, last_updated=pointB)
events = logbook._exclude_events((eventA, eventB), {})
- entries = list(logbook.humanify(events))
+ entries = list(logbook.humanify(self.hass, events))
self.assertEqual(1, len(entries))
self.assert_entry(entries[0], pointA, 'bla', domain='switch',
entity_id=entity_id)
- def test_entry_to_dict(self):
- """Test conversion of entry to dict."""
- entry = logbook.Entry(
- dt_util.utcnow(), 'Alarm', 'is triggered', 'switch', 'test_switch'
- )
- data = entry.as_dict()
- self.assertEqual('Alarm', data.get(logbook.ATTR_NAME))
- self.assertEqual('is triggered', data.get(logbook.ATTR_MESSAGE))
- self.assertEqual('switch', data.get(logbook.ATTR_DOMAIN))
- self.assertEqual('test_switch', data.get(logbook.ATTR_ENTITY_ID))
-
def test_home_assistant_start_stop_grouped(self):
"""Test if HA start and stop events are grouped.
Events that are occurring in the same minute.
"""
- entries = list(logbook.humanify((
+ entries = list(logbook.humanify(self.hass, (
ha.Event(EVENT_HOMEASSISTANT_STOP),
ha.Event(EVENT_HOMEASSISTANT_START),
)))
@@ -428,7 +418,7 @@ class TestComponentLogbook(unittest.TestCase):
entity_id = 'switch.bla'
pointA = dt_util.utcnow()
- entries = list(logbook.humanify((
+ entries = list(logbook.humanify(self.hass, (
ha.Event(EVENT_HOMEASSISTANT_START),
self.create_state_changed_event(pointA, entity_id, 10)
)))
@@ -509,7 +499,7 @@ class TestComponentLogbook(unittest.TestCase):
message = 'has a custom entry'
entity_id = 'sun.sun'
- entries = list(logbook.humanify((
+ entries = list(logbook.humanify(self.hass, (
ha.Event(logbook.EVENT_LOGBOOK_ENTRY, {
logbook.ATTR_NAME: name,
logbook.ATTR_MESSAGE: message,
@@ -526,19 +516,19 @@ class TestComponentLogbook(unittest.TestCase):
domain=None, entity_id=None):
"""Assert an entry is what is expected."""
if when:
- self.assertEqual(when, entry.when)
+ self.assertEqual(when, entry['when'])
if name:
- self.assertEqual(name, entry.name)
+ self.assertEqual(name, entry['name'])
if message:
- self.assertEqual(message, entry.message)
+ self.assertEqual(message, entry['message'])
if domain:
- self.assertEqual(domain, entry.domain)
+ self.assertEqual(domain, entry['domain'])
if entity_id:
- self.assertEqual(entity_id, entry.entity_id)
+ self.assertEqual(entity_id, entry['entity_id'])
def create_state_changed_event(self, event_time_fired, entity_id, state,
attributes=None, last_changed=None,
@@ -566,3 +556,131 @@ async def test_logbook_view(hass, aiohttp_client):
response = await client.get(
'/api/logbook/{}'.format(dt_util.utcnow().isoformat()))
assert response.status == 200
+
+
+async def test_logbook_view_period_entity(hass, aiohttp_client):
+ """Test the logbook view with period and entity."""
+ await hass.async_add_job(init_recorder_component, hass)
+ await async_setup_component(hass, 'logbook', {})
+ await hass.components.recorder.wait_connection_ready()
+ await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done)
+
+ entity_id_test = 'switch.test'
+ hass.states.async_set(entity_id_test, STATE_OFF)
+ hass.states.async_set(entity_id_test, STATE_ON)
+ entity_id_second = 'switch.second'
+ hass.states.async_set(entity_id_second, STATE_OFF)
+ hass.states.async_set(entity_id_second, STATE_ON)
+ await hass.async_block_till_done()
+ await hass.async_add_job(hass.data[recorder.DATA_INSTANCE].block_till_done)
+
+ client = await aiohttp_client(hass.http.app)
+
+ # Today time 00:00:00
+ start = dt_util.utcnow().date()
+ start_date = datetime(start.year, start.month, start.day)
+
+ # Test today entries without filters
+ response = await client.get(
+ '/api/logbook/{}'.format(start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 2
+ assert json[0]['entity_id'] == entity_id_test
+ assert json[1]['entity_id'] == entity_id_second
+
+ # Test today entries with filter by period
+ response = await client.get(
+ '/api/logbook/{}?period=1'.format(start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 2
+ assert json[0]['entity_id'] == entity_id_test
+ assert json[1]['entity_id'] == entity_id_second
+
+ # Test today entries with filter by entity_id
+ response = await client.get(
+ '/api/logbook/{}?entity=switch.test'.format(
+ start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 1
+ assert json[0]['entity_id'] == entity_id_test
+
+ # Test entries for 3 days with filter by entity_id
+ response = await client.get(
+ '/api/logbook/{}?period=3&entity=switch.test'.format(
+ start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 1
+ assert json[0]['entity_id'] == entity_id_test
+
+ # Tomorrow time 00:00:00
+ start = (dt_util.utcnow() + timedelta(days=1)).date()
+ start_date = datetime(start.year, start.month, start.day)
+
+ # Test tomorrow entries without filters
+ response = await client.get(
+ '/api/logbook/{}'.format(start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 0
+
+ # Test tomorrow entries with filter by entity_id
+ response = await client.get(
+ '/api/logbook/{}?entity=switch.test'.format(
+ start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 0
+
+ # Test entries from tomorrow to 3 days ago with filter by entity_id
+ response = await client.get(
+ '/api/logbook/{}?period=3&entity=switch.test'.format(
+ start_date.isoformat()))
+ assert response.status == 200
+ json = await response.json()
+ assert len(json) == 1
+ assert json[0]['entity_id'] == entity_id_test
+
+
+async def test_humanify_alexa_event(hass):
+ """Test humanifying Alexa event."""
+ hass.states.async_set('light.kitchen', 'on', {
+ 'friendly_name': 'Kitchen Light'
+ })
+
+ results = list(logbook.humanify(hass, [
+ ha.Event(EVENT_ALEXA_SMART_HOME, {'request': {
+ 'namespace': 'Alexa.Discovery',
+ 'name': 'Discover',
+ }}),
+ ha.Event(EVENT_ALEXA_SMART_HOME, {'request': {
+ 'namespace': 'Alexa.PowerController',
+ 'name': 'TurnOn',
+ 'entity_id': 'light.kitchen'
+ }}),
+ ha.Event(EVENT_ALEXA_SMART_HOME, {'request': {
+ 'namespace': 'Alexa.PowerController',
+ 'name': 'TurnOn',
+ 'entity_id': 'light.non_existing'
+ }}),
+
+ ]))
+
+ event1, event2, event3 = results
+
+ assert event1['name'] == 'Amazon Alexa'
+ assert event1['message'] == 'send command Alexa.Discovery/Discover'
+ assert event1['entity_id'] is None
+
+ assert event2['name'] == 'Amazon Alexa'
+ assert event2['message'] == \
+ 'send command Alexa.PowerController/TurnOn for Kitchen Light'
+ assert event2['entity_id'] == 'light.kitchen'
+
+ assert event3['name'] == 'Amazon Alexa'
+ assert event3['message'] == \
+ 'send command Alexa.PowerController/TurnOn for light.non_existing'
+ assert event3['entity_id'] == 'light.non_existing'
diff --git a/tests/components/test_microsoft_face.py b/tests/components/test_microsoft_face.py
index 601d5e7ebcc..6c9c58da7df 100644
--- a/tests/components/test_microsoft_face.py
+++ b/tests/components/test_microsoft_face.py
@@ -3,12 +3,72 @@ import asyncio
from unittest.mock import patch
from homeassistant.components import camera, microsoft_face as mf
+from homeassistant.components.microsoft_face import (
+ ATTR_CAMERA_ENTITY, ATTR_GROUP, ATTR_PERSON, DOMAIN, SERVICE_CREATE_GROUP,
+ SERVICE_CREATE_PERSON, SERVICE_DELETE_GROUP, SERVICE_DELETE_PERSON,
+ SERVICE_FACE_PERSON, SERVICE_TRAIN_GROUP)
+from homeassistant.const import ATTR_NAME
from homeassistant.setup import setup_component
from tests.common import (
get_test_home_assistant, assert_setup_component, mock_coro, load_fixture)
+def create_group(hass, name):
+ """Create a new person group.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_NAME: name}
+ hass.services.call(DOMAIN, SERVICE_CREATE_GROUP, data)
+
+
+def delete_group(hass, name):
+ """Delete a person group.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_NAME: name}
+ hass.services.call(DOMAIN, SERVICE_DELETE_GROUP, data)
+
+
+def train_group(hass, group):
+ """Train a person group.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_GROUP: group}
+ hass.services.call(DOMAIN, SERVICE_TRAIN_GROUP, data)
+
+
+def create_person(hass, group, name):
+ """Create a person in a group.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_GROUP: group, ATTR_NAME: name}
+ hass.services.call(DOMAIN, SERVICE_CREATE_PERSON, data)
+
+
+def delete_person(hass, group, name):
+ """Delete a person in a group.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_GROUP: group, ATTR_NAME: name}
+ hass.services.call(DOMAIN, SERVICE_DELETE_PERSON, data)
+
+
+def face_person(hass, group, person, camera_entity):
+ """Add a new face picture to a person.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ data = {ATTR_GROUP: group, ATTR_PERSON: person,
+ ATTR_CAMERA_ENTITY: camera_entity}
+ hass.services.call(DOMAIN, SERVICE_FACE_PERSON, data)
+
+
class TestMicrosoftFaceSetup:
"""Test the microsoft face component."""
@@ -108,14 +168,14 @@ class TestMicrosoftFaceSetup:
with assert_setup_component(3, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, self.config)
- mf.create_group(self.hass, 'Service Group')
+ create_group(self.hass, 'Service Group')
self.hass.block_till_done()
entity = self.hass.states.get('microsoft_face.service_group')
assert entity is not None
assert len(aioclient_mock.mock_calls) == 1
- mf.delete_group(self.hass, 'Service Group')
+ delete_group(self.hass, 'Service Group')
self.hass.block_till_done()
entity = self.hass.states.get('microsoft_face.service_group')
@@ -153,7 +213,7 @@ class TestMicrosoftFaceSetup:
status=200, text="{}"
)
- mf.create_person(self.hass, 'test group1', 'Hans')
+ create_person(self.hass, 'test group1', 'Hans')
self.hass.block_till_done()
entity_group1 = self.hass.states.get('microsoft_face.test_group1')
@@ -163,7 +223,7 @@ class TestMicrosoftFaceSetup:
assert entity_group1.attributes['Hans'] == \
'25985303-c537-4467-b41d-bdb45cd95ca1'
- mf.delete_person(self.hass, 'test group1', 'Hans')
+ delete_person(self.hass, 'test group1', 'Hans')
self.hass.block_till_done()
entity_group1 = self.hass.states.get('microsoft_face.test_group1')
@@ -184,7 +244,7 @@ class TestMicrosoftFaceSetup:
status=200, text="{}"
)
- mf.train_group(self.hass, 'Service Group')
+ train_group(self.hass, 'Service Group')
self.hass.block_till_done()
assert len(aioclient_mock.mock_calls) == 1
@@ -219,7 +279,7 @@ class TestMicrosoftFaceSetup:
status=200, text="{}"
)
- mf.face_person(
+ face_person(
self.hass, 'test_group2', 'David', 'camera.demo_camera')
self.hass.block_till_done()
@@ -238,7 +298,7 @@ class TestMicrosoftFaceSetup:
with assert_setup_component(3, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, self.config)
- mf.create_group(self.hass, 'Service Group')
+ create_group(self.hass, 'Service Group')
self.hass.block_till_done()
entity = self.hass.states.get('microsoft_face.service_group')
@@ -257,7 +317,7 @@ class TestMicrosoftFaceSetup:
with assert_setup_component(3, mf.DOMAIN):
setup_component(self.hass, mf.DOMAIN, self.config)
- mf.create_group(self.hass, 'Service Group')
+ create_group(self.hass, 'Service Group')
self.hass.block_till_done()
entity = self.hass.states.get('microsoft_face.service_group')
diff --git a/tests/components/test_panel_iframe.py b/tests/components/test_panel_iframe.py
index 3ac06c09a26..cb868f64b58 100644
--- a/tests/components/test_panel_iframe.py
+++ b/tests/components/test_panel_iframe.py
@@ -62,7 +62,7 @@ class TestPanelIframe(unittest.TestCase):
panels = self.hass.data[frontend.DATA_PANELS]
- assert panels.get('router').to_response(self.hass, None) == {
+ assert panels.get('router').to_response() == {
'component_name': 'iframe',
'config': {'url': 'http://192.168.1.1'},
'icon': 'mdi:network-wireless',
@@ -70,7 +70,7 @@ class TestPanelIframe(unittest.TestCase):
'url_path': 'router'
}
- assert panels.get('weather').to_response(self.hass, None) == {
+ assert panels.get('weather').to_response() == {
'component_name': 'iframe',
'config': {'url': 'https://www.wunderground.com/us/ca/san-diego'},
'icon': 'mdi:weather',
@@ -78,7 +78,7 @@ class TestPanelIframe(unittest.TestCase):
'url_path': 'weather',
}
- assert panels.get('api').to_response(self.hass, None) == {
+ assert panels.get('api').to_response() == {
'component_name': 'iframe',
'config': {'url': '/api'},
'icon': 'mdi:weather',
@@ -86,7 +86,7 @@ class TestPanelIframe(unittest.TestCase):
'url_path': 'api',
}
- assert panels.get('ftp').to_response(self.hass, None) == {
+ assert panels.get('ftp').to_response() == {
'component_name': 'iframe',
'config': {'url': 'ftp://some/ftp'},
'icon': 'mdi:weather',
diff --git a/tests/components/test_script.py b/tests/components/test_script.py
index b9aa921cb63..43727b6d559 100644
--- a/tests/components/test_script.py
+++ b/tests/components/test_script.py
@@ -3,9 +3,13 @@
import unittest
from unittest.mock import patch
-from homeassistant.core import Context, callback
-from homeassistant.setup import setup_component
from homeassistant.components import script
+from homeassistant.components.script import DOMAIN
+from homeassistant.const import (
+ ATTR_ENTITY_ID, SERVICE_RELOAD, SERVICE_TOGGLE, SERVICE_TURN_OFF)
+from homeassistant.core import Context, callback, split_entity_id
+from homeassistant.loader import bind_hass
+from homeassistant.setup import setup_component
from tests.common import get_test_home_assistant
@@ -13,6 +17,44 @@ from tests.common import get_test_home_assistant
ENTITY_ID = 'script.test'
+@bind_hass
+def turn_on(hass, entity_id, variables=None, context=None):
+ """Turn script on.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ _, object_id = split_entity_id(entity_id)
+
+ hass.services.call(DOMAIN, object_id, variables, context=context)
+
+
+@bind_hass
+def turn_off(hass, entity_id):
+ """Turn script on.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, {ATTR_ENTITY_ID: entity_id})
+
+
+@bind_hass
+def toggle(hass, entity_id):
+ """Toggle the script.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_TOGGLE, {ATTR_ENTITY_ID: entity_id})
+
+
+@bind_hass
+def reload(hass):
+ """Reload script component.
+
+ This is a legacy helper method. Do not use it for new tests.
+ """
+ hass.services.call(DOMAIN, SERVICE_RELOAD)
+
+
class TestScriptComponent(unittest.TestCase):
"""Test the Script component."""
@@ -76,17 +118,17 @@ class TestScriptComponent(unittest.TestCase):
}
})
- script.turn_on(self.hass, ENTITY_ID)
+ turn_on(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertTrue(script.is_on(self.hass, ENTITY_ID))
self.assertEqual(0, len(events))
# Calling turn_on a second time should not advance the script
- script.turn_on(self.hass, ENTITY_ID)
+ turn_on(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertEqual(0, len(events))
- script.turn_off(self.hass, ENTITY_ID)
+ turn_off(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertFalse(script.is_on(self.hass, ENTITY_ID))
self.assertEqual(0, len(events))
@@ -121,12 +163,12 @@ class TestScriptComponent(unittest.TestCase):
}
})
- script.toggle(self.hass, ENTITY_ID)
+ toggle(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertTrue(script.is_on(self.hass, ENTITY_ID))
self.assertEqual(0, len(events))
- script.toggle(self.hass, ENTITY_ID)
+ toggle(self.hass, ENTITY_ID)
self.hass.block_till_done()
self.assertFalse(script.is_on(self.hass, ENTITY_ID))
self.assertEqual(0, len(events))
@@ -156,7 +198,7 @@ class TestScriptComponent(unittest.TestCase):
},
})
- script.turn_on(self.hass, ENTITY_ID, {
+ turn_on(self.hass, ENTITY_ID, {
'greeting': 'world'
}, context=context)
@@ -204,7 +246,7 @@ class TestScriptComponent(unittest.TestCase):
}}}):
with patch('homeassistant.config.find_config_file',
return_value=''):
- script.reload(self.hass)
+ reload(self.hass)
self.hass.block_till_done()
assert self.hass.states.get(ENTITY_ID) is None
diff --git a/tests/components/test_spc.py b/tests/components/test_spc.py
index d4bedda4e96..bcbf970a48b 100644
--- a/tests/components/test_spc.py
+++ b/tests/components/test_spc.py
@@ -59,7 +59,8 @@ async def test_update_alarm_device(hass):
return_value=mock_coro(True)):
assert await async_setup_component(hass, 'spc', config) is True
- await hass.async_block_till_done()
+ await hass.async_block_till_done()
+
entity_id = 'alarm_control_panel.house'
assert hass.states.get(entity_id).state == STATE_ALARM_ARMED_AWAY
diff --git a/tests/components/test_upnp.py b/tests/components/test_upnp.py
deleted file mode 100644
index 6089e6859f2..00000000000
--- a/tests/components/test_upnp.py
+++ /dev/null
@@ -1,183 +0,0 @@
-"""Test the UPNP component."""
-from collections import OrderedDict
-from unittest.mock import patch, MagicMock
-
-import pytest
-
-from homeassistant.const import EVENT_HOMEASSISTANT_STOP
-from homeassistant.setup import async_setup_component
-from homeassistant.components.upnp import IP_SERVICE, DATA_UPNP
-
-
-class MockService(MagicMock):
- """Mock upnp IP service."""
-
- async def add_port_mapping(self, *args, **kwargs):
- """Original function."""
- self.mock_add_port_mapping(*args, **kwargs)
-
- async def delete_port_mapping(self, *args, **kwargs):
- """Original function."""
- self.mock_delete_port_mapping(*args, **kwargs)
-
-
-class MockDevice(MagicMock):
- """Mock upnp device."""
-
- def find_first_service(self, *args, **kwargs):
- """Original function."""
- self._service = MockService()
- return self._service
-
- def peep_first_service(self):
- """Access Mock first service."""
- return self._service
-
-
-class MockResp(MagicMock):
- """Mock upnp msearch response."""
-
- async def get_device(self, *args, **kwargs):
- """Original function."""
- device = MockDevice()
- service = {'serviceType': IP_SERVICE}
- device.services = [service]
- return device
-
-
-@pytest.fixture
-def mock_msearch_first(*args, **kwargs):
- """Wrap async mock msearch_first."""
- async def async_mock_msearch_first(*args, **kwargs):
- """Mock msearch_first."""
- return MockResp(*args, **kwargs)
-
- with patch('pyupnp_async.msearch_first', new=async_mock_msearch_first):
- yield
-
-
-@pytest.fixture
-def mock_async_exception(*args, **kwargs):
- """Wrap async mock exception."""
- async def async_mock_exception(*args, **kwargs):
- return Exception
-
- with patch('pyupnp_async.msearch_first', new=async_mock_exception):
- yield
-
-
-@pytest.fixture
-def mock_local_ip():
- """Mock get_local_ip."""
- with patch('homeassistant.components.upnp.get_local_ip',
- return_value='192.168.0.10'):
- yield
-
-
-async def test_setup_fail_if_no_ip(hass):
- """Test setup fails if we can't find a local IP."""
- with patch('homeassistant.components.upnp.get_local_ip',
- return_value='127.0.0.1'):
- result = await async_setup_component(hass, 'upnp', {
- 'upnp': {}
- })
-
- assert not result
-
-
-async def test_setup_fail_if_cannot_select_igd(hass,
- mock_local_ip,
- mock_async_exception):
- """Test setup fails if we can't find an UPnP IGD."""
- result = await async_setup_component(hass, 'upnp', {
- 'upnp': {}
- })
-
- assert not result
-
-
-async def test_setup_succeeds_if_specify_ip(hass, mock_msearch_first):
- """Test setup succeeds if we specify IP and can't find a local IP."""
- with patch('homeassistant.components.upnp.get_local_ip',
- return_value='127.0.0.1'):
- result = await async_setup_component(hass, 'upnp', {
- 'upnp': {
- 'local_ip': '192.168.0.10',
- 'port_mapping': 'True'
- }
- })
-
- assert result
- mock_service = hass.data[DATA_UPNP].peep_first_service()
- assert len(mock_service.mock_add_port_mapping.mock_calls) == 1
- mock_service.mock_add_port_mapping.assert_called_once_with(
- 8123, 8123, '192.168.0.10', 'TCP', desc='Home Assistant')
-
-
-async def test_no_config_maps_hass_local_to_remote_port(hass,
- mock_local_ip,
- mock_msearch_first):
- """Test by default we map local to remote port."""
- result = await async_setup_component(hass, 'upnp', {
- 'upnp': {
- 'port_mapping': 'True'
- }
- })
-
- assert result
- mock_service = hass.data[DATA_UPNP].peep_first_service()
- assert len(mock_service.mock_add_port_mapping.mock_calls) == 1
- mock_service.mock_add_port_mapping.assert_called_once_with(
- 8123, 8123, '192.168.0.10', 'TCP', desc='Home Assistant')
-
-
-async def test_map_hass_to_remote_port(hass,
- mock_local_ip,
- mock_msearch_first):
- """Test mapping hass to remote port."""
- result = await async_setup_component(hass, 'upnp', {
- 'upnp': {
- 'port_mapping': 'True',
- 'ports': {
- 'hass': 1000
- }
- }
- })
-
- assert result
- mock_service = hass.data[DATA_UPNP].peep_first_service()
- assert len(mock_service.mock_add_port_mapping.mock_calls) == 1
- mock_service.mock_add_port_mapping.assert_called_once_with(
- 8123, 1000, '192.168.0.10', 'TCP', desc='Home Assistant')
-
-
-async def test_map_internal_to_remote_ports(hass,
- mock_local_ip,
- mock_msearch_first):
- """Test mapping local to remote ports."""
- ports = OrderedDict()
- ports['hass'] = 1000
- ports[1883] = 3883
-
- result = await async_setup_component(hass, 'upnp', {
- 'upnp': {
- 'port_mapping': 'True',
- 'ports': ports
- }
- })
-
- assert result
- mock_service = hass.data[DATA_UPNP].peep_first_service()
- assert len(mock_service.mock_add_port_mapping.mock_calls) == 2
-
- mock_service.mock_add_port_mapping.assert_any_call(
- 8123, 1000, '192.168.0.10', 'TCP', desc='Home Assistant')
- mock_service.mock_add_port_mapping.assert_any_call(
- 1883, 3883, '192.168.0.10', 'TCP', desc='Home Assistant')
-
- hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
- await hass.async_block_till_done()
- assert len(mock_service.mock_delete_port_mapping.mock_calls) == 2
-
- mock_service.mock_delete_port_mapping.assert_any_call(1000, 'TCP')
- mock_service.mock_delete_port_mapping.assert_any_call(3883, 'TCP')
diff --git a/tests/components/test_webhook.py b/tests/components/test_webhook.py
new file mode 100644
index 00000000000..9434c3d98d5
--- /dev/null
+++ b/tests/components/test_webhook.py
@@ -0,0 +1,96 @@
+"""Test the webhook component."""
+from unittest.mock import Mock
+
+import pytest
+
+from homeassistant.setup import async_setup_component
+
+
+@pytest.fixture
+def mock_client(hass, aiohttp_client):
+ """Create http client for webhooks."""
+ hass.loop.run_until_complete(async_setup_component(hass, 'webhook', {}))
+ return hass.loop.run_until_complete(aiohttp_client(hass.http.app))
+
+
+async def test_unregistering_webhook(hass, mock_client):
+ """Test unregistering a webhook."""
+ hooks = []
+ webhook_id = hass.components.webhook.async_generate_id()
+
+ async def handle(*args):
+ """Handle webhook."""
+ hooks.append(args)
+
+ hass.components.webhook.async_register(webhook_id, handle)
+
+ resp = await mock_client.post('/api/webhook/{}'.format(webhook_id))
+ assert resp.status == 200
+ assert len(hooks) == 1
+
+ hass.components.webhook.async_unregister(webhook_id)
+
+ resp = await mock_client.post('/api/webhook/{}'.format(webhook_id))
+ assert resp.status == 200
+ assert len(hooks) == 1
+
+
+async def test_generate_webhook_url(hass):
+ """Test we generate a webhook url correctly."""
+ hass.config.api = Mock(base_url='https://example.com')
+ url = hass.components.webhook.async_generate_url('some_id')
+
+ assert url == 'https://example.com/api/webhook/some_id'
+
+
+async def test_posting_webhook_nonexisting(hass, mock_client):
+ """Test posting to a nonexisting webhook."""
+ resp = await mock_client.post('/api/webhook/non-existing')
+ assert resp.status == 200
+
+
+async def test_posting_webhook_invalid_json(hass, mock_client):
+ """Test posting to a nonexisting webhook."""
+ hass.components.webhook.async_register('hello', None)
+ resp = await mock_client.post('/api/webhook/hello', data='not-json')
+ assert resp.status == 200
+
+
+async def test_posting_webhook_json(hass, mock_client):
+ """Test posting a webhook with JSON data."""
+ hooks = []
+ webhook_id = hass.components.webhook.async_generate_id()
+
+ async def handle(*args):
+ """Handle webhook."""
+ hooks.append((args[0], args[1], await args[2].text()))
+
+ hass.components.webhook.async_register(webhook_id, handle)
+
+ resp = await mock_client.post('/api/webhook/{}'.format(webhook_id), json={
+ 'data': True
+ })
+ assert resp.status == 200
+ assert len(hooks) == 1
+ assert hooks[0][0] is hass
+ assert hooks[0][1] == webhook_id
+ assert hooks[0][2] == '{"data": true}'
+
+
+async def test_posting_webhook_no_data(hass, mock_client):
+ """Test posting a webhook with no data."""
+ hooks = []
+ webhook_id = hass.components.webhook.async_generate_id()
+
+ async def handle(*args):
+ """Handle webhook."""
+ hooks.append(args)
+
+ hass.components.webhook.async_register(webhook_id, handle)
+
+ resp = await mock_client.post('/api/webhook/{}'.format(webhook_id))
+ assert resp.status == 200
+ assert len(hooks) == 1
+ assert hooks[0][0] is hass
+ assert hooks[0][1] == webhook_id
+ assert await hooks[0][2].text() == ''
diff --git a/tests/components/test_websocket_api.py b/tests/components/test_websocket_api.py
deleted file mode 100644
index cf74081adb1..00000000000
--- a/tests/components/test_websocket_api.py
+++ /dev/null
@@ -1,558 +0,0 @@
-"""Tests for the Home Assistant Websocket API."""
-import asyncio
-from unittest.mock import patch, Mock
-
-from aiohttp import WSMsgType
-from async_timeout import timeout
-import pytest
-
-from homeassistant.core import callback
-from homeassistant.components import websocket_api as wapi
-from homeassistant.setup import async_setup_component
-
-from tests.common import mock_coro, async_mock_service
-
-API_PASSWORD = 'test1234'
-
-
-@pytest.fixture
-def websocket_client(hass, hass_ws_client):
- """Create a websocket client."""
- return hass.loop.run_until_complete(hass_ws_client(hass))
-
-
-@pytest.fixture
-def no_auth_websocket_client(hass, loop, aiohttp_client):
- """Websocket connection that requires authentication."""
- assert loop.run_until_complete(
- async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- }))
-
- client = loop.run_until_complete(aiohttp_client(hass.http.app))
- ws = loop.run_until_complete(client.ws_connect(wapi.URL))
-
- auth_ok = loop.run_until_complete(ws.receive_json())
- assert auth_ok['type'] == wapi.TYPE_AUTH_REQUIRED
-
- yield ws
-
- if not ws.closed:
- loop.run_until_complete(ws.close())
-
-
-@pytest.fixture
-def mock_low_queue():
- """Mock a low queue."""
- with patch.object(wapi, 'MAX_PENDING_MSG', 5):
- yield
-
-
-@asyncio.coroutine
-def test_auth_via_msg(no_auth_websocket_client):
- """Test authenticating."""
- yield from no_auth_websocket_client.send_json({
- 'type': wapi.TYPE_AUTH,
- 'api_password': API_PASSWORD
- })
-
- msg = yield from no_auth_websocket_client.receive_json()
-
- assert msg['type'] == wapi.TYPE_AUTH_OK
-
-
-@asyncio.coroutine
-def test_auth_via_msg_incorrect_pass(no_auth_websocket_client):
- """Test authenticating."""
- with patch('homeassistant.components.websocket_api.process_wrong_login',
- return_value=mock_coro()) as mock_process_wrong_login:
- yield from no_auth_websocket_client.send_json({
- 'type': wapi.TYPE_AUTH,
- 'api_password': API_PASSWORD + 'wrong'
- })
-
- msg = yield from no_auth_websocket_client.receive_json()
-
- assert mock_process_wrong_login.called
- assert msg['type'] == wapi.TYPE_AUTH_INVALID
- assert msg['message'] == 'Invalid access token or password'
-
-
-@asyncio.coroutine
-def test_pre_auth_only_auth_allowed(no_auth_websocket_client):
- """Verify that before authentication, only auth messages are allowed."""
- yield from no_auth_websocket_client.send_json({
- 'type': wapi.TYPE_CALL_SERVICE,
- 'domain': 'domain_test',
- 'service': 'test_service',
- 'service_data': {
- 'hello': 'world'
- }
- })
-
- msg = yield from no_auth_websocket_client.receive_json()
-
- assert msg['type'] == wapi.TYPE_AUTH_INVALID
- assert msg['message'].startswith('Message incorrectly formatted')
-
-
-@asyncio.coroutine
-def test_invalid_message_format(websocket_client):
- """Test sending invalid JSON."""
- yield from websocket_client.send_json({'type': 5})
-
- msg = yield from websocket_client.receive_json()
-
- assert msg['type'] == wapi.TYPE_RESULT
- error = msg['error']
- assert error['code'] == wapi.ERR_INVALID_FORMAT
- assert error['message'].startswith('Message incorrectly formatted')
-
-
-@asyncio.coroutine
-def test_invalid_json(websocket_client):
- """Test sending invalid JSON."""
- yield from websocket_client.send_str('this is not JSON')
-
- msg = yield from websocket_client.receive()
-
- assert msg.type == WSMsgType.close
-
-
-@asyncio.coroutine
-def test_quiting_hass(hass, websocket_client):
- """Test sending invalid JSON."""
- with patch.object(hass.loop, 'stop'):
- yield from hass.async_stop()
-
- msg = yield from websocket_client.receive()
-
- assert msg.type == WSMsgType.CLOSE
-
-
-@asyncio.coroutine
-def test_call_service(hass, websocket_client):
- """Test call service command."""
- calls = []
-
- @callback
- def service_call(call):
- calls.append(call)
-
- hass.services.async_register('domain_test', 'test_service', service_call)
-
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': wapi.TYPE_CALL_SERVICE,
- 'domain': 'domain_test',
- 'service': 'test_service',
- 'service_data': {
- 'hello': 'world'
- }
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
-
- assert len(calls) == 1
- call = calls[0]
-
- assert call.domain == 'domain_test'
- assert call.service == 'test_service'
- assert call.data == {'hello': 'world'}
-
-
-@asyncio.coroutine
-def test_subscribe_unsubscribe_events(hass, websocket_client):
- """Test subscribe/unsubscribe events command."""
- init_count = sum(hass.bus.async_listeners().values())
-
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': wapi.TYPE_SUBSCRIBE_EVENTS,
- 'event_type': 'test_event'
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
-
- # Verify we have a new listener
- assert sum(hass.bus.async_listeners().values()) == init_count + 1
-
- hass.bus.async_fire('ignore_event')
- hass.bus.async_fire('test_event', {'hello': 'world'})
- hass.bus.async_fire('ignore_event')
-
- with timeout(3, loop=hass.loop):
- msg = yield from websocket_client.receive_json()
-
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_EVENT
- event = msg['event']
-
- assert event['event_type'] == 'test_event'
- assert event['data'] == {'hello': 'world'}
- assert event['origin'] == 'LOCAL'
-
- yield from websocket_client.send_json({
- 'id': 6,
- 'type': wapi.TYPE_UNSUBSCRIBE_EVENTS,
- 'subscription': 5
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 6
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
-
- # Check our listener got unsubscribed
- assert sum(hass.bus.async_listeners().values()) == init_count
-
-
-@asyncio.coroutine
-def test_get_states(hass, websocket_client):
- """Test get_states command."""
- hass.states.async_set('greeting.hello', 'world')
- hass.states.async_set('greeting.bye', 'universe')
-
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': wapi.TYPE_GET_STATES,
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
-
- states = []
- for state in hass.states.async_all():
- state = state.as_dict()
- state['last_changed'] = state['last_changed'].isoformat()
- state['last_updated'] = state['last_updated'].isoformat()
- states.append(state)
-
- assert msg['result'] == states
-
-
-@asyncio.coroutine
-def test_get_services(hass, websocket_client):
- """Test get_services command."""
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': wapi.TYPE_GET_SERVICES,
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
- assert msg['result'] == hass.services.async_services()
-
-
-@asyncio.coroutine
-def test_get_config(hass, websocket_client):
- """Test get_config command."""
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': wapi.TYPE_GET_CONFIG,
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert msg['success']
-
- if 'components' in msg['result']:
- msg['result']['components'] = set(msg['result']['components'])
- if 'whitelist_external_dirs' in msg['result']:
- msg['result']['whitelist_external_dirs'] = \
- set(msg['result']['whitelist_external_dirs'])
-
- assert msg['result'] == hass.config.as_dict()
-
-
-@asyncio.coroutine
-def test_ping(websocket_client):
- """Test get_panels command."""
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': wapi.TYPE_PING,
- })
-
- msg = yield from websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_PONG
-
-
-@asyncio.coroutine
-def test_pending_msg_overflow(hass, mock_low_queue, websocket_client):
- """Test get_panels command."""
- for idx in range(10):
- yield from websocket_client.send_json({
- 'id': idx + 1,
- 'type': wapi.TYPE_PING,
- })
- msg = yield from websocket_client.receive()
- assert msg.type == WSMsgType.close
-
-
-@asyncio.coroutine
-def test_unknown_command(websocket_client):
- """Test get_panels command."""
- yield from websocket_client.send_json({
- 'id': 5,
- 'type': 'unknown_command',
- })
-
- msg = yield from websocket_client.receive_json()
- assert not msg['success']
- assert msg['error']['code'] == wapi.ERR_UNKNOWN_COMMAND
-
-
-async def test_auth_active_with_token(hass, aiohttp_client, hass_access_token):
- """Test authenticating with a token."""
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- with patch('homeassistant.auth.AuthManager.active') as auth_active:
- auth_active.return_value = True
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'access_token': hass_access_token
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_OK
-
-
-async def test_auth_active_user_inactive(hass, aiohttp_client,
- hass_access_token):
- """Test authenticating with a token."""
- refresh_token = await hass.auth.async_validate_access_token(
- hass_access_token)
- refresh_token.user.is_active = False
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- with patch('homeassistant.auth.AuthManager.active') as auth_active:
- auth_active.return_value = True
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'access_token': hass_access_token
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_INVALID
-
-
-async def test_auth_active_with_password_not_allow(hass, aiohttp_client):
- """Test authenticating with a token."""
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- with patch('homeassistant.auth.AuthManager.active',
- return_value=True):
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'api_password': API_PASSWORD
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_INVALID
-
-
-async def test_auth_legacy_support_with_password(hass, aiohttp_client):
- """Test authenticating with a token."""
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- with patch('homeassistant.auth.AuthManager.active',
- return_value=True),\
- patch('homeassistant.auth.AuthManager.support_legacy',
- return_value=True):
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'api_password': API_PASSWORD
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_OK
-
-
-async def test_auth_with_invalid_token(hass, aiohttp_client):
- """Test authenticating with a token."""
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- with patch('homeassistant.auth.AuthManager.active') as auth_active:
- auth_active.return_value = True
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'access_token': 'incorrect'
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_INVALID
-
-
-async def test_call_service_context_with_user(hass, aiohttp_client,
- hass_access_token):
- """Test that the user is set in the service call context."""
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- calls = async_mock_service(hass, 'domain_test', 'test_service')
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- with patch('homeassistant.auth.AuthManager.active') as auth_active:
- auth_active.return_value = True
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'access_token': hass_access_token
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_OK
-
- await ws.send_json({
- 'id': 5,
- 'type': wapi.TYPE_CALL_SERVICE,
- 'domain': 'domain_test',
- 'service': 'test_service',
- 'service_data': {
- 'hello': 'world'
- }
- })
-
- msg = await ws.receive_json()
- assert msg['success']
-
- refresh_token = await hass.auth.async_validate_access_token(
- hass_access_token)
-
- assert len(calls) == 1
- call = calls[0]
- assert call.domain == 'domain_test'
- assert call.service == 'test_service'
- assert call.data == {'hello': 'world'}
- assert call.context.user_id == refresh_token.user.id
-
-
-async def test_call_service_context_no_user(hass, aiohttp_client):
- """Test that connection without user sets context."""
- assert await async_setup_component(hass, 'websocket_api', {
- 'http': {
- 'api_password': API_PASSWORD
- }
- })
-
- calls = async_mock_service(hass, 'domain_test', 'test_service')
- client = await aiohttp_client(hass.http.app)
-
- async with client.ws_connect(wapi.URL) as ws:
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_REQUIRED
-
- await ws.send_json({
- 'type': wapi.TYPE_AUTH,
- 'api_password': API_PASSWORD
- })
-
- auth_msg = await ws.receive_json()
- assert auth_msg['type'] == wapi.TYPE_AUTH_OK
-
- await ws.send_json({
- 'id': 5,
- 'type': wapi.TYPE_CALL_SERVICE,
- 'domain': 'domain_test',
- 'service': 'test_service',
- 'service_data': {
- 'hello': 'world'
- }
- })
-
- msg = await ws.receive_json()
- assert msg['success']
-
- assert len(calls) == 1
- call = calls[0]
- assert call.domain == 'domain_test'
- assert call.service == 'test_service'
- assert call.data == {'hello': 'world'}
- assert call.context.user_id is None
-
-
-async def test_handler_failing(hass, websocket_client):
- """Test a command that raises."""
- hass.components.websocket_api.async_register_command(
- 'bla', Mock(side_effect=TypeError),
- wapi.BASE_COMMAND_MESSAGE_SCHEMA.extend({'type': 'bla'}))
- await websocket_client.send_json({
- 'id': 5,
- 'type': 'bla',
- })
-
- msg = await websocket_client.receive_json()
- assert msg['id'] == 5
- assert msg['type'] == wapi.TYPE_RESULT
- assert not msg['success']
- assert msg['error']['code'] == wapi.ERR_UNKNOWN_ERROR
diff --git a/tests/components/tradfri/conftest.py b/tests/components/tradfri/conftest.py
new file mode 100644
index 00000000000..9a5745264b7
--- /dev/null
+++ b/tests/components/tradfri/conftest.py
@@ -0,0 +1,12 @@
+"""Common tradfri test fixtures."""
+from unittest.mock import patch
+
+import pytest
+
+
+@pytest.fixture
+def mock_gateway_info():
+ """Mock get_gateway_info."""
+ with patch('homeassistant.components.tradfri.config_flow.'
+ 'get_gateway_info') as mock_gateway:
+ yield mock_gateway
diff --git a/tests/components/tradfri/test_config_flow.py b/tests/components/tradfri/test_config_flow.py
index 99566356f61..6756a01bbc7 100644
--- a/tests/components/tradfri/test_config_flow.py
+++ b/tests/components/tradfri/test_config_flow.py
@@ -17,14 +17,6 @@ def mock_auth():
yield mock_auth
-@pytest.fixture
-def mock_gateway_info():
- """Mock get_gateway_info."""
- with patch('homeassistant.components.tradfri.config_flow.'
- 'get_gateway_info') as mock_gateway:
- yield mock_gateway
-
-
@pytest.fixture
def mock_entry_setup():
"""Mock entry setup."""
@@ -125,34 +117,65 @@ async def test_discovery_connection(hass, mock_auth, mock_entry_setup):
}
-async def test_import_connection(hass, mock_gateway_info, mock_entry_setup):
+async def test_import_connection(hass, mock_auth, mock_entry_setup):
"""Test a connection via import."""
- mock_gateway_info.side_effect = \
- lambda hass, host, identity, key: mock_coro({
- 'host': host,
- 'identity': identity,
- 'key': key,
- 'gateway_id': 'mock-gateway'
- })
+ mock_auth.side_effect = lambda hass, host, code: mock_coro({
+ 'host': host,
+ 'gateway_id': 'bla',
+ 'identity': 'mock-iden',
+ 'key': 'mock-key',
+ })
- result = await hass.config_entries.flow.async_init(
+ flow = await hass.config_entries.flow.async_init(
'tradfri', context={'source': 'import'}, data={
'host': '123.123.123.123',
- 'identity': 'mock-iden',
- 'key': 'mock-key',
'import_groups': True
})
+ result = await hass.config_entries.flow.async_configure(flow['flow_id'], {
+ 'security_code': 'abcd',
+ })
+
assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
assert result['result'].data == {
'host': '123.123.123.123',
- 'gateway_id': 'mock-gateway',
+ 'gateway_id': 'bla',
'identity': 'mock-iden',
'key': 'mock-key',
'import_groups': True
}
- assert len(mock_gateway_info.mock_calls) == 1
+ assert len(mock_entry_setup.mock_calls) == 1
+
+
+async def test_import_connection_no_groups(hass, mock_auth, mock_entry_setup):
+ """Test a connection via import and no groups allowed."""
+ mock_auth.side_effect = lambda hass, host, code: mock_coro({
+ 'host': host,
+ 'gateway_id': 'bla',
+ 'identity': 'mock-iden',
+ 'key': 'mock-key',
+ })
+
+ flow = await hass.config_entries.flow.async_init(
+ 'tradfri', context={'source': 'import'}, data={
+ 'host': '123.123.123.123',
+ 'import_groups': False
+ })
+
+ result = await hass.config_entries.flow.async_configure(flow['flow_id'], {
+ 'security_code': 'abcd',
+ })
+
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['result'].data == {
+ 'host': '123.123.123.123',
+ 'gateway_id': 'bla',
+ 'identity': 'mock-iden',
+ 'key': 'mock-key',
+ 'import_groups': False
+ }
+
assert len(mock_entry_setup.mock_calls) == 1
@@ -187,6 +210,37 @@ async def test_import_connection_legacy(hass, mock_gateway_info,
assert len(mock_entry_setup.mock_calls) == 1
+async def test_import_connection_legacy_no_groups(
+ hass, mock_gateway_info, mock_entry_setup):
+ """Test a connection via legacy import and no groups allowed."""
+ mock_gateway_info.side_effect = \
+ lambda hass, host, identity, key: mock_coro({
+ 'host': host,
+ 'identity': identity,
+ 'key': key,
+ 'gateway_id': 'mock-gateway'
+ })
+
+ result = await hass.config_entries.flow.async_init(
+ 'tradfri', context={'source': 'import'}, data={
+ 'host': '123.123.123.123',
+ 'key': 'mock-key',
+ 'import_groups': False
+ })
+
+ assert result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY
+ assert result['result'].data == {
+ 'host': '123.123.123.123',
+ 'gateway_id': 'mock-gateway',
+ 'identity': 'homeassistant',
+ 'key': 'mock-key',
+ 'import_groups': False
+ }
+
+ assert len(mock_gateway_info.mock_calls) == 1
+ assert len(mock_entry_setup.mock_calls) == 1
+
+
async def test_discovery_duplicate_aborted(hass):
"""Test a duplicate discovery host is ignored."""
MockConfigEntry(
diff --git a/tests/components/tradfri/test_init.py b/tests/components/tradfri/test_init.py
index 4527e87f605..800c7b72ee6 100644
--- a/tests/components/tradfri/test_init.py
+++ b/tests/components/tradfri/test_init.py
@@ -58,7 +58,7 @@ async def test_config_json_host_not_imported(hass):
assert len(mock_init.mock_calls) == 0
-async def test_config_json_host_imported(hass):
+async def test_config_json_host_imported(hass, mock_gateway_info):
"""Test that we import a configured host."""
with patch('homeassistant.components.tradfri.load_json',
return_value={'mock-host': {'key': 'some-info'}}):
diff --git a/tests/components/upnp/__init__.py b/tests/components/upnp/__init__.py
new file mode 100644
index 00000000000..4fcc4167e5b
--- /dev/null
+++ b/tests/components/upnp/__init__.py
@@ -0,0 +1 @@
+"""Tests for the IGD component."""
diff --git a/tests/components/upnp/test_config_flow.py b/tests/components/upnp/test_config_flow.py
new file mode 100644
index 00000000000..3ff1316975f
--- /dev/null
+++ b/tests/components/upnp/test_config_flow.py
@@ -0,0 +1,240 @@
+"""Tests for UPnP/IGD config flow."""
+
+from homeassistant.components import upnp
+from homeassistant.components.upnp import config_flow as upnp_config_flow
+
+from tests.common import MockConfigEntry
+
+
+async def test_flow_none_discovered(hass):
+ """Test no device discovered flow."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+ hass.data[upnp.DOMAIN] = {
+ 'discovered': {}
+ }
+
+ result = await flow.async_step_user()
+ assert result['type'] == 'abort'
+ assert result['reason'] == 'no_devices_discovered'
+
+
+async def test_flow_already_configured(hass):
+ """Test device already configured flow."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # discovered device
+ udn = 'uuid:device_1'
+ hass.data[upnp.DOMAIN] = {
+ 'discovered': {
+ udn: {
+ 'friendly_name': '192.168.1.1 (Test device)',
+ 'host': '192.168.1.1',
+ 'udn': udn,
+ },
+ },
+ }
+
+ # configured entry
+ MockConfigEntry(domain=upnp.DOMAIN, data={
+ 'udn': udn,
+ 'host': '192.168.1.1',
+ }).add_to_hass(hass)
+
+ result = await flow.async_step_user({
+ 'name': '192.168.1.1 (Test device)',
+ 'enable_sensors': True,
+ 'enable_port_mapping': False,
+ })
+ assert result['type'] == 'abort'
+ assert result['reason'] == 'already_configured'
+
+
+async def test_flow_no_sensors_no_port_mapping(hass):
+ """Test single device, no sensors, no port_mapping."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # discovered device
+ udn = 'uuid:device_1'
+ hass.data[upnp.DOMAIN] = {
+ 'discovered': {
+ udn: {
+ 'friendly_name': '192.168.1.1 (Test device)',
+ 'host': '192.168.1.1',
+ 'udn': udn,
+ },
+ },
+ }
+
+ # configured entry
+ MockConfigEntry(domain=upnp.DOMAIN, data={
+ 'udn': udn,
+ 'host': '192.168.1.1',
+ }).add_to_hass(hass)
+
+ result = await flow.async_step_user({
+ 'name': '192.168.1.1 (Test device)',
+ 'enable_sensors': False,
+ 'enable_port_mapping': False,
+ })
+ assert result['type'] == 'abort'
+ assert result['reason'] == 'no_sensors_or_port_mapping'
+
+
+async def test_flow_discovered_form(hass):
+ """Test single device discovered, show form flow."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # discovered device
+ udn = 'uuid:device_1'
+ hass.data[upnp.DOMAIN] = {
+ 'discovered': {
+ udn: {
+ 'friendly_name': '192.168.1.1 (Test device)',
+ 'host': '192.168.1.1',
+ 'udn': udn,
+ },
+ },
+ }
+
+ result = await flow.async_step_user()
+ assert result['type'] == 'form'
+ assert result['step_id'] == 'user'
+
+
+async def test_flow_two_discovered_form(hass):
+ """Test two devices discovered, show form flow with two devices."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # discovered device
+ udn_1 = 'uuid:device_1'
+ udn_2 = 'uuid:device_2'
+ hass.data[upnp.DOMAIN] = {
+ 'discovered': {
+ udn_1: {
+ 'friendly_name': '192.168.1.1 (Test device)',
+ 'host': '192.168.1.1',
+ 'udn': udn_1,
+ },
+ udn_2: {
+ 'friendly_name': '192.168.2.1 (Test device)',
+ 'host': '192.168.2.1',
+ 'udn': udn_2,
+ },
+ },
+ }
+
+ result = await flow.async_step_user()
+ assert result['type'] == 'form'
+ assert result['step_id'] == 'user'
+ assert result['data_schema']({
+ 'name': '192.168.1.1 (Test device)',
+ 'enable_sensors': True,
+ 'enable_port_mapping': False,
+ })
+ assert result['data_schema']({
+ 'name': '192.168.2.1 (Test device)',
+ 'enable_sensors': True,
+ 'enable_port_mapping': False,
+ })
+
+
+async def test_config_entry_created(hass):
+ """Test config entry is created."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # discovered device
+ hass.data[upnp.DOMAIN] = {
+ 'discovered': {
+ 'uuid:device_1': {
+ 'friendly_name': '192.168.1.1 (Test device)',
+ 'name': 'Test device 1',
+ 'host': '192.168.1.1',
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': 'uuid:device_1',
+ },
+ },
+ }
+
+ result = await flow.async_step_user({
+ 'name': '192.168.1.1 (Test device)',
+ 'enable_sensors': True,
+ 'enable_port_mapping': False,
+ })
+ assert result['type'] == 'create_entry'
+ assert result['data'] == {
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': 'uuid:device_1',
+ 'port_mapping': False,
+ 'sensors': True,
+ }
+ assert result['title'] == 'Test device 1'
+
+
+async def test_flow_discovery_auto_config_sensors(hass):
+ """Test creation of device with auto_config."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # auto_config active
+ hass.data[upnp.DOMAIN] = {
+ 'auto_config': {
+ 'active': True,
+ 'enable_port_mapping': False,
+ 'enable_sensors': True,
+ },
+ }
+
+ # discovered device
+ result = await flow.async_step_discovery({
+ 'name': 'Test device 1',
+ 'host': '192.168.1.1',
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': 'uuid:device_1',
+ })
+
+ assert result['type'] == 'create_entry'
+ assert result['data'] == {
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': 'uuid:device_1',
+ 'sensors': True,
+ 'port_mapping': False,
+ }
+ assert result['title'] == 'Test device 1'
+
+
+async def test_flow_discovery_auto_config_sensors_port_mapping(hass):
+ """Test creation of device with auto_config, with port mapping."""
+ flow = upnp_config_flow.UpnpFlowHandler()
+ flow.hass = hass
+
+ # auto_config active, with port_mapping
+ hass.data[upnp.DOMAIN] = {
+ 'auto_config': {
+ 'active': True,
+ 'enable_port_mapping': True,
+ 'enable_sensors': True,
+ },
+ }
+
+ # discovered device
+ result = await flow.async_step_discovery({
+ 'name': 'Test device 1',
+ 'host': '192.168.1.1',
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': 'uuid:device_1',
+ })
+
+ assert result['type'] == 'create_entry'
+ assert result['data'] == {
+ 'udn': 'uuid:device_1',
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'sensors': True,
+ 'port_mapping': True,
+ }
+ assert result['title'] == 'Test device 1'
diff --git a/tests/components/upnp/test_init.py b/tests/components/upnp/test_init.py
new file mode 100644
index 00000000000..ce4656032a6
--- /dev/null
+++ b/tests/components/upnp/test_init.py
@@ -0,0 +1,188 @@
+"""Test UPnP/IGD setup process."""
+
+from ipaddress import ip_address
+from unittest.mock import patch, MagicMock
+
+from homeassistant.setup import async_setup_component
+from homeassistant.components import upnp
+from homeassistant.components.upnp.device import Device
+from homeassistant.const import EVENT_HOMEASSISTANT_STOP
+
+from tests.common import MockConfigEntry
+from tests.common import mock_coro
+
+
+class MockDevice(Device):
+ """Mock device for Device."""
+
+ def __init__(self, udn):
+ """Initializer."""
+ super().__init__(None)
+ self._udn = udn
+ self.added_port_mappings = []
+ self.removed_port_mappings = []
+
+ @classmethod
+ async def async_create_device(cls, hass, ssdp_description):
+ """Return self."""
+ return cls()
+
+ @property
+ def udn(self):
+ """Get the UDN."""
+ return self._udn
+
+ async def _async_add_port_mapping(self,
+ external_port,
+ local_ip,
+ internal_port):
+ """Add a port mapping."""
+ entry = [external_port, local_ip, internal_port]
+ self.added_port_mappings.append(entry)
+
+ async def _async_delete_port_mapping(self, external_port):
+ """Remove a port mapping."""
+ entry = external_port
+ self.removed_port_mappings.append(entry)
+
+
+async def test_async_setup_no_auto_config(hass):
+ """Test async_setup."""
+ # setup component, enable auto_config
+ await async_setup_component(hass, 'upnp')
+
+ assert hass.data[upnp.DOMAIN]['auto_config'] == {
+ 'active': False,
+ 'enable_sensors': False,
+ 'enable_port_mapping': False,
+ 'ports': {'hass': 'hass'},
+ }
+
+
+async def test_async_setup_auto_config(hass):
+ """Test async_setup."""
+ # setup component, enable auto_config
+ await async_setup_component(hass, 'upnp', {'upnp': {}, 'discovery': {}})
+
+ assert hass.data[upnp.DOMAIN]['auto_config'] == {
+ 'active': True,
+ 'enable_sensors': True,
+ 'enable_port_mapping': False,
+ 'ports': {'hass': 'hass'},
+ }
+
+
+async def test_async_setup_auto_config_port_mapping(hass):
+ """Test async_setup."""
+ # setup component, enable auto_config
+ await async_setup_component(hass, 'upnp', {
+ 'upnp': {
+ 'port_mapping': True,
+ 'ports': {'hass': 'hass'},
+ },
+ 'discovery': {}})
+
+ assert hass.data[upnp.DOMAIN]['auto_config'] == {
+ 'active': True,
+ 'enable_sensors': True,
+ 'enable_port_mapping': True,
+ 'ports': {'hass': 'hass'},
+ }
+
+
+async def test_async_setup_auto_config_no_sensors(hass):
+ """Test async_setup."""
+ # setup component, enable auto_config
+ await async_setup_component(hass, 'upnp', {
+ 'upnp': {'sensors': False},
+ 'discovery': {}})
+
+ assert hass.data[upnp.DOMAIN]['auto_config'] == {
+ 'active': True,
+ 'enable_sensors': False,
+ 'enable_port_mapping': False,
+ 'ports': {'hass': 'hass'},
+ }
+
+
+async def test_async_setup_entry_default(hass):
+ """Test async_setup_entry."""
+ udn = 'uuid:device_1'
+ entry = MockConfigEntry(domain=upnp.DOMAIN, data={
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': udn,
+ 'sensors': True,
+ 'port_mapping': False,
+ })
+
+ # ensure hass.http is available
+ await async_setup_component(hass, 'upnp')
+
+ # mock homeassistant.components.upnp.device.Device
+ mock_device = MagicMock()
+ mock_device.udn = udn
+ mock_device.async_add_port_mappings.return_value = mock_coro()
+ mock_device.async_delete_port_mappings.return_value = mock_coro()
+ with patch.object(Device, 'async_create_device') as mock_create_device:
+ mock_create_device.return_value = mock_coro(
+ return_value=mock_device)
+ with patch('homeassistant.components.upnp.device.get_local_ip',
+ return_value='192.168.1.10'):
+ assert await upnp.async_setup_entry(hass, entry) is True
+
+ # ensure device is stored/used
+ assert hass.data[upnp.DOMAIN]['devices'][udn] == mock_device
+
+ hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
+ await hass.async_block_till_done()
+
+ # ensure cleaned up
+ assert udn not in hass.data[upnp.DOMAIN]['devices']
+
+ # ensure no port-mapping-methods called
+ assert len(mock_device.async_add_port_mappings.mock_calls) == 0
+ assert len(mock_device.async_delete_port_mappings.mock_calls) == 0
+
+
+async def test_async_setup_entry_port_mapping(hass):
+ """Test async_setup_entry."""
+ udn = 'uuid:device_1'
+ entry = MockConfigEntry(domain=upnp.DOMAIN, data={
+ 'ssdp_description': 'http://192.168.1.1/desc.xml',
+ 'udn': udn,
+ 'sensors': False,
+ 'port_mapping': True,
+ })
+
+ # ensure hass.http is available
+ await async_setup_component(hass, 'upnp', {
+ 'upnp': {
+ 'port_mapping': True,
+ 'ports': {'hass': 'hass'},
+ },
+ 'discovery': {},
+ })
+
+ mock_device = MockDevice(udn)
+ with patch.object(Device, 'async_create_device') as mock_create_device:
+ mock_create_device.return_value = mock_coro(return_value=mock_device)
+ with patch('homeassistant.components.upnp.device.get_local_ip',
+ return_value='192.168.1.10'):
+ assert await upnp.async_setup_entry(hass, entry) is True
+
+ # ensure device is stored/used
+ assert hass.data[upnp.DOMAIN]['devices'][udn] == mock_device
+
+ # ensure add-port-mapping-methods called
+ assert mock_device.added_port_mappings == [
+ [8123, ip_address('192.168.1.10'), 8123]
+ ]
+
+ hass.bus.async_fire(EVENT_HOMEASSISTANT_STOP)
+ await hass.async_block_till_done()
+
+ # ensure cleaned up
+ assert udn not in hass.data[upnp.DOMAIN]['devices']
+
+ # ensure delete-port-mapping-methods called
+ assert mock_device.removed_port_mappings == [8123]
diff --git a/tests/components/vacuum/common.py b/tests/components/vacuum/common.py
new file mode 100644
index 00000000000..436f23f5546
--- /dev/null
+++ b/tests/components/vacuum/common.py
@@ -0,0 +1,101 @@
+"""Collection of helper methods.
+
+All containing methods are legacy helpers that should not be used by new
+components. Instead call the service directly.
+"""
+from homeassistant.components.vacuum import (
+ ATTR_FAN_SPEED, ATTR_PARAMS, DOMAIN, SERVICE_CLEAN_SPOT, SERVICE_LOCATE,
+ SERVICE_PAUSE, SERVICE_SEND_COMMAND, SERVICE_SET_FAN_SPEED, SERVICE_START,
+ SERVICE_START_PAUSE, SERVICE_STOP, SERVICE_RETURN_TO_BASE)
+from homeassistant.const import (
+ ATTR_COMMAND, ATTR_ENTITY_ID, SERVICE_TOGGLE,
+ SERVICE_TURN_OFF, SERVICE_TURN_ON)
+from homeassistant.loader import bind_hass
+
+
+@bind_hass
+def turn_on(hass, entity_id=None):
+ """Turn all or specified vacuum on."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
+
+
+@bind_hass
+def turn_off(hass, entity_id=None):
+ """Turn all or specified vacuum off."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
+
+
+@bind_hass
+def toggle(hass, entity_id=None):
+ """Toggle all or specified vacuum."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
+
+
+@bind_hass
+def locate(hass, entity_id=None):
+ """Locate all or specified vacuum."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_LOCATE, data)
+
+
+@bind_hass
+def clean_spot(hass, entity_id=None):
+ """Tell all or specified vacuum to perform a spot clean-up."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_CLEAN_SPOT, data)
+
+
+@bind_hass
+def return_to_base(hass, entity_id=None):
+ """Tell all or specified vacuum to return to base."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_RETURN_TO_BASE, data)
+
+
+@bind_hass
+def start_pause(hass, entity_id=None):
+ """Tell all or specified vacuum to start or pause the current task."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_START_PAUSE, data)
+
+
+@bind_hass
+def start(hass, entity_id=None):
+ """Tell all or specified vacuum to start or resume the current task."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_START, data)
+
+
+@bind_hass
+def pause(hass, entity_id=None):
+ """Tell all or the specified vacuum to pause the current task."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_PAUSE, data)
+
+
+@bind_hass
+def stop(hass, entity_id=None):
+ """Stop all or specified vacuum."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
+ hass.services.call(DOMAIN, SERVICE_STOP, data)
+
+
+@bind_hass
+def set_fan_speed(hass, fan_speed, entity_id=None):
+ """Set fan speed for all or specified vacuum."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ data[ATTR_FAN_SPEED] = fan_speed
+ hass.services.call(DOMAIN, SERVICE_SET_FAN_SPEED, data)
+
+
+@bind_hass
+def send_command(hass, command, params=None, entity_id=None):
+ """Send command to all or specified vacuum."""
+ data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
+ data[ATTR_COMMAND] = command
+ if params is not None:
+ data[ATTR_PARAMS] = params
+ hass.services.call(DOMAIN, SERVICE_SEND_COMMAND, data)
diff --git a/tests/components/vacuum/test_demo.py b/tests/components/vacuum/test_demo.py
index 1fc8f8cd5c1..f88908ecc41 100644
--- a/tests/components/vacuum/test_demo.py
+++ b/tests/components/vacuum/test_demo.py
@@ -15,7 +15,9 @@ from homeassistant.components.vacuum.demo import (
from homeassistant.const import (
ATTR_SUPPORTED_FEATURES, CONF_PLATFORM, STATE_OFF, STATE_ON)
from homeassistant.setup import setup_component
+
from tests.common import get_test_home_assistant, mock_service
+from tests.components.vacuum import common
ENTITY_VACUUM_BASIC = '{}.{}'.format(DOMAIN, DEMO_VACUUM_BASIC).lower()
@@ -108,27 +110,27 @@ class TestVacuumDemo(unittest.TestCase):
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass))
- vacuum.turn_on(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.turn_on(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.turn_off(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.turn_off(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.toggle(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.toggle(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.start_pause(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.start_pause(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.start_pause(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.start_pause(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.stop(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.stop(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
@@ -136,39 +138,39 @@ class TestVacuumDemo(unittest.TestCase):
self.assertLess(state.attributes.get(ATTR_BATTERY_LEVEL), 100)
self.assertNotEqual("Charging", state.attributes.get(ATTR_STATUS))
- vacuum.locate(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.locate(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertIn("I'm over here", state.attributes.get(ATTR_STATUS))
- vacuum.return_to_base(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.return_to_base(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertIn("Returning home", state.attributes.get(ATTR_STATUS))
- vacuum.set_fan_speed(self.hass, FAN_SPEEDS[-1],
+ common.set_fan_speed(self.hass, FAN_SPEEDS[-1],
entity_id=ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertEqual(FAN_SPEEDS[-1], state.attributes.get(ATTR_FAN_SPEED))
- vacuum.clean_spot(self.hass, entity_id=ENTITY_VACUUM_COMPLETE)
+ common.clean_spot(self.hass, entity_id=ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
self.assertIn("spot", state.attributes.get(ATTR_STATUS))
self.assertEqual(STATE_ON, state.state)
- vacuum.start(self.hass, ENTITY_VACUUM_STATE)
+ common.start(self.hass, ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertEqual(STATE_CLEANING, state.state)
- vacuum.pause(self.hass, ENTITY_VACUUM_STATE)
+ common.pause(self.hass, ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertEqual(STATE_PAUSED, state.state)
- vacuum.stop(self.hass, ENTITY_VACUUM_STATE)
+ common.stop(self.hass, ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertEqual(STATE_IDLE, state.state)
@@ -177,18 +179,18 @@ class TestVacuumDemo(unittest.TestCase):
self.assertLess(state.attributes.get(ATTR_BATTERY_LEVEL), 100)
self.assertNotEqual(STATE_DOCKED, state.state)
- vacuum.return_to_base(self.hass, ENTITY_VACUUM_STATE)
+ common.return_to_base(self.hass, ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertEqual(STATE_RETURNING, state.state)
- vacuum.set_fan_speed(self.hass, FAN_SPEEDS[-1],
+ common.set_fan_speed(self.hass, FAN_SPEEDS[-1],
entity_id=ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertEqual(FAN_SPEEDS[-1], state.attributes.get(ATTR_FAN_SPEED))
- vacuum.clean_spot(self.hass, entity_id=ENTITY_VACUUM_STATE)
+ common.clean_spot(self.hass, entity_id=ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertEqual(STATE_CLEANING, state.state)
@@ -199,11 +201,11 @@ class TestVacuumDemo(unittest.TestCase):
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
- vacuum.turn_off(self.hass, ENTITY_VACUUM_NONE)
+ common.turn_off(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
- vacuum.stop(self.hass, ENTITY_VACUUM_NONE)
+ common.stop(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
@@ -211,37 +213,37 @@ class TestVacuumDemo(unittest.TestCase):
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
- vacuum.turn_on(self.hass, ENTITY_VACUUM_NONE)
+ common.turn_on(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
- vacuum.toggle(self.hass, ENTITY_VACUUM_NONE)
+ common.toggle(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
# Non supported methods:
- vacuum.start_pause(self.hass, ENTITY_VACUUM_NONE)
+ common.start_pause(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_NONE))
- vacuum.locate(self.hass, ENTITY_VACUUM_NONE)
+ common.locate(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_NONE)
self.assertIsNone(state.attributes.get(ATTR_STATUS))
- vacuum.return_to_base(self.hass, ENTITY_VACUUM_NONE)
+ common.return_to_base(self.hass, ENTITY_VACUUM_NONE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_NONE)
self.assertIsNone(state.attributes.get(ATTR_STATUS))
- vacuum.set_fan_speed(self.hass, FAN_SPEEDS[-1],
+ common.set_fan_speed(self.hass, FAN_SPEEDS[-1],
entity_id=ENTITY_VACUUM_NONE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_NONE)
self.assertNotEqual(FAN_SPEEDS[-1],
state.attributes.get(ATTR_FAN_SPEED))
- vacuum.clean_spot(self.hass, entity_id=ENTITY_VACUUM_BASIC)
+ common.clean_spot(self.hass, entity_id=ENTITY_VACUUM_BASIC)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_BASIC)
self.assertNotIn("spot", state.attributes.get(ATTR_STATUS))
@@ -252,7 +254,7 @@ class TestVacuumDemo(unittest.TestCase):
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.pause(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.pause(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertTrue(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
@@ -260,22 +262,22 @@ class TestVacuumDemo(unittest.TestCase):
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
- vacuum.start(self.hass, ENTITY_VACUUM_COMPLETE)
+ common.start(self.hass, ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
self.assertFalse(vacuum.is_on(self.hass, ENTITY_VACUUM_COMPLETE))
# StateVacuumDevice does not support on/off
- vacuum.turn_on(self.hass, entity_id=ENTITY_VACUUM_STATE)
+ common.turn_on(self.hass, entity_id=ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertNotEqual(STATE_CLEANING, state.state)
- vacuum.turn_off(self.hass, entity_id=ENTITY_VACUUM_STATE)
+ common.turn_off(self.hass, entity_id=ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertNotEqual(STATE_RETURNING, state.state)
- vacuum.toggle(self.hass, entity_id=ENTITY_VACUUM_STATE)
+ common.toggle(self.hass, entity_id=ENTITY_VACUUM_STATE)
self.hass.block_till_done()
state = self.hass.states.get(ENTITY_VACUUM_STATE)
self.assertNotEqual(STATE_CLEANING, state.state)
@@ -287,7 +289,7 @@ class TestVacuumDemo(unittest.TestCase):
self.hass, DOMAIN, SERVICE_SEND_COMMAND)
params = {"rotate": 150, "speed": 20}
- vacuum.send_command(
+ common.send_command(
self.hass, 'test_command', entity_id=ENTITY_VACUUM_BASIC,
params=params)
@@ -305,7 +307,7 @@ class TestVacuumDemo(unittest.TestCase):
set_fan_speed_calls = mock_service(
self.hass, DOMAIN, SERVICE_SET_FAN_SPEED)
- vacuum.set_fan_speed(
+ common.set_fan_speed(
self.hass, FAN_SPEEDS[0], entity_id=ENTITY_VACUUM_COMPLETE)
self.hass.block_till_done()
@@ -326,7 +328,7 @@ class TestVacuumDemo(unittest.TestCase):
old_state_complete = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
old_state_state = self.hass.states.get(ENTITY_VACUUM_STATE)
- vacuum.set_fan_speed(
+ common.set_fan_speed(
self.hass, FAN_SPEEDS[0], entity_id=group_vacuums)
self.hass.block_till_done()
@@ -356,7 +358,7 @@ class TestVacuumDemo(unittest.TestCase):
old_state_basic = self.hass.states.get(ENTITY_VACUUM_BASIC)
old_state_complete = self.hass.states.get(ENTITY_VACUUM_COMPLETE)
- vacuum.send_command(
+ common.send_command(
self.hass, 'test_command', params={"p1": 3},
entity_id=group_vacuums)
diff --git a/tests/components/vacuum/test_mqtt.py b/tests/components/vacuum/test_mqtt.py
index ba2288e3fc6..ddd4289c24d 100644
--- a/tests/components/vacuum/test_mqtt.py
+++ b/tests/components/vacuum/test_mqtt.py
@@ -9,8 +9,10 @@ from homeassistant.components.mqtt import CONF_COMMAND_TOPIC
from homeassistant.const import (
CONF_PLATFORM, STATE_OFF, STATE_ON, STATE_UNAVAILABLE, CONF_NAME)
from homeassistant.setup import setup_component
+
from tests.common import (
fire_mqtt_message, get_test_home_assistant, mock_mqtt_component)
+from tests.components.vacuum import common
class TestVacuumMQTT(unittest.TestCase):
@@ -69,55 +71,55 @@ class TestVacuumMQTT(unittest.TestCase):
vacuum.DOMAIN: self.default_config,
}))
- vacuum.turn_on(self.hass, 'vacuum.mqtttest')
+ common.turn_on(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'turn_on', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.turn_off(self.hass, 'vacuum.mqtttest')
+ common.turn_off(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'turn_off', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.stop(self.hass, 'vacuum.mqtttest')
+ common.stop(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'stop', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.clean_spot(self.hass, 'vacuum.mqtttest')
+ common.clean_spot(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'clean_spot', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.locate(self.hass, 'vacuum.mqtttest')
+ common.locate(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'locate', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.start_pause(self.hass, 'vacuum.mqtttest')
+ common.start_pause(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'start_pause', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.return_to_base(self.hass, 'vacuum.mqtttest')
+ common.return_to_base(self.hass, 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/command', 'return_to_base', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.set_fan_speed(self.hass, 'high', 'vacuum.mqtttest')
+ common.set_fan_speed(self.hass, 'high', 'vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/set_fan_speed', 'high', 0, False)
self.mock_publish.async_publish.reset_mock()
- vacuum.send_command(self.hass, '44 FE 93', entity_id='vacuum.mqtttest')
+ common.send_command(self.hass, '44 FE 93', entity_id='vacuum.mqtttest')
self.hass.block_till_done()
self.mock_publish.async_publish.assert_called_once_with(
'vacuum/send_command', '44 FE 93', 0, False)
diff --git a/tests/components/websocket_api/__init__.py b/tests/components/websocket_api/__init__.py
new file mode 100644
index 00000000000..c218c6165d4
--- /dev/null
+++ b/tests/components/websocket_api/__init__.py
@@ -0,0 +1,2 @@
+"""Tests for the websocket API."""
+API_PASSWORD = 'test1234'
diff --git a/tests/components/websocket_api/conftest.py b/tests/components/websocket_api/conftest.py
new file mode 100644
index 00000000000..b7825600cb1
--- /dev/null
+++ b/tests/components/websocket_api/conftest.py
@@ -0,0 +1,36 @@
+"""Fixtures for websocket tests."""
+import pytest
+
+from homeassistant.setup import async_setup_component
+from homeassistant.components.websocket_api.http import URL
+from homeassistant.components.websocket_api.auth import TYPE_AUTH_REQUIRED
+
+from . import API_PASSWORD
+
+
+@pytest.fixture
+def websocket_client(hass, hass_ws_client):
+ """Create a websocket client."""
+ return hass.loop.run_until_complete(hass_ws_client(hass))
+
+
+@pytest.fixture
+def no_auth_websocket_client(hass, loop, aiohttp_client):
+ """Websocket connection that requires authentication."""
+ assert loop.run_until_complete(
+ async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ }))
+
+ client = loop.run_until_complete(aiohttp_client(hass.http.app))
+ ws = loop.run_until_complete(client.ws_connect(URL))
+
+ auth_ok = loop.run_until_complete(ws.receive_json())
+ assert auth_ok['type'] == TYPE_AUTH_REQUIRED
+
+ yield ws
+
+ if not ws.closed:
+ loop.run_until_complete(ws.close())
diff --git a/tests/components/websocket_api/test_auth.py b/tests/components/websocket_api/test_auth.py
new file mode 100644
index 00000000000..ed54b509aaa
--- /dev/null
+++ b/tests/components/websocket_api/test_auth.py
@@ -0,0 +1,190 @@
+"""Test auth of websocket API."""
+from unittest.mock import patch
+
+from homeassistant.components.websocket_api.const import URL
+from homeassistant.components.websocket_api.auth import (
+ TYPE_AUTH, TYPE_AUTH_INVALID, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED)
+
+from homeassistant.components.websocket_api import commands
+from homeassistant.setup import async_setup_component
+
+from tests.common import mock_coro
+
+from . import API_PASSWORD
+
+
+async def test_auth_via_msg(no_auth_websocket_client):
+ """Test authenticating."""
+ await no_auth_websocket_client.send_json({
+ 'type': TYPE_AUTH,
+ 'api_password': API_PASSWORD
+ })
+
+ msg = await no_auth_websocket_client.receive_json()
+
+ assert msg['type'] == TYPE_AUTH_OK
+
+
+async def test_auth_via_msg_incorrect_pass(no_auth_websocket_client):
+ """Test authenticating."""
+ with patch('homeassistant.components.websocket_api.auth.'
+ 'process_wrong_login', return_value=mock_coro()) \
+ as mock_process_wrong_login:
+ await no_auth_websocket_client.send_json({
+ 'type': TYPE_AUTH,
+ 'api_password': API_PASSWORD + 'wrong'
+ })
+
+ msg = await no_auth_websocket_client.receive_json()
+
+ assert mock_process_wrong_login.called
+ assert msg['type'] == TYPE_AUTH_INVALID
+ assert msg['message'] == 'Invalid access token or password'
+
+
+async def test_pre_auth_only_auth_allowed(no_auth_websocket_client):
+ """Verify that before authentication, only auth messages are allowed."""
+ await no_auth_websocket_client.send_json({
+ 'type': commands.TYPE_CALL_SERVICE,
+ 'domain': 'domain_test',
+ 'service': 'test_service',
+ 'service_data': {
+ 'hello': 'world'
+ }
+ })
+
+ msg = await no_auth_websocket_client.receive_json()
+
+ assert msg['type'] == TYPE_AUTH_INVALID
+ assert msg['message'].startswith('Auth message incorrectly formatted')
+
+
+async def test_auth_active_with_token(hass, aiohttp_client, hass_access_token):
+ """Test authenticating with a token."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active') as auth_active:
+ auth_active.return_value = True
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'access_token': hass_access_token
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_OK
+
+
+async def test_auth_active_user_inactive(hass, aiohttp_client,
+ hass_access_token):
+ """Test authenticating with a token."""
+ refresh_token = await hass.auth.async_validate_access_token(
+ hass_access_token)
+ refresh_token.user.is_active = False
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active') as auth_active:
+ auth_active.return_value = True
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'access_token': hass_access_token
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_INVALID
+
+
+async def test_auth_active_with_password_not_allow(hass, aiohttp_client):
+ """Test authenticating with a token."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active',
+ return_value=True):
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'api_password': API_PASSWORD
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_INVALID
+
+
+async def test_auth_legacy_support_with_password(hass, aiohttp_client):
+ """Test authenticating with a token."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active',
+ return_value=True),\
+ patch('homeassistant.auth.AuthManager.support_legacy',
+ return_value=True):
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'api_password': API_PASSWORD
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_OK
+
+
+async def test_auth_with_invalid_token(hass, aiohttp_client):
+ """Test authenticating with a token."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active') as auth_active:
+ auth_active.return_value = True
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'access_token': 'incorrect'
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_INVALID
diff --git a/tests/components/websocket_api/test_commands.py b/tests/components/websocket_api/test_commands.py
new file mode 100644
index 00000000000..84c29533859
--- /dev/null
+++ b/tests/components/websocket_api/test_commands.py
@@ -0,0 +1,263 @@
+"""Tests for WebSocket API commands."""
+from unittest.mock import patch
+
+from async_timeout import timeout
+
+from homeassistant.core import callback
+from homeassistant.components.websocket_api.const import URL
+from homeassistant.components.websocket_api.auth import (
+ TYPE_AUTH, TYPE_AUTH_OK, TYPE_AUTH_REQUIRED
+)
+from homeassistant.components.websocket_api import const, commands
+from homeassistant.setup import async_setup_component
+
+from tests.common import async_mock_service
+
+from . import API_PASSWORD
+
+
+async def test_call_service(hass, websocket_client):
+ """Test call service command."""
+ calls = []
+
+ @callback
+ def service_call(call):
+ calls.append(call)
+
+ hass.services.async_register('domain_test', 'test_service', service_call)
+
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_CALL_SERVICE,
+ 'domain': 'domain_test',
+ 'service': 'test_service',
+ 'service_data': {
+ 'hello': 'world'
+ }
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == const.TYPE_RESULT
+ assert msg['success']
+
+ assert len(calls) == 1
+ call = calls[0]
+
+ assert call.domain == 'domain_test'
+ assert call.service == 'test_service'
+ assert call.data == {'hello': 'world'}
+
+
+async def test_subscribe_unsubscribe_events(hass, websocket_client):
+ """Test subscribe/unsubscribe events command."""
+ init_count = sum(hass.bus.async_listeners().values())
+
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_SUBSCRIBE_EVENTS,
+ 'event_type': 'test_event'
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == const.TYPE_RESULT
+ assert msg['success']
+
+ # Verify we have a new listener
+ assert sum(hass.bus.async_listeners().values()) == init_count + 1
+
+ hass.bus.async_fire('ignore_event')
+ hass.bus.async_fire('test_event', {'hello': 'world'})
+ hass.bus.async_fire('ignore_event')
+
+ with timeout(3, loop=hass.loop):
+ msg = await websocket_client.receive_json()
+
+ assert msg['id'] == 5
+ assert msg['type'] == commands.TYPE_EVENT
+ event = msg['event']
+
+ assert event['event_type'] == 'test_event'
+ assert event['data'] == {'hello': 'world'}
+ assert event['origin'] == 'LOCAL'
+
+ await websocket_client.send_json({
+ 'id': 6,
+ 'type': commands.TYPE_UNSUBSCRIBE_EVENTS,
+ 'subscription': 5
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 6
+ assert msg['type'] == const.TYPE_RESULT
+ assert msg['success']
+
+ # Check our listener got unsubscribed
+ assert sum(hass.bus.async_listeners().values()) == init_count
+
+
+async def test_get_states(hass, websocket_client):
+ """Test get_states command."""
+ hass.states.async_set('greeting.hello', 'world')
+ hass.states.async_set('greeting.bye', 'universe')
+
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_GET_STATES,
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == const.TYPE_RESULT
+ assert msg['success']
+
+ states = []
+ for state in hass.states.async_all():
+ state = state.as_dict()
+ state['last_changed'] = state['last_changed'].isoformat()
+ state['last_updated'] = state['last_updated'].isoformat()
+ states.append(state)
+
+ assert msg['result'] == states
+
+
+async def test_get_services(hass, websocket_client):
+ """Test get_services command."""
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_GET_SERVICES,
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == const.TYPE_RESULT
+ assert msg['success']
+ assert msg['result'] == hass.services.async_services()
+
+
+async def test_get_config(hass, websocket_client):
+ """Test get_config command."""
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_GET_CONFIG,
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == const.TYPE_RESULT
+ assert msg['success']
+
+ if 'components' in msg['result']:
+ msg['result']['components'] = set(msg['result']['components'])
+ if 'whitelist_external_dirs' in msg['result']:
+ msg['result']['whitelist_external_dirs'] = \
+ set(msg['result']['whitelist_external_dirs'])
+
+ assert msg['result'] == hass.config.as_dict()
+
+
+async def test_ping(websocket_client):
+ """Test get_panels command."""
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_PING,
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == commands.TYPE_PONG
+
+
+async def test_call_service_context_with_user(hass, aiohttp_client,
+ hass_access_token):
+ """Test that the user is set in the service call context."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ calls = async_mock_service(hass, 'domain_test', 'test_service')
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ with patch('homeassistant.auth.AuthManager.active') as auth_active:
+ auth_active.return_value = True
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'access_token': hass_access_token
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_OK
+
+ await ws.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_CALL_SERVICE,
+ 'domain': 'domain_test',
+ 'service': 'test_service',
+ 'service_data': {
+ 'hello': 'world'
+ }
+ })
+
+ msg = await ws.receive_json()
+ assert msg['success']
+
+ refresh_token = await hass.auth.async_validate_access_token(
+ hass_access_token)
+
+ assert len(calls) == 1
+ call = calls[0]
+ assert call.domain == 'domain_test'
+ assert call.service == 'test_service'
+ assert call.data == {'hello': 'world'}
+ assert call.context.user_id == refresh_token.user.id
+
+
+async def test_call_service_context_no_user(hass, aiohttp_client):
+ """Test that connection without user sets context."""
+ assert await async_setup_component(hass, 'websocket_api', {
+ 'http': {
+ 'api_password': API_PASSWORD
+ }
+ })
+
+ calls = async_mock_service(hass, 'domain_test', 'test_service')
+ client = await aiohttp_client(hass.http.app)
+
+ async with client.ws_connect(URL) as ws:
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_REQUIRED
+
+ await ws.send_json({
+ 'type': TYPE_AUTH,
+ 'api_password': API_PASSWORD
+ })
+
+ auth_msg = await ws.receive_json()
+ assert auth_msg['type'] == TYPE_AUTH_OK
+
+ await ws.send_json({
+ 'id': 5,
+ 'type': commands.TYPE_CALL_SERVICE,
+ 'domain': 'domain_test',
+ 'service': 'test_service',
+ 'service_data': {
+ 'hello': 'world'
+ }
+ })
+
+ msg = await ws.receive_json()
+ assert msg['success']
+
+ assert len(calls) == 1
+ call = calls[0]
+ assert call.domain == 'domain_test'
+ assert call.service == 'test_service'
+ assert call.data == {'hello': 'world'}
+ assert call.context.user_id is None
diff --git a/tests/components/websocket_api/test_init.py b/tests/components/websocket_api/test_init.py
new file mode 100644
index 00000000000..a7e54e8146a
--- /dev/null
+++ b/tests/components/websocket_api/test_init.py
@@ -0,0 +1,92 @@
+"""Tests for the Home Assistant Websocket API."""
+import asyncio
+from unittest.mock import patch, Mock
+
+from aiohttp import WSMsgType
+import pytest
+
+from homeassistant.components.websocket_api import const, commands, messages
+
+
+@pytest.fixture
+def mock_low_queue():
+ """Mock a low queue."""
+ with patch('homeassistant.components.websocket_api.http.MAX_PENDING_MSG',
+ 5):
+ yield
+
+
+@asyncio.coroutine
+def test_invalid_message_format(websocket_client):
+ """Test sending invalid JSON."""
+ yield from websocket_client.send_json({'type': 5})
+
+ msg = yield from websocket_client.receive_json()
+
+ assert msg['type'] == const.TYPE_RESULT
+ error = msg['error']
+ assert error['code'] == const.ERR_INVALID_FORMAT
+ assert error['message'].startswith('Message incorrectly formatted')
+
+
+@asyncio.coroutine
+def test_invalid_json(websocket_client):
+ """Test sending invalid JSON."""
+ yield from websocket_client.send_str('this is not JSON')
+
+ msg = yield from websocket_client.receive()
+
+ assert msg.type == WSMsgType.close
+
+
+@asyncio.coroutine
+def test_quiting_hass(hass, websocket_client):
+ """Test sending invalid JSON."""
+ with patch.object(hass.loop, 'stop'):
+ yield from hass.async_stop()
+
+ msg = yield from websocket_client.receive()
+
+ assert msg.type == WSMsgType.CLOSE
+
+
+@asyncio.coroutine
+def test_pending_msg_overflow(hass, mock_low_queue, websocket_client):
+ """Test get_panels command."""
+ for idx in range(10):
+ yield from websocket_client.send_json({
+ 'id': idx + 1,
+ 'type': commands.TYPE_PING,
+ })
+ msg = yield from websocket_client.receive()
+ assert msg.type == WSMsgType.close
+
+
+@asyncio.coroutine
+def test_unknown_command(websocket_client):
+ """Test get_panels command."""
+ yield from websocket_client.send_json({
+ 'id': 5,
+ 'type': 'unknown_command',
+ })
+
+ msg = yield from websocket_client.receive_json()
+ assert not msg['success']
+ assert msg['error']['code'] == const.ERR_UNKNOWN_COMMAND
+
+
+async def test_handler_failing(hass, websocket_client):
+ """Test a command that raises."""
+ hass.components.websocket_api.async_register_command(
+ 'bla', Mock(side_effect=TypeError),
+ messages.BASE_COMMAND_MESSAGE_SCHEMA.extend({'type': 'bla'}))
+ await websocket_client.send_json({
+ 'id': 5,
+ 'type': 'bla',
+ })
+
+ msg = await websocket_client.receive_json()
+ assert msg['id'] == 5
+ assert msg['type'] == const.TYPE_RESULT
+ assert not msg['success']
+ assert msg['error']['code'] == const.ERR_UNKNOWN_ERROR
diff --git a/tests/components/zwave/test_init.py b/tests/components/zwave/test_init.py
index 1857d14ad84..a2290d8aabf 100644
--- a/tests/components/zwave/test_init.py
+++ b/tests/components/zwave/test_init.py
@@ -458,6 +458,33 @@ def test_network_complete(hass, mock_openzwave):
assert len(events) == 1
+@asyncio.coroutine
+def test_network_complete_some_dead(hass, mock_openzwave):
+ """Test Node network complete some dead event."""
+ mock_receivers = []
+
+ def mock_connect(receiver, signal, *args, **kwargs):
+ if signal == MockNetwork.SIGNAL_ALL_NODES_QUERIED_SOME_DEAD:
+ mock_receivers.append(receiver)
+
+ with patch('pydispatch.dispatcher.connect', new=mock_connect):
+ yield from async_setup_component(hass, 'zwave', {'zwave': {}})
+
+ assert len(mock_receivers) == 1
+
+ events = []
+
+ def listener(event):
+ events.append(event)
+
+ hass.bus.async_listen(const.EVENT_NETWORK_COMPLETE_SOME_DEAD, listener)
+
+ hass.async_add_job(mock_receivers[0])
+ yield from hass.async_block_till_done()
+
+ assert len(events) == 1
+
+
class TestZWaveDeviceEntityValues(unittest.TestCase):
"""Tests for the ZWaveDeviceEntityValues helper."""
@@ -749,9 +776,11 @@ class TestZWaveDeviceEntityValues(unittest.TestCase):
self.device_config = {'mock_component.registry_id': {
zwave.CONF_IGNORED: True
}}
- self.registry.async_get_or_create(
- 'mock_component', zwave.DOMAIN, '567-1000',
- suggested_object_id='registry_id')
+ with patch.object(self.registry, 'async_schedule_save'):
+ self.registry.async_get_or_create(
+ 'mock_component', zwave.DOMAIN, '567-1000',
+ suggested_object_id='registry_id')
+
zwave.ZWaveDeviceEntityValues(
hass=self.hass,
schema=self.mock_schema,
@@ -1359,6 +1388,53 @@ class TestZWaveServices(unittest.TestCase):
assert node.refresh_info.called
assert len(node.refresh_info.mock_calls) == 1
+ def test_set_node_value(self):
+ """Test zwave set_node_value service."""
+ value = MockValue(
+ index=12,
+ command_class=const.COMMAND_CLASS_INDICATOR,
+ data=4
+ )
+ node = MockNode(node_id=14,
+ command_classes=[const.COMMAND_CLASS_INDICATOR])
+ node.values = {12: value}
+ node.get_values.return_value = node.values
+ self.zwave_network.nodes = {14: node}
+
+ self.hass.services.call('zwave', 'set_node_value', {
+ const.ATTR_NODE_ID: 14,
+ const.ATTR_VALUE_ID: 12,
+ const.ATTR_CONFIG_VALUE: 2,
+ })
+ self.hass.block_till_done()
+
+ assert self.zwave_network.nodes[14].values[12].data == 2
+
+ def test_refresh_node_value(self):
+ """Test zwave refresh_node_value service."""
+ node = MockNode(node_id=14,
+ command_classes=[const.COMMAND_CLASS_INDICATOR],
+ network=self.zwave_network)
+ value = MockValue(
+ node=node,
+ index=12,
+ command_class=const.COMMAND_CLASS_INDICATOR,
+ data=2
+ )
+ value.refresh = MagicMock()
+
+ node.values = {12: value}
+ node.get_values.return_value = node.values
+ self.zwave_network.nodes = {14: node}
+
+ self.hass.services.call('zwave', 'refresh_node_value', {
+ const.ATTR_NODE_ID: 14,
+ const.ATTR_VALUE_ID: 12
+ })
+ self.hass.block_till_done()
+
+ assert value.refresh.called
+
def test_heal_node(self):
"""Test zwave heal_node service."""
node = MockNode(node_id=19)
diff --git a/tests/fixtures/geo_rss_events.xml b/tests/fixtures/geo_rss_events.xml
deleted file mode 100644
index 212994756d2..00000000000
--- a/tests/fixtures/geo_rss_events.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-
-
-
-
- -
- Title 1
- Description 1
- Category 1
- Sun, 30 Jul 2017 09:00:00 UTC
- GUID 1
- -32.916667 151.75
-
-
- -
- Title 2
- Description 2
- Category 2
- Sun, 30 Jul 2017 09:05:00 GMT
- GUID 2
- 148.601111
- -32.256944
-
-
- -
- Title 3
- Description 3
- Category 3
- Sun, 30 Jul 2017 09:05:00 GMT
- GUID 3
-
- -33.283333 149.1
- -33.2999997 149.1
- -33.2999997 149.1166663888889
- -33.283333 149.1166663888889
- -33.283333 149.1
-
-
-
- -
- Title 4
- Description 4
- Category 4
- Sun, 30 Jul 2017 09:15:00 GMT
- GUID 4
- 52.518611 13.408333
-
-
- -
- Title 5
- Description 5
- Category 5
- Sun, 30 Jul 2017 09:20:00 GMT
- GUID 5
-
-
-
- -
- Title 6
- Description 6
- Category 6
- 2017-07-30T09:25:00.000Z
- Link 6
- -33.75801 150.70544
-
-
- -
- Title 1
- Description 1
- Category 1
- Sun, 30 Jul 2017 09:00:00 UTC
- GUID 1
- 45.256 -110.45 46.46 -109.48 43.84 -109.86
-
-
-
\ No newline at end of file
diff --git a/tests/helpers/test_device_registry.py b/tests/helpers/test_device_registry.py
index b251846c491..a87ad3d483a 100644
--- a/tests/helpers/test_device_registry.py
+++ b/tests/helpers/test_device_registry.py
@@ -17,7 +17,10 @@ async def test_get_or_create_returns_same_entry(registry):
config_entry_id='1234',
connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
identifiers={('bridgeid', '0123')},
- manufacturer='manufacturer', model='model')
+ sw_version='sw-version',
+ name='name',
+ manufacturer='manufacturer',
+ model='model')
entry2 = registry.async_get_or_create(
config_entry_id='1234',
connections={('ethernet', '11:22:33:44:55:66:77:88')},
@@ -25,15 +28,19 @@ async def test_get_or_create_returns_same_entry(registry):
manufacturer='manufacturer', model='model')
entry3 = registry.async_get_or_create(
config_entry_id='1234',
- connections={('ethernet', '12:34:56:78:90:AB:CD:EF')},
- identifiers={('bridgeid', '1234')},
- manufacturer='manufacturer', model='model')
+ connections={('ethernet', '12:34:56:78:90:AB:CD:EF')}
+ )
assert len(registry.devices) == 1
assert entry.id == entry2.id
assert entry.id == entry3.id
assert entry.identifiers == {('bridgeid', '0123')}
+ assert entry3.manufacturer == 'manufacturer'
+ assert entry3.model == 'model'
+ assert entry3.name == 'name'
+ assert entry3.sw_version == 'sw-version'
+
async def test_requirement_for_identifier_or_connection(registry):
"""Make sure we do require some descriptor of device."""
diff --git a/tests/helpers/test_entity_platform.py b/tests/helpers/test_entity_platform.py
index 631d446d186..e985771e486 100644
--- a/tests/helpers/test_entity_platform.py
+++ b/tests/helpers/test_entity_platform.py
@@ -719,6 +719,8 @@ async def test_device_info_called(hass):
assert await entity_platform.async_setup_entry(config_entry)
await hass.async_block_till_done()
+ assert len(hass.states.async_entity_ids()) == 2
+
device = registry.async_get_device({('hue', '1234')}, set())
assert device is not None
assert device.identifiers == {('hue', '1234')}
@@ -728,3 +730,45 @@ async def test_device_info_called(hass):
assert device.name == 'test-name'
assert device.sw_version == 'test-sw'
assert device.hub_device_id == hub.id
+
+
+async def test_device_info_not_overrides(hass):
+ """Test device info is forwarded correctly."""
+ registry = await hass.helpers.device_registry.async_get_registry()
+ device = registry.async_get_or_create(
+ config_entry_id='bla',
+ connections={('mac', 'abcd')},
+ manufacturer='test-manufacturer',
+ model='test-model'
+ )
+
+ assert device.manufacturer == 'test-manufacturer'
+ assert device.model == 'test-model'
+
+ async def async_setup_entry(hass, config_entry, async_add_entities):
+ """Mock setup entry method."""
+ async_add_entities([
+ MockEntity(unique_id='qwer', device_info={
+ 'connections': {('mac', 'abcd')},
+ }),
+ ])
+ return True
+
+ platform = MockPlatform(
+ async_setup_entry=async_setup_entry
+ )
+ config_entry = MockConfigEntry(entry_id='super-mock-id')
+ entity_platform = MockEntityPlatform(
+ hass,
+ platform_name=config_entry.domain,
+ platform=platform
+ )
+
+ assert await entity_platform.async_setup_entry(config_entry)
+ await hass.async_block_till_done()
+
+ device2 = registry.async_get_device(set(), {('mac', 'abcd')})
+ assert device2 is not None
+ assert device.id == device2.id
+ assert device2.manufacturer == 'test-manufacturer'
+ assert device2.model == 'test-model'
diff --git a/tests/helpers/test_event.py b/tests/helpers/test_event.py
index deefcec773a..5b57ca75d51 100644
--- a/tests/helpers/test_event.py
+++ b/tests/helpers/test_event.py
@@ -13,6 +13,7 @@ import homeassistant.core as ha
from homeassistant.const import MATCH_ALL
from homeassistant.helpers.event import (
async_call_later,
+ call_later,
track_point_in_utc_time,
track_point_in_time,
track_utc_time_change,
@@ -645,6 +646,22 @@ class TestEventHelpers(unittest.TestCase):
self.hass.block_till_done()
self.assertEqual(0, len(specific_runs))
+ def test_call_later(self):
+ """Test calling an action later."""
+ def action(): pass
+ now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC)
+
+ with patch('homeassistant.helpers.event'
+ '.async_track_point_in_utc_time') as mock, \
+ patch('homeassistant.util.dt.utcnow', return_value=now):
+ call_later(self.hass, 3, action)
+
+ assert len(mock.mock_calls) == 1
+ p_hass, p_action, p_point = mock.mock_calls[0][1]
+ assert p_hass is self.hass
+ assert p_action is action
+ assert p_point == now + timedelta(seconds=3)
+
@asyncio.coroutine
def test_async_call_later(hass):
@@ -659,7 +676,7 @@ def test_async_call_later(hass):
assert len(mock.mock_calls) == 1
p_hass, p_action, p_point = mock.mock_calls[0][1]
- assert hass is hass
+ assert p_hass is hass
assert p_action is action
assert p_point == now + timedelta(seconds=3)
assert remove is mock()
diff --git a/tests/helpers/test_storage.py b/tests/helpers/test_storage.py
index 6cb75899d35..38b8a7cd380 100644
--- a/tests/helpers/test_storage.py
+++ b/tests/helpers/test_storage.py
@@ -15,6 +15,7 @@ from tests.common import async_fire_time_changed, mock_coro
MOCK_VERSION = 1
MOCK_KEY = 'storage-test'
MOCK_DATA = {'hello': 'world'}
+MOCK_DATA2 = {'goodbye': 'cruel world'}
@pytest.fixture
diff --git a/tests/helpers/test_template.py b/tests/helpers/test_template.py
index 6f426c290c5..dc8106e0ed3 100644
--- a/tests/helpers/test_template.py
+++ b/tests/helpers/test_template.py
@@ -562,6 +562,36 @@ class TestHelpersTemplate(unittest.TestCase):
""", self.hass)
self.assertEqual('LHR', tpl.render())
+ def test_bitwise_and(self):
+ """Test bitwise_and method."""
+ tpl = template.Template("""
+{{ 8 | bitwise_and(8) }}
+ """, self.hass)
+ self.assertEqual(str(8 & 8), tpl.render())
+ tpl = template.Template("""
+{{ 10 | bitwise_and(2) }}
+ """, self.hass)
+ self.assertEqual(str(10 & 2), tpl.render())
+ tpl = template.Template("""
+{{ 8 | bitwise_and(2) }}
+ """, self.hass)
+ self.assertEqual(str(8 & 2), tpl.render())
+
+ def test_bitwise_or(self):
+ """Test bitwise_or method."""
+ tpl = template.Template("""
+{{ 8 | bitwise_or(8) }}
+ """, self.hass)
+ self.assertEqual(str(8 | 8), tpl.render())
+ tpl = template.Template("""
+{{ 10 | bitwise_or(2) }}
+ """, self.hass)
+ self.assertEqual(str(10 | 2), tpl.render())
+ tpl = template.Template("""
+{{ 8 | bitwise_or(2) }}
+ """, self.hass)
+ self.assertEqual(str(8 | 2), tpl.render())
+
def test_distance_function_with_1_state(self):
"""Test distance function with 1 state."""
self.hass.states.set('test.object', 'happy', {
diff --git a/tests/mock/zwave.py b/tests/mock/zwave.py
index 59d97ddb621..36735b1693b 100644
--- a/tests/mock/zwave.py
+++ b/tests/mock/zwave.py
@@ -88,7 +88,8 @@ class MockNetwork(MagicMock):
SIGNAL_NODE_QUERIES_COMPLETE = 'mock_NodeQueriesComplete'
SIGNAL_AWAKE_NODES_QUERIED = 'mock_AwakeNodesQueried'
SIGNAL_ALL_NODES_QUERIED = 'mock_AllNodesQueried'
- SIGNAL_ALL_NODES_QUERIED_SOME_DEAD = 'mock_AllNodesQueriedSomeDead'
+ SIGNAL_ALL_NODES_QUERIED_SOME_DEAD = \
+ 'mock_AllNodesQueriedSomeDead'
SIGNAL_MSG_COMPLETE = 'mock_MsgComplete'
SIGNAL_NOTIFICATION = 'mock_Notification'
SIGNAL_CONTROLLER_COMMAND = 'mock_ControllerCommand'
diff --git a/tests/test_config_entries.py b/tests/test_config_entries.py
index 57d63eb8271..340118502b1 100644
--- a/tests/test_config_entries.py
+++ b/tests/test_config_entries.py
@@ -6,6 +6,7 @@ from unittest.mock import MagicMock, patch
import pytest
from homeassistant import config_entries, loader, data_entry_flow
+from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.setup import async_setup_component
from homeassistant.util import dt
@@ -49,7 +50,11 @@ def test_remove_entry(hass, manager):
MockModule('comp', async_unload_entry=mock_unload_entry))
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
- MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager)
+ MockConfigEntry(
+ domain='test',
+ entry_id='test2',
+ state=config_entries.ENTRY_STATE_LOADED
+ ).add_to_manager(manager)
MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
assert [item.entry_id for item in manager.async_entries()] == \
@@ -79,7 +84,11 @@ def test_remove_entry_raises(hass, manager):
MockModule('comp', async_unload_entry=mock_unload_entry))
MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
- MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager)
+ MockConfigEntry(
+ domain='test',
+ entry_id='test2',
+ state=config_entries.ENTRY_STATE_LOADED
+ ).add_to_manager(manager)
MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
assert [item.entry_id for item in manager.async_entries()] == \
@@ -94,6 +103,33 @@ def test_remove_entry_raises(hass, manager):
['test1', 'test3']
+@asyncio.coroutine
+def test_remove_entry_if_not_loaded(hass, manager):
+ """Test that we can remove an entry."""
+ mock_unload_entry = MagicMock(return_value=mock_coro(True))
+
+ loader.set_component(
+ hass, 'test',
+ MockModule('comp', async_unload_entry=mock_unload_entry))
+
+ MockConfigEntry(domain='test', entry_id='test1').add_to_manager(manager)
+ MockConfigEntry(domain='test', entry_id='test2').add_to_manager(manager)
+ MockConfigEntry(domain='test', entry_id='test3').add_to_manager(manager)
+
+ assert [item.entry_id for item in manager.async_entries()] == \
+ ['test1', 'test2', 'test3']
+
+ result = yield from manager.async_remove('test2')
+
+ assert result == {
+ 'require_restart': False
+ }
+ assert [item.entry_id for item in manager.async_entries()] == \
+ ['test1', 'test3']
+
+ assert len(mock_unload_entry.mock_calls) == 1
+
+
@asyncio.coroutine
def test_add_entry_calls_setup_entry(hass, manager):
"""Test we call setup_config_entry."""
@@ -315,3 +351,66 @@ async def test_loading_default_config(hass):
await manager.async_load()
assert len(manager.async_entries()) == 0
+
+
+async def test_updating_entry_data(manager):
+ """Test that we can update an entry data."""
+ entry = MockConfigEntry(
+ domain='test',
+ data={'first': True},
+ )
+ entry.add_to_manager(manager)
+
+ manager.async_update_entry(entry, data={
+ 'second': True
+ })
+
+ assert entry.data == {
+ 'second': True
+ }
+
+
+async def test_setup_raise_not_ready(hass, caplog):
+ """Test a setup raising not ready."""
+ entry = MockConfigEntry(domain='test')
+
+ mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
+ loader.set_component(
+ hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry))
+
+ with patch('homeassistant.helpers.event.async_call_later') as mock_call:
+ await entry.async_setup(hass)
+
+ assert len(mock_call.mock_calls) == 1
+ assert 'Config entry for test not ready yet' in caplog.text
+ p_hass, p_wait_time, p_setup = mock_call.mock_calls[0][1]
+
+ assert p_hass is hass
+ assert p_wait_time == 5
+ assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY
+
+ mock_setup_entry.side_effect = None
+ mock_setup_entry.return_value = mock_coro(True)
+
+ await p_setup(None)
+ assert entry.state == config_entries.ENTRY_STATE_LOADED
+
+
+async def test_setup_retrying_during_unload(hass):
+ """Test if we unload an entry that is in retry mode."""
+ entry = MockConfigEntry(domain='test')
+
+ mock_setup_entry = MagicMock(side_effect=ConfigEntryNotReady)
+ loader.set_component(
+ hass, 'test', MockModule('test', async_setup_entry=mock_setup_entry))
+
+ with patch('homeassistant.helpers.event.async_call_later') as mock_call:
+ await entry.async_setup(hass)
+
+ assert entry.state == config_entries.ENTRY_STATE_SETUP_RETRY
+ assert len(mock_call.return_value.mock_calls) == 0
+
+ await entry.async_unload(hass)
+
+ assert entry.state == config_entries.ENTRY_STATE_NOT_LOADED
+ assert len(mock_call.return_value.mock_calls) == 1
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 4beb7db570e..c4adb971593 100644
--- a/tests/test_loader.py
+++ b/tests/test_loader.py
@@ -79,10 +79,10 @@ def test_component_loader_non_existing(hass):
@asyncio.coroutine
def test_component_wrapper(hass):
"""Test component wrapper."""
- calls = async_mock_service(hass, 'light', 'turn_on')
+ calls = async_mock_service(hass, 'persistent_notification', 'create')
components = loader.Components(hass)
- components.light.async_turn_on('light.test')
+ components.persistent_notification.async_create('message')
yield from hass.async_block_till_done()
assert len(calls) == 1
diff --git a/tests/util/test_json.py b/tests/util/test_json.py
new file mode 100644
index 00000000000..53f62682b5e
--- /dev/null
+++ b/tests/util/test_json.py
@@ -0,0 +1,75 @@
+"""Test Home Assistant json utility functions."""
+import os
+import unittest
+import sys
+from tempfile import mkdtemp
+
+from homeassistant.util.json import (SerializationError,
+ load_json, save_json)
+from homeassistant.exceptions import HomeAssistantError
+
+# Test data that can be saved as JSON
+TEST_JSON_A = {"a": 1, "B": "two"}
+TEST_JSON_B = {"a": "one", "B": 2}
+# Test data that can not be saved as JSON (keys must be strings)
+TEST_BAD_OBJECT = {("A",): 1}
+# Test data that can not be loaded as JSON
+TEST_BAD_SERIALIED = "THIS IS NOT JSON\n"
+
+
+class TestJSON(unittest.TestCase):
+ """Test util.json save and load."""
+
+ def setUp(self):
+ """Set up for tests."""
+ self.tmp_dir = mkdtemp()
+
+ def tearDown(self):
+ """Clean up after tests."""
+ for fname in os.listdir(self.tmp_dir):
+ os.remove(os.path.join(self.tmp_dir, fname))
+ os.rmdir(self.tmp_dir)
+
+ def _path_for(self, leaf_name):
+ return os.path.join(self.tmp_dir, leaf_name+".json")
+
+ def test_save_and_load(self):
+ """Test saving and loading back."""
+ fname = self._path_for("test1")
+ save_json(fname, TEST_JSON_A)
+ data = load_json(fname)
+ self.assertEqual(data, TEST_JSON_A)
+
+ # Skipped on Windows
+ @unittest.skipIf(sys.platform.startswith('win'),
+ "private permissions not supported on Windows")
+ def test_save_and_load_private(self):
+ """Test we can load private files and that they are protected."""
+ fname = self._path_for("test2")
+ save_json(fname, TEST_JSON_A, private=True)
+ data = load_json(fname)
+ self.assertEqual(data, TEST_JSON_A)
+ stats = os.stat(fname)
+ self.assertEqual(stats.st_mode & 0o77, 0)
+
+ def test_overwrite_and_reload(self):
+ """Test that we can overwrite an existing file and read back."""
+ fname = self._path_for("test3")
+ save_json(fname, TEST_JSON_A)
+ save_json(fname, TEST_JSON_B)
+ data = load_json(fname)
+ self.assertEqual(data, TEST_JSON_B)
+
+ def test_save_bad_data(self):
+ """Test error from trying to save unserialisable data."""
+ fname = self._path_for("test4")
+ with self.assertRaises(SerializationError):
+ save_json(fname, TEST_BAD_OBJECT)
+
+ def test_load_bad_data(self):
+ """Test error from trying to load unserialisable data."""
+ fname = self._path_for("test5")
+ with open(fname, "w") as fh:
+ fh.write(TEST_BAD_SERIALIED)
+ with self.assertRaises(HomeAssistantError):
+ load_json(fname)
diff --git a/tox.ini b/tox.ini
index 60dacd5d8cb..dcfb209ef3a 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,6 +13,7 @@ whitelist_externals = /usr/bin/env
install_command = /usr/bin/env LANG=C.UTF-8 pip install {opts} {packages}
commands =
pytest --timeout=9 --duration=10 {posargs}
+ {toxinidir}/script/check_dirty
deps =
-r{toxinidir}/requirements_test_all.txt
-c{toxinidir}/homeassistant/package_constraints.txt
@@ -29,6 +30,7 @@ whitelist_externals = /usr/bin/env
install_command = /usr/bin/env LANG=C.UTF-8 pip install {opts} {packages}
commands =
pytest --timeout=9 --duration=10 --cov --cov-report= {posargs}
+ {toxinidir}/script/check_dirty
deps =
-r{toxinidir}/requirements_test_all.txt
-c{toxinidir}/homeassistant/package_constraints.txt