Merge pull request #17361 from home-assistant/rc

0.80.0
This commit is contained in:
Paulus Schoutsen 2018-10-12 16:57:52 +02:00 committed by GitHub
commit 8eb4e77365
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
724 changed files with 15578 additions and 9144 deletions

View File

@ -28,6 +28,9 @@ omit =
homeassistant/components/apple_tv.py homeassistant/components/apple_tv.py
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
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/*/bbb_gpio.py homeassistant/components/*/bbb_gpio.py
homeassistant/components/blink.py homeassistant/components/blink/*
homeassistant/components/*/blink.py homeassistant/components/*/blink.py
homeassistant/components/bloomsky.py homeassistant/components/bloomsky.py
@ -105,8 +108,11 @@ omit =
homeassistant/components/envisalink.py homeassistant/components/envisalink.py
homeassistant/components/*/envisalink.py homeassistant/components/*/envisalink.py
homeassistant/components/evohome.py
homeassistant/components/*/evohome.py
homeassistant/components/fritzbox.py homeassistant/components/fritzbox.py
homeassistant/components/switch/fritzbox.py homeassistant/components/*/fritzbox.py
homeassistant/components/ecovacs.py homeassistant/components/ecovacs.py
homeassistant/components/*/ecovacs.py homeassistant/components/*/ecovacs.py
@ -310,6 +316,9 @@ omit =
homeassistant/components/*/thinkingcleaner.py homeassistant/components/*/thinkingcleaner.py
homeassistant/components/tibber/*
homeassistant/components/*/tibber.py
homeassistant/components/toon.py homeassistant/components/toon.py
homeassistant/components/*/toon.py homeassistant/components/*/toon.py
@ -510,6 +519,7 @@ omit =
homeassistant/components/light/lw12wifi.py homeassistant/components/light/lw12wifi.py
homeassistant/components/light/mystrom.py homeassistant/components/light/mystrom.py
homeassistant/components/light/nanoleaf_aurora.py homeassistant/components/light/nanoleaf_aurora.py
homeassistant/components/light/opple.py
homeassistant/components/light/osramlightify.py homeassistant/components/light/osramlightify.py
homeassistant/components/light/piglow.py homeassistant/components/light/piglow.py
homeassistant/components/light/rpi_gpio_pwm.py homeassistant/components/light/rpi_gpio_pwm.py
@ -620,7 +630,6 @@ omit =
homeassistant/components/notify/telstra.py homeassistant/components/notify/telstra.py
homeassistant/components/notify/twitter.py homeassistant/components/notify/twitter.py
homeassistant/components/notify/xmpp.py homeassistant/components/notify/xmpp.py
homeassistant/components/notify/yessssms.py
homeassistant/components/nuimo_controller.py homeassistant/components/nuimo_controller.py
homeassistant/components/prometheus.py homeassistant/components/prometheus.py
homeassistant/components/rainbird.py homeassistant/components/rainbird.py
@ -679,6 +688,7 @@ omit =
homeassistant/components/sensor/fritzbox_netmonitor.py homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py homeassistant/components/sensor/gearbest.py
homeassistant/components/sensor/geizhals.py homeassistant/components/sensor/geizhals.py
homeassistant/components/sensor/gitlab_ci.py
homeassistant/components/sensor/gitter.py homeassistant/components/sensor/gitter.py
homeassistant/components/sensor/glances.py homeassistant/components/sensor/glances.py
homeassistant/components/sensor/google_travel_time.py homeassistant/components/sensor/google_travel_time.py
@ -687,6 +697,7 @@ omit =
homeassistant/components/sensor/haveibeenpwned.py homeassistant/components/sensor/haveibeenpwned.py
homeassistant/components/sensor/hp_ilo.py homeassistant/components/sensor/hp_ilo.py
homeassistant/components/sensor/htu21d.py homeassistant/components/sensor/htu21d.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/imap_email_content.py homeassistant/components/sensor/imap_email_content.py
homeassistant/components/sensor/imap.py homeassistant/components/sensor/imap.py
homeassistant/components/sensor/influxdb.py homeassistant/components/sensor/influxdb.py
@ -709,6 +720,7 @@ omit =
homeassistant/components/sensor/mqtt_room.py homeassistant/components/sensor/mqtt_room.py
homeassistant/components/sensor/mvglive.py homeassistant/components/sensor/mvglive.py
homeassistant/components/sensor/nederlandse_spoorwegen.py homeassistant/components/sensor/nederlandse_spoorwegen.py
homeassistant/components/sensor/netatmo_public.py
homeassistant/components/sensor/netdata.py homeassistant/components/sensor/netdata.py
homeassistant/components/sensor/netdata_public.py homeassistant/components/sensor/netdata_public.py
homeassistant/components/sensor/neurio_energy.py homeassistant/components/sensor/neurio_energy.py
@ -764,7 +776,6 @@ omit =
homeassistant/components/sensor/tank_utility.py homeassistant/components/sensor/tank_utility.py
homeassistant/components/sensor/ted5000.py homeassistant/components/sensor/ted5000.py
homeassistant/components/sensor/temper.py homeassistant/components/sensor/temper.py
homeassistant/components/sensor/tibber.py
homeassistant/components/sensor/time_date.py homeassistant/components/sensor/time_date.py
homeassistant/components/sensor/torque.py homeassistant/components/sensor/torque.py
homeassistant/components/sensor/trafikverket_weatherstation.py homeassistant/components/sensor/trafikverket_weatherstation.py
@ -772,7 +783,6 @@ omit =
homeassistant/components/sensor/travisci.py homeassistant/components/sensor/travisci.py
homeassistant/components/sensor/twitch.py homeassistant/components/sensor/twitch.py
homeassistant/components/sensor/uber.py homeassistant/components/sensor/uber.py
homeassistant/components/sensor/upnp.py
homeassistant/components/sensor/ups.py homeassistant/components/sensor/ups.py
homeassistant/components/sensor/uscis.py homeassistant/components/sensor/uscis.py
homeassistant/components/sensor/vasttrafik.py homeassistant/components/sensor/vasttrafik.py

View File

@ -41,6 +41,7 @@ homeassistant/components/hassio.py @home-assistant/hassio
# Individual components # Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell 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/binary_sensor/hikvision.py @mezz64
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/camera/yi.py @bachya 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/group.py @cdce8p
homeassistant/components/cover/template.py @PhracturedBlue homeassistant/components/cover/template.py @PhracturedBlue
homeassistant/components/device_tracker/automatic.py @armills homeassistant/components/device_tracker/automatic.py @armills
homeassistant/components/device_tracker/huawei_router.py @abmantis
homeassistant/components/device_tracker/tile.py @bachya homeassistant/components/device_tracker/tile.py @bachya
homeassistant/components/history_graph.py @andrey-git homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/lifx.py @amelchio 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/sma.py @kellerza
homeassistant/components/sensor/sql.py @dgomes homeassistant/components/sensor/sql.py @dgomes
homeassistant/components/sensor/sytadin.py @gautric 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/sensor/waqi.py @andrey-git
homeassistant/components/switch/tplink.py @rytilahti homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/vacuum/roomba.py @pschmitt homeassistant/components/vacuum/roomba.py @pschmitt
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/axis.py @kane610 homeassistant/components/*/axis.py @kane610
homeassistant/components/blink/* @fronzbot
homeassistant/components/*/blink.py @fronzbot
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/*/deconz.py @kane610 homeassistant/components/*/deconz.py @kane610
homeassistant/components/ecovacs.py @OverloadUT homeassistant/components/ecovacs.py @OverloadUT
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/*/eight_sleep.py @mezz64 homeassistant/components/*/eight_sleep.py @mezz64
homeassistant/components/hive.py @Rendili @KJonline homeassistant/components/hive.py @Rendili @KJonline
@ -106,7 +110,7 @@ homeassistant/components/konnected.py @heythisisnate
homeassistant/components/*/konnected.py @heythisisnate homeassistant/components/*/konnected.py @heythisisnate
homeassistant/components/matrix.py @tinloaf homeassistant/components/matrix.py @tinloaf
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/*/openuv.py @bachya
homeassistant/components/qwikswitch.py @kellerza homeassistant/components/qwikswitch.py @kellerza
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/*/tesla.py @zabuldon
homeassistant/components/tellduslive.py @molobrakos @fredrike homeassistant/components/tellduslive.py @molobrakos @fredrike
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/*/tradfri.py @ggravlingen
homeassistant/components/upcloud.py @scop homeassistant/components/upcloud.py @scop
homeassistant/components/*/upcloud.py @scop homeassistant/components/*/upcloud.py @scop

View File

@ -1,9 +1,9 @@
"""Storage for auth models.""" """Storage for auth models."""
from collections import OrderedDict from collections import OrderedDict
from datetime import timedelta from datetime import timedelta
import hmac
from logging import getLogger from logging import getLogger
from typing import Any, Dict, List, Optional # noqa: F401 from typing import Any, Dict, List, Optional # noqa: F401
import hmac
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
@ -28,7 +28,8 @@ class AuthStore:
"""Initialize the auth store.""" """Initialize the auth store."""
self.hass = hass self.hass = hass
self._users = None # type: Optional[Dict[str, models.User]] 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]: async def async_get_users(self) -> List[models.User]:
"""Retrieve all users.""" """Retrieve all users."""
@ -213,14 +214,24 @@ class AuthStore:
if self._users is not None: if self._users is not None:
return return
users = OrderedDict() # type: Dict[str, models.User]
if data is None: if data is None:
self._users = users self._set_defaults()
return 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']: 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']: for cred_dict in data['credentials']:
users[cred_dict['user_id']].credentials.append(models.Credentials( users[cred_dict['user_id']].credentials.append(models.Credentials(
@ -340,3 +351,7 @@ class AuthStore:
'credentials': credentials, 'credentials': credentials,
'refresh_tokens': refresh_tokens, 'refresh_tokens': refresh_tokens,
} }
def _set_defaults(self) -> None:
"""Set default values for auth store."""
self._users = OrderedDict() # type: Dict[str, models.User]

View File

@ -85,7 +85,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
super().__init__(hass, config) super().__init__(hass, config)
self._user_settings = None # type: Optional[_UsersDict] self._user_settings = None # type: Optional[_UsersDict]
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY) STORAGE_VERSION, STORAGE_KEY, private=True)
self._include = config.get(CONF_INCLUDE, []) self._include = config.get(CONF_INCLUDE, [])
self._exclude = config.get(CONF_EXCLUDE, []) self._exclude = config.get(CONF_EXCLUDE, [])
self._message_template = config[CONF_MESSAGE] self._message_template = config[CONF_MESSAGE]

View File

@ -67,7 +67,7 @@ class TotpAuthModule(MultiFactorAuthModule):
super().__init__(hass, config) super().__init__(hass, config)
self._users = None # type: Optional[Dict[str, str]] self._users = None # type: Optional[Dict[str, str]]
self._user_store = hass.helpers.storage.Store( self._user_store = hass.helpers.storage.Store(
STORAGE_VERSION, STORAGE_KEY) STORAGE_VERSION, STORAGE_KEY, private=True)
@property @property
def input_schema(self) -> vol.Schema: def input_schema(self) -> vol.Schema:

View File

@ -19,19 +19,19 @@ class User:
"""A user.""" """A user."""
name = attr.ib(type=str) # type: Optional[str] 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_owner = attr.ib(type=bool, default=False)
is_active = attr.ib(type=bool, default=False) is_active = attr.ib(type=bool, default=False)
system_generated = attr.ib(type=bool, default=False) system_generated = attr.ib(type=bool, default=False)
# List of credentials of a user. # List of credentials of a user.
credentials = attr.ib( credentials = attr.ib(
type=list, default=attr.Factory(list), cmp=False type=list, factory=list, cmp=False
) # type: List[Credentials] ) # type: List[Credentials]
# Tokens associated with a user. # Tokens associated with a user.
refresh_tokens = attr.ib( refresh_tokens = attr.ib(
type=dict, default=attr.Factory(dict), cmp=False type=dict, factory=dict, cmp=False
) # type: Dict[str, RefreshToken] ) # type: Dict[str, RefreshToken]
@ -48,12 +48,10 @@ class RefreshToken:
validator=attr.validators.in_(( validator=attr.validators.in_((
TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM, TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM,
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN))) TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)))
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex)) id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow)) created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
token = attr.ib(type=str, token = attr.ib(type=str, factory=lambda: generate_secret(64))
default=attr.Factory(lambda: generate_secret(64))) jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64))
jwt_key = attr.ib(type=str,
default=attr.Factory(lambda: generate_secret(64)))
last_used_at = attr.ib(type=Optional[datetime], default=None) last_used_at = attr.ib(type=Optional[datetime], default=None)
last_used_ip = attr.ib(type=Optional[str], default=None) last_used_ip = attr.ib(type=Optional[str], default=None)
@ -69,7 +67,7 @@ class Credentials:
# Allow the auth provider to store data to represent their auth. # Allow the auth provider to store data to represent their auth.
data = attr.ib(type=dict) 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) is_new = attr.ib(type=bool, default=True)

View File

@ -52,7 +52,8 @@ class Data:
def __init__(self, hass: HomeAssistant) -> None: def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the user data store.""" """Initialize the user data store."""
self.hass = hass 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]] self._data = None # type: Optional[Dict[str, Any]]
async def async_load(self) -> None: async def async_load(self) -> None:

View File

@ -59,61 +59,9 @@ def is_on(hass, entity_id=None):
return False return False
def turn_on(hass, entity_id=None, **service_data): async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
"""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]:
"""Set up general services related to Home Assistant.""" """Set up general services related to Home Assistant."""
@asyncio.coroutine async def async_handle_turn_service(service):
def async_handle_turn_service(service):
"""Handle calls to homeassistant.turn_on/off.""" """Handle calls to homeassistant.turn_on/off."""
entity_ids = extract_entity_ids(hass, service) 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( tasks.append(hass.services.async_call(
domain, service.service, data, blocking)) domain, service.service, data, blocking))
yield from asyncio.wait(tasks, loop=hass.loop) await asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) 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( hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}")) intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
@asyncio.coroutine async def async_handle_core_service(call):
def async_handle_core_service(call):
"""Service handler for handling core services.""" """Service handler for handling core services."""
if call.service == SERVICE_HOMEASSISTANT_STOP: if call.service == SERVICE_HOMEASSISTANT_STOP:
hass.async_create_task(hass.async_stop()) hass.async_create_task(hass.async_stop())
return return
try: try:
errors = yield from conf_util.async_check_ha_config_file(hass) errors = await conf_util.async_check_ha_config_file(hass)
except HomeAssistantError: except HomeAssistantError:
return return
@ -193,16 +140,15 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
hass.services.async_register( hass.services.async_register(
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
@asyncio.coroutine async def async_handle_reload_config(call):
def async_handle_reload_config(call):
"""Service handler for reloading core config.""" """Service handler for reloading core config."""
try: try:
conf = yield from conf_util.async_hass_config_yaml(hass) conf = await conf_util.async_hass_config_yaml(hass)
except HomeAssistantError as err: except HomeAssistantError as err:
_LOGGER.error(err) _LOGGER.error(err)
return 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, conf.get(ha.DOMAIN) or {})
hass.services.async_register( hass.services.async_register(

View File

@ -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 For more details about this component, please refer to the documentation at
https://home-assistant.io/components/abode/ https://home-assistant.io/components/abode/
""" """
import asyncio
import logging import logging
from functools import partial from functools import partial
from requests.exceptions import HTTPError, ConnectTimeout from requests.exceptions import HTTPError, ConnectTimeout
@ -261,8 +260,7 @@ class AbodeDevice(Entity):
self._data = data self._data = data
self._device = device self._device = device
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Subscribe Abode events.""" """Subscribe Abode events."""
self.hass.async_add_job( self.hass.async_add_job(
self._data.abode.events.add_device_callback, self._data.abode.events.add_device_callback,
@ -308,8 +306,7 @@ class AbodeAutomation(Entity):
self._automation = automation self._automation = automation
self._event = event self._event = event
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Subscribe Abode events.""" """Subscribe Abode events."""
if self._event: if self._event:
self.hass.async_add_job( self.hass.async_add_job(

View File

@ -4,7 +4,6 @@ Component to interface with an alarm control panel.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel/ https://home-assistant.io/components/alarm_control_panel/
""" """
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -14,7 +13,6 @@ from homeassistant.const import (
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER, ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY, SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS) SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
from homeassistant.loader import bind_hass
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
@ -32,85 +30,12 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
}) })
@bind_hass async def async_setup(hass, config):
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):
"""Track states and offer events for sensors.""" """Track states and offer events for sensors."""
component = hass.data[DOMAIN] = EntityComponent( component = hass.data[DOMAIN] = EntityComponent(
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
yield from component.async_setup(config) await component.async_setup(config)
component.async_register_entity_service( component.async_register_entity_service(
SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA, SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA,

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdecoder/ https://home-assistant.io/components/alarm_control_panel.alarmdecoder/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -59,8 +58,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
self._ready = None self._ready = None
self._zone_bypassed = None self._zone_bypassed = None
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect( self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_PANEL_MESSAGE, self._message_callback) SIGNAL_PANEL_MESSAGE, self._message_callback)

View File

@ -4,7 +4,6 @@ Interfaces with Alarm.com alarm control panels.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/ https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
""" """
import asyncio
import logging import logging
import re import re
@ -32,9 +31,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up a Alarm.com control panel.""" """Set up a Alarm.com control panel."""
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
code = config.get(CONF_CODE) code = config.get(CONF_CODE)
@ -42,7 +40,7 @@ def async_setup_platform(hass, config, async_add_entities,
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
alarmdotcom = AlarmDotCom(hass, name, code, username, password) alarmdotcom = AlarmDotCom(hass, name, code, username, password)
yield from alarmdotcom.async_login() await alarmdotcom.async_login()
async_add_entities([alarmdotcom]) async_add_entities([alarmdotcom])
@ -63,15 +61,13 @@ class AlarmDotCom(alarm.AlarmControlPanel):
self._alarm = Alarmdotcom( self._alarm = Alarmdotcom(
username, password, self._websession, hass.loop) username, password, self._websession, hass.loop)
@asyncio.coroutine async def async_login(self):
def async_login(self):
"""Login to Alarm.com.""" """Login to Alarm.com."""
yield from self._alarm.async_login() await self._alarm.async_login()
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Fetch the latest state.""" """Fetch the latest state."""
yield from self._alarm.async_update() await self._alarm.async_update()
return self._alarm.state return self._alarm.state
@property @property
@ -106,23 +102,20 @@ class AlarmDotCom(alarm.AlarmControlPanel):
'sensor_status': self._alarm.sensor_status 'sensor_status': self._alarm.sensor_status
} }
@asyncio.coroutine async def async_alarm_disarm(self, code=None):
def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
if self._validate_code(code): if self._validate_code(code):
yield from self._alarm.async_alarm_disarm() await self._alarm.async_alarm_disarm()
@asyncio.coroutine async def async_alarm_arm_home(self, code=None):
def async_alarm_arm_home(self, code=None):
"""Send arm hom command.""" """Send arm hom command."""
if self._validate_code(code): if self._validate_code(code):
yield from self._alarm.async_alarm_arm_home() await self._alarm.async_alarm_arm_home()
@asyncio.coroutine async def async_alarm_arm_away(self, code=None):
def async_alarm_arm_away(self, code=None):
"""Send arm away command.""" """Send arm away command."""
if self._validate_code(code): 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): def _validate_code(self, code):
"""Validate given code.""" """Validate given code."""

View File

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

View File

@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.egardia/ https://home-assistant.io/components/alarm_control_panel.egardia/
""" """
import asyncio
import logging import logging
import requests import requests
@ -61,8 +60,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
self._rs_codes = rs_codes self._rs_codes = rs_codes
self._rs_port = rs_port self._rs_port = rs_port
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Add Egardiaserver callback if enabled.""" """Add Egardiaserver callback if enabled."""
if self._rs_enabled: if self._rs_enabled:
_LOGGER.debug("Registering callback to Egardiaserver") _LOGGER.debug("Registering callback to Egardiaserver")

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.envisalink/ https://home-assistant.io/components/alarm_control_panel.envisalink/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -32,9 +31,8 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Perform the setup for Envisalink alarm panels.""" """Perform the setup for Envisalink alarm panels."""
configured_partitions = discovery_info['partitions'] configured_partitions = discovery_info['partitions']
code = discovery_info[CONF_CODE] code = discovery_info[CONF_CODE]
@ -88,8 +86,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
_LOGGER.debug("Setting up alarm: %s", alarm_name) _LOGGER.debug("Setting up alarm: %s", alarm_name)
super().__init__(alarm_name, info, controller) super().__init__(alarm_name, info, controller)
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
async_dispatcher_connect( async_dispatcher_connect(
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback) self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
@ -128,8 +125,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
state = STATE_ALARM_DISARMED state = STATE_ALARM_DISARMED
return state return state
@asyncio.coroutine async def async_alarm_disarm(self, code=None):
def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
if code: if code:
self.hass.data[DATA_EVL].disarm_partition( self.hass.data[DATA_EVL].disarm_partition(
@ -138,8 +134,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
self.hass.data[DATA_EVL].disarm_partition( self.hass.data[DATA_EVL].disarm_partition(
str(self._code), self._partition_number) str(self._code), self._partition_number)
@asyncio.coroutine async def async_alarm_arm_home(self, code=None):
def async_alarm_arm_home(self, code=None):
"""Send arm home command.""" """Send arm home command."""
if code: if code:
self.hass.data[DATA_EVL].arm_stay_partition( 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( self.hass.data[DATA_EVL].arm_stay_partition(
str(self._code), self._partition_number) str(self._code), self._partition_number)
@asyncio.coroutine async def async_alarm_arm_away(self, code=None):
def async_alarm_arm_away(self, code=None):
"""Send arm away command.""" """Send arm away command."""
if code: if code:
self.hass.data[DATA_EVL].arm_away_partition( 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( self.hass.data[DATA_EVL].arm_away_partition(
str(self._code), self._partition_number) str(self._code), self._partition_number)
@asyncio.coroutine async def async_alarm_trigger(self, code=None):
def async_alarm_trigger(self, code=None):
"""Alarm trigger command. Will be used to trigger a panic alarm.""" """Alarm trigger command. Will be used to trigger a panic alarm."""
self.hass.data[DATA_EVL].panic_alarm(self._panic_type) self.hass.data[DATA_EVL].panic_alarm(self._panic_type)

View File

@ -4,7 +4,6 @@ Support for manual alarms controllable via MQTT.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/ https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
""" """
import asyncio
import copy import copy
import datetime import datetime
import logging import logging
@ -363,8 +362,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
return mqtt.async_subscribe( return mqtt.async_subscribe(
self.hass, self._command_topic, message_received, self._qos) self.hass, self._command_topic, message_received, self._qos)
@asyncio.coroutine async def _async_state_changed_listener(self, entity_id, old_state,
def _async_state_changed_listener(self, entity_id, old_state, new_state): new_state):
"""Publish state change to MQTT.""" """Publish state change to MQTT."""
mqtt.async_publish( mqtt.async_publish(
self.hass, self._state_topic, new_state.state, self._qos, True) self.hass, self._state_topic, new_state.state, self._qos, True)

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.mqtt/ https://home-assistant.io/components/alarm_control_panel.mqtt/
""" """
import asyncio
import logging import logging
import re import re
@ -18,10 +17,13 @@ from homeassistant.const import (
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN, STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
CONF_NAME, CONF_CODE) CONF_NAME, CONF_CODE)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
CONF_RETAIN, MqttAvailability) CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
import homeassistant.helpers.config_validation as cv 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__) _LOGGER = logging.getLogger(__name__)
@ -46,13 +48,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
def async_setup_platform(hass, config, async_add_entities, async_add_entities, discovery_info=None):
discovery_info=None): """Set up MQTT alarm control panel through configuration.yaml."""
"""Set up the MQTT Alarm Control Panel platform.""" await _async_setup_entity(hass, config, async_add_entities)
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
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( async_add_entities([MqttAlarm(
config.get(CONF_NAME), config.get(CONF_NAME),
config.get(CONF_STATE_TOPIC), 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_CODE),
config.get(CONF_AVAILABILITY_TOPIC), config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_PAYLOAD_AVAILABLE), 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.""" """Representation of a MQTT alarm status."""
def __init__(self, name, state_topic, command_topic, qos, retain, def __init__(self, name, state_topic, command_topic, qos, retain,
payload_disarm, payload_arm_home, payload_arm_away, code, 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.""" """Init the MQTT Alarm Control Panel."""
super().__init__(availability_topic, qos, payload_available, MqttAvailability.__init__(self, availability_topic, qos,
payload_not_available) payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._state = STATE_UNKNOWN self._state = STATE_UNKNOWN
self._name = name self._name = name
self._state_topic = state_topic self._state_topic = state_topic
@ -87,11 +108,12 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self._payload_arm_home = payload_arm_home self._payload_arm_home = payload_arm_home
self._payload_arm_away = payload_arm_away self._payload_arm_away = payload_arm_away
self._code = code self._code = code
self._discovery_hash = discovery_hash
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Subscribe mqtt events.""" """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 @callback
def message_received(topic, payload, qos): def message_received(topic, payload, qos):
@ -104,7 +126,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
self._state = payload self._state = payload
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
yield from mqtt.async_subscribe( await mqtt.async_subscribe(
self.hass, self._state_topic, message_received, self._qos) self.hass, self._state_topic, message_received, self._qos)
@property @property
@ -131,8 +153,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
return 'Number' return 'Number'
return 'Any' return 'Any'
@asyncio.coroutine async def async_alarm_disarm(self, code=None):
def async_alarm_disarm(self, code=None):
"""Send disarm command. """Send disarm command.
This method is a coroutine. 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.hass, self._command_topic, self._payload_disarm, self._qos,
self._retain) self._retain)
@asyncio.coroutine async def async_alarm_arm_home(self, code=None):
def async_alarm_arm_home(self, code=None):
"""Send arm home command. """Send arm home command.
This method is a coroutine. 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.hass, self._command_topic, self._payload_arm_home, self._qos,
self._retain) self._retain)
@asyncio.coroutine async def async_alarm_arm_away(self, code=None):
def async_alarm_arm_away(self, code=None):
"""Send arm away command. """Send arm away command.
This method is a coroutine. This method is a coroutine.

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.satel_integra/ https://home-assistant.io/components/alarm_control_panel.satel_integra/
""" """
import asyncio
import logging import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
@ -18,9 +17,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['satel_integra'] DEPENDENCIES = ['satel_integra']
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up for Satel Integra alarm panels.""" """Set up for Satel Integra alarm panels."""
if not discovery_info: if not discovery_info:
return return
@ -39,8 +37,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
self._state = None self._state = None
self._arm_home_mode = arm_home_mode self._arm_home_mode = arm_home_mode
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
async_dispatcher_connect( async_dispatcher_connect(
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback) self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)
@ -74,21 +71,18 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
"""Return the state of the device.""" """Return the state of the device."""
return self._state return self._state
@asyncio.coroutine async def async_alarm_disarm(self, code=None):
def async_alarm_disarm(self, code=None):
"""Send disarm command.""" """Send disarm command."""
if code: if code:
yield from self.hass.data[DATA_SATEL].disarm(code) await self.hass.data[DATA_SATEL].disarm(code)
@asyncio.coroutine async def async_alarm_arm_away(self, code=None):
def async_alarm_arm_away(self, code=None):
"""Send arm away command.""" """Send arm away command."""
if code: if code:
yield from self.hass.data[DATA_SATEL].arm(code) await self.hass.data[DATA_SATEL].arm(code)
@asyncio.coroutine async def async_alarm_arm_home(self, code=None):
def async_alarm_arm_home(self, code=None):
"""Send arm home command.""" """Send arm home command."""
if code: if code:
yield from self.hass.data[DATA_SATEL].arm( await self.hass.data[DATA_SATEL].arm(
code, self._arm_home_mode) code, self._arm_home_mode)

View File

@ -12,75 +12,100 @@ import voluptuous as vol
from homeassistant.components.alarm_control_panel import ( from homeassistant.components.alarm_control_panel import (
PLATFORM_SCHEMA, AlarmControlPanel) PLATFORM_SCHEMA, AlarmControlPanel)
from homeassistant.const import ( from homeassistant.const import (
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
STATE_ALARM_DISARMED, STATE_UNKNOWN) from homeassistant.helpers import aiohttp_client, config_validation as cv
import homeassistant.helpers.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__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'SimpliSafe'
ATTR_ALARM_ACTIVE = "alarm_active" ATTR_ALARM_ACTIVE = "alarm_active"
ATTR_TEMPERATURE = "temperature" ATTR_TEMPERATURE = "temperature"
DATA_FILE = '.simplisafe'
DEFAULT_NAME = 'SimpliSafe'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_USERNAME): 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_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.""" """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) name = config.get(CONF_NAME)
code = config.get(CONF_CODE) 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: try:
simplisafe = SimpliSafeApiInterface(username, password) if config_data:
except SimpliSafeAPIException: try:
_LOGGER.error("Failed to set up SimpliSafe") 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 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 = await simplisafe.get_systems()
systems.append(SimpliSafeAlarm(system, name, code)) async_add_entities(
[SimpliSafeAlarm(system, name, code) for system in systems], True)
add_entities(systems)
class SimpliSafeAlarm(AlarmControlPanel): class SimpliSafeAlarm(AlarmControlPanel):
"""Representation of a SimpliSafe alarm.""" """Representation of a SimpliSafe alarm."""
def __init__(self, simplisafe, name, code): def __init__(self, system, name, code):
"""Initialize the SimpliSafe alarm.""" """Initialize the SimpliSafe alarm."""
self.simplisafe = simplisafe self._attrs = {}
self._name = name
self._code = str(code) if code else None self._code = str(code) if code else None
self._name = name
self._system = system
self._state = None
@property @property
def unique_id(self): def unique_id(self):
"""Return the unique ID.""" """Return the unique ID."""
return self.simplisafe.location_id return self._system.system_id
@property @property
def name(self): def name(self):
"""Return the name of the device.""" """Return the name of the device."""
if self._name is not None: if self._name:
return self._name return self._name
return 'Alarm {}'.format(self.simplisafe.location_id) return 'Alarm {}'.format(self._system.system_id)
@property @property
def code_format(self): def code_format(self):
"""Return one or more digits/characters.""" """Return one or more digits/characters."""
if self._code is None: if not self._code:
return None return None
if isinstance(self._code, str) and re.search('^\\d+$', self._code): if isinstance(self._code, str) and re.search('^\\d+$', self._code):
return 'Number' return 'Number'
@ -89,53 +114,12 @@ class SimpliSafeAlarm(AlarmControlPanel):
@property @property
def state(self): def state(self):
"""Return the state of the device.""" """Return the state of the device."""
status = self.simplisafe.state return self._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
@property @property
def device_state_attributes(self): def device_state_attributes(self):
"""Return the state attributes.""" """Return the state attributes."""
attributes = {} return self._attrs
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")
def _validate_code(self, code, state): def _validate_code(self, code, state):
"""Validate given code.""" """Validate given code."""
@ -143,3 +127,46 @@ class SimpliSafeAlarm(AlarmControlPanel):
if not check: if not check:
_LOGGER.warning("Wrong code entered for %s", state) _LOGGER.warning("Wrong code entered for %s", state)
return check 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

View File

@ -9,8 +9,7 @@ import logging
import homeassistant.components.alarm_control_panel as alarm import homeassistant.components.alarm_control_panel as alarm
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.spc import ( from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_ALARM)
ATTR_DISCOVER_AREAS, DATA_API, SIGNAL_UPDATE_ALARM)
from homeassistant.const import ( from homeassistant.const import (
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED) 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, async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None): discovery_info=None):
"""Set up the SPC alarm control panel platform.""" """Set up the SPC alarm control panel platform."""
if (discovery_info is None or if discovery_info is None:
discovery_info[ATTR_DISCOVER_AREAS] is None):
return return
api = hass.data[DATA_API]
async_add_entities([SpcAlarm(area=area, api=hass.data[DATA_API]) async_add_entities([SpcAlarm(area=area, api=api)
for area in discovery_info[ATTR_DISCOVER_AREAS]]) for area in api.areas.values()])
class SpcAlarm(alarm.AlarmControlPanel): class SpcAlarm(alarm.AlarmControlPanel):

View File

@ -4,7 +4,6 @@ Interfaces with Wink Cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/alarm_control_panel.wink/ https://home-assistant.io/components/alarm_control_panel.wink/
""" """
import asyncio
import logging import logging
import homeassistant.components.alarm_control_panel as alarm 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): class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
"""Representation a Wink camera alarm.""" """Representation a Wink camera alarm."""
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Call when entity is added to hass.""" """Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self) self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)

View File

@ -10,7 +10,8 @@ import logging
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.components.notify import (
ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY)
from homeassistant.const import ( from homeassistant.const import (
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF, CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID) SERVICE_TURN_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) return hass.states.is_state(entity_id, STATE_ON)
def turn_on(hass, entity_id): async def async_setup(hass, config):
"""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):
"""Set up the Alert component.""" """Set up the Alert component."""
alerts = config.get(DOMAIN) alerts = config.get(DOMAIN)
all_alerts = {} all_alerts = {}
@asyncio.coroutine async def async_handle_alert_service(service_call):
def async_handle_alert_service(service_call):
"""Handle calls to alert services.""" """Handle calls to alert services."""
alert_ids = service.extract_entity_ids(hass, service_call) alert_ids = service.extract_entity_ids(hass, service_call)
@ -113,11 +73,11 @@ def async_setup(hass, config):
alert = all_alerts[alert_id] alert = all_alerts[alert_id]
alert.async_set_context(service_call.context) alert.async_set_context(service_call.context)
if service_call.service == SERVICE_TURN_ON: if service_call.service == SERVICE_TURN_ON:
yield from alert.async_turn_on() await alert.async_turn_on()
elif service_call.service == SERVICE_TOGGLE: elif service_call.service == SERVICE_TOGGLE:
yield from alert.async_toggle() await alert.async_toggle()
else: else:
yield from alert.async_turn_off() await alert.async_turn_off()
# Setup alerts # Setup alerts
for entity_id, alert in alerts.items(): 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()] tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
if tasks: if tasks:
yield from asyncio.wait(tasks, loop=hass.loop) await asyncio.wait(tasks, loop=hass.loop)
return True return True
@ -196,17 +156,15 @@ class Alert(ToggleEntity):
"""Hide the alert when it is not firing.""" """Hide the alert when it is not firing."""
return not self._can_ack or not self._firing return not self._can_ack or not self._firing
@asyncio.coroutine async def watched_entity_change(self, entity, from_state, to_state):
def watched_entity_change(self, entity, from_state, to_state):
"""Determine if the alert should start or stop.""" """Determine if the alert should start or stop."""
_LOGGER.debug("Watched entity (%s) has changed", entity) _LOGGER.debug("Watched entity (%s) has changed", entity)
if to_state.state == self._alert_state and not self._firing: 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: if to_state.state != self._alert_state and self._firing:
yield from self.end_alerting() await self.end_alerting()
@asyncio.coroutine async def begin_alerting(self):
def begin_alerting(self):
"""Begin the alert procedures.""" """Begin the alert procedures."""
_LOGGER.debug("Beginning Alert: %s", self._name) _LOGGER.debug("Beginning Alert: %s", self._name)
self._ack = False self._ack = False
@ -214,25 +172,23 @@ class Alert(ToggleEntity):
self._next_delay = 0 self._next_delay = 0
if not self._skip_first: if not self._skip_first:
yield from self._notify() await self._notify()
else: else:
yield from self._schedule_notify() await self._schedule_notify()
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@asyncio.coroutine async def end_alerting(self):
def end_alerting(self):
"""End the alert procedures.""" """End the alert procedures."""
_LOGGER.debug("Ending Alert: %s", self._name) _LOGGER.debug("Ending Alert: %s", self._name)
self._cancel() self._cancel()
self._ack = False self._ack = False
self._firing = False self._firing = False
if self._done_message and self._send_done_message: 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() self.async_schedule_update_ha_state()
@asyncio.coroutine async def _schedule_notify(self):
def _schedule_notify(self):
"""Schedule a notification.""" """Schedule a notification."""
delay = self._delay[self._next_delay] delay = self._delay[self._next_delay]
next_msg = datetime.now() + 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) event.async_track_point_in_time(self.hass, self._notify, next_msg)
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1) self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
@asyncio.coroutine async def _notify(self, *args):
def _notify(self, *args):
"""Send the alert notification.""" """Send the alert notification."""
if not self._firing: if not self._firing:
return return
@ -250,36 +205,32 @@ class Alert(ToggleEntity):
_LOGGER.info("Alerting: %s", self._name) _LOGGER.info("Alerting: %s", self._name)
self._send_done_message = True self._send_done_message = True
for target in self._notifiers: for target in self._notifiers:
yield from self.hass.services.async_call( await self.hass.services.async_call(
'notify', target, {'message': self._name}) DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name})
yield from self._schedule_notify() await self._schedule_notify()
@asyncio.coroutine async def _notify_done_message(self, *args):
def _notify_done_message(self, *args):
"""Send notification of complete alert.""" """Send notification of complete alert."""
_LOGGER.info("Alerting: %s", self._done_message) _LOGGER.info("Alerting: %s", self._done_message)
self._send_done_message = False self._send_done_message = False
for target in self._notifiers: for target in self._notifiers:
yield from self.hass.services.async_call( await self.hass.services.async_call(
'notify', target, {'message': self._done_message}) DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message})
@asyncio.coroutine async def async_turn_on(self, **kwargs):
def async_turn_on(self, **kwargs):
"""Async Unacknowledge alert.""" """Async Unacknowledge alert."""
_LOGGER.debug("Reset Alert: %s", self._name) _LOGGER.debug("Reset Alert: %s", self._name)
self._ack = False self._ack = False
yield from self.async_update_ha_state() await self.async_update_ha_state()
@asyncio.coroutine async def async_turn_off(self, **kwargs):
def async_turn_off(self, **kwargs):
"""Async Acknowledge alert.""" """Async Acknowledge alert."""
_LOGGER.debug("Acknowledged Alert: %s", self._name) _LOGGER.debug("Acknowledged Alert: %s", self._name)
self._ack = True self._ack = True
yield from self.async_update_ha_state() await self.async_update_ha_state()
@asyncio.coroutine async def async_toggle(self, **kwargs):
def async_toggle(self, **kwargs):
"""Async toggle alert.""" """Async toggle alert."""
if self._ack: if self._ack:
return self.async_turn_on() return await self.async_turn_on()
return self.async_turn_off() return await self.async_turn_off()

View File

@ -4,7 +4,6 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/ https://home-assistant.io/components/alexa/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -53,8 +52,7 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Activate Alexa component.""" """Activate Alexa component."""
config = config.get(DOMAIN, {}) config = config.get(DOMAIN, {})
flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS) flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS)

View File

@ -4,7 +4,6 @@ Support for Alexa skill service end point.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/alexa/ https://home-assistant.io/components/alexa/
""" """
import asyncio
import enum import enum
import logging import logging
@ -59,16 +58,15 @@ class AlexaIntentsView(http.HomeAssistantView):
url = INTENTS_API_ENDPOINT url = INTENTS_API_ENDPOINT
name = 'api:alexa' name = 'api:alexa'
@asyncio.coroutine async def post(self, request):
def post(self, request):
"""Handle Alexa.""" """Handle Alexa."""
hass = request.app['hass'] hass = request.app['hass']
message = yield from request.json() message = await request.json()
_LOGGER.debug("Received Alexa request: %s", message) _LOGGER.debug("Received Alexa request: %s", message)
try: 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) return b'' if response is None else self.json(response)
except UnknownRequest as err: except UnknownRequest as err:
_LOGGER.warning(str(err)) _LOGGER.warning(str(err))
@ -101,8 +99,7 @@ def intent_error_response(hass, message, error):
return alexa_response.as_dict() return alexa_response.as_dict()
@asyncio.coroutine async def async_handle_message(hass, message):
def async_handle_message(hass, message):
"""Handle an Alexa intent. """Handle an Alexa intent.
Raises: Raises:
@ -120,20 +117,18 @@ def async_handle_message(hass, message):
if not handler: if not handler:
raise UnknownRequest('Received unknown request {}'.format(req_type)) raise UnknownRequest('Received unknown request {}'.format(req_type))
return (yield from handler(hass, message)) return await handler(hass, message)
@HANDLERS.register('SessionEndedRequest') @HANDLERS.register('SessionEndedRequest')
@asyncio.coroutine async def async_handle_session_end(hass, message):
def async_handle_session_end(hass, message):
"""Handle a session end request.""" """Handle a session end request."""
return None return None
@HANDLERS.register('IntentRequest') @HANDLERS.register('IntentRequest')
@HANDLERS.register('LaunchRequest') @HANDLERS.register('LaunchRequest')
@asyncio.coroutine async def async_handle_intent(hass, message):
def async_handle_intent(hass, message):
"""Handle an intent request. """Handle an intent request.
Raises: Raises:
@ -153,7 +148,7 @@ def async_handle_intent(hass, message):
else: else:
intent_name = alexa_intent_info['name'] intent_name = alexa_intent_info['name']
intent_response = yield from intent.async_handle( intent_response = await intent.async_handle(
hass, DOMAIN, intent_name, hass, DOMAIN, intent_name,
{key: {'value': value} for key, value {key: {'value': value} for key, value
in alexa_response.variables.items()}) in alexa_response.variables.items()})

View File

@ -1,5 +1,4 @@
"""Support for alexa Smart Home Skill API.""" """Support for alexa Smart Home Skill API."""
import asyncio
import logging import logging
import math import math
from datetime import datetime from datetime import datetime
@ -695,8 +694,7 @@ class SmartHomeView(http.HomeAssistantView):
"""Initialize.""" """Initialize."""
self.smart_home_config = smart_home_config self.smart_home_config = smart_home_config
@asyncio.coroutine async def post(self, request):
def post(self, request):
"""Handle Alexa Smart Home requests. """Handle Alexa Smart Home requests.
The Smart Home API requires the endpoint to be implemented in AWS The Smart Home API requires the endpoint to be implemented in AWS
@ -704,11 +702,11 @@ class SmartHomeView(http.HomeAssistantView):
the response. the response.
""" """
hass = request.app['hass'] hass = request.app['hass']
message = yield from request.json() message = await request.json()
_LOGGER.debug("Received Alexa Smart Home request: %s", message) _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) hass, self.smart_home_config, message)
_LOGGER.debug("Sending Alexa Smart Home response: %s", response) _LOGGER.debug("Sending Alexa Smart Home response: %s", response)
return b'' if response is None else self.json(response) return b'' if response is None else self.json(response)

View File

@ -149,16 +149,14 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Set up the IP Webcam component.""" """Set up the IP Webcam component."""
from pydroid_ipcam import PyDroidIPCam from pydroid_ipcam import PyDroidIPCam
webcams = hass.data[DATA_IP_WEBCAM] = {} webcams = hass.data[DATA_IP_WEBCAM] = {}
websession = async_get_clientsession(hass) websession = async_get_clientsession(hass)
@asyncio.coroutine async def async_setup_ipcamera(cam_config):
def async_setup_ipcamera(cam_config):
"""Set up an IP camera.""" """Set up an IP camera."""
host = cam_config[CONF_HOST] host = cam_config[CONF_HOST]
username = cam_config.get(CONF_USERNAME) username = cam_config.get(CONF_USERNAME)
@ -188,16 +186,15 @@ def async_setup(hass, config):
if motion is None: if motion is None:
motion = 'motion_active' in cam.enabled_sensors motion = 'motion_active' in cam.enabled_sensors
@asyncio.coroutine async def async_update_data(now):
def async_update_data(now):
"""Update data from IP camera in SCAN_INTERVAL.""" """Update data from IP camera in SCAN_INTERVAL."""
yield from cam.update() await cam.update()
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host) async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
async_track_point_in_utc_time( async_track_point_in_utc_time(
hass, async_update_data, utcnow() + interval) hass, async_update_data, utcnow() + interval)
yield from async_update_data(None) await async_update_data(None)
# Load platforms # Load platforms
webcams[host] = cam webcams[host] = cam
@ -242,7 +239,7 @@ def async_setup(hass, config):
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]] tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
if tasks: if tasks:
yield from asyncio.wait(tasks, loop=hass.loop) await asyncio.wait(tasks, loop=hass.loop)
return True return True
@ -255,8 +252,7 @@ class AndroidIPCamEntity(Entity):
self._host = host self._host = host
self._ipcam = ipcam self._ipcam = ipcam
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register update dispatcher.""" """Register update dispatcher."""
@callback @callback
def async_ipcam_update(host): def async_ipcam_update(host):

View File

@ -77,14 +77,13 @@ def request_configuration(hass, config, atv, credentials):
"""Request configuration steps from the user.""" """Request configuration steps from the user."""
configurator = hass.components.configurator configurator = hass.components.configurator
@asyncio.coroutine async def configuration_callback(callback_data):
def configuration_callback(callback_data):
"""Handle the submitted configuration.""" """Handle the submitted configuration."""
from pyatv import exceptions from pyatv import exceptions
pin = callback_data.get('pin') pin = callback_data.get('pin')
try: try:
yield from atv.airplay.finish_authentication(pin) await atv.airplay.finish_authentication(pin)
hass.components.persistent_notification.async_create( hass.components.persistent_notification.async_create(
'Authentication succeeded!<br /><br />Add the following ' 'Authentication succeeded!<br /><br />Add the following '
'to credentials: in your apple_tv configuration:<br /><br />' 'to credentials: in your apple_tv configuration:<br /><br />'
@ -108,11 +107,10 @@ def request_configuration(hass, config, atv, credentials):
) )
@asyncio.coroutine async def scan_for_apple_tvs(hass):
def scan_for_apple_tvs(hass):
"""Scan for devices and present a notification of the ones found.""" """Scan for devices and present a notification of the ones found."""
import pyatv 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 = [] devices = []
for atv in atvs: for atv in atvs:
@ -132,14 +130,12 @@ def scan_for_apple_tvs(hass):
notification_id=NOTIFICATION_SCAN_ID) notification_id=NOTIFICATION_SCAN_ID)
@asyncio.coroutine async def async_setup(hass, config):
def async_setup(hass, config):
"""Set up the Apple TV component.""" """Set up the Apple TV component."""
if DATA_APPLE_TV not in hass.data: if DATA_APPLE_TV not in hass.data:
hass.data[DATA_APPLE_TV] = {} hass.data[DATA_APPLE_TV] = {}
@asyncio.coroutine async def async_service_handler(service):
def async_service_handler(service):
"""Handle service calls.""" """Handle service calls."""
entity_ids = service.data.get(ATTR_ENTITY_ID) entity_ids = service.data.get(ATTR_ENTITY_ID)
@ -158,17 +154,16 @@ def async_setup(hass, config):
continue continue
atv = device.atv atv = device.atv
credentials = yield from atv.airplay.generate_credentials() credentials = await atv.airplay.generate_credentials()
yield from atv.airplay.load_credentials(credentials) await atv.airplay.load_credentials(credentials)
_LOGGER.debug('Generated new credentials: %s', 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.async_add_job(request_configuration,
hass, config, atv, credentials) hass, config, atv, credentials)
@asyncio.coroutine async def atv_discovered(service, info):
def atv_discovered(service, info):
"""Set up an Apple TV that was auto discovered.""" """Set up an Apple TV that was auto discovered."""
yield from _setup_atv(hass, { await _setup_atv(hass, {
CONF_NAME: info['name'], CONF_NAME: info['name'],
CONF_HOST: info['host'], CONF_HOST: info['host'],
CONF_LOGIN_ID: info['properties']['hG'], 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, [])] tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
if tasks: if tasks:
yield from asyncio.wait(tasks, loop=hass.loop) await asyncio.wait(tasks, loop=hass.loop)
hass.services.async_register( hass.services.async_register(
DOMAIN, SERVICE_SCAN, async_service_handler, DOMAIN, SERVICE_SCAN, async_service_handler,
@ -192,8 +187,7 @@ def async_setup(hass, config):
return True return True
@asyncio.coroutine async def _setup_atv(hass, atv_config):
def _setup_atv(hass, atv_config):
"""Set up an Apple TV.""" """Set up an Apple TV."""
import pyatv import pyatv
name = atv_config.get(CONF_NAME) name = atv_config.get(CONF_NAME)
@ -209,7 +203,7 @@ def _setup_atv(hass, atv_config):
session = async_get_clientsession(hass) session = async_get_clientsession(hass)
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session) atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
if credentials: if credentials:
yield from atv.airplay.load_credentials(credentials) await atv.airplay.load_credentials(credentials)
power = AppleTVPowerManager(hass, atv, start_off) power = AppleTVPowerManager(hass, atv, start_off)
hass.data[DATA_APPLE_TV][host] = { hass.data[DATA_APPLE_TV][host] = {
@ -258,4 +252,4 @@ class AppleTVPowerManager:
self.atv.push_updater.start() self.atv.push_updater.start()
for listener in self.listeners: 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())

View File

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

View File

@ -16,7 +16,8 @@
"description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:", "description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:",
"title": "\u00dcberpr\u00fcfe das Setup" "title": "\u00dcberpr\u00fcfe das Setup"
} }
} },
"title": "Benachrichtig f\u00fcr One-Time Password"
}, },
"totp": { "totp": {
"error": { "error": {

View File

@ -1,11 +1,23 @@
{ {
"mfa_setup": { "mfa_setup": {
"notify": { "notify": {
"abort": {
"no_available_service": "Aucun service de notification disponible."
},
"error": {
"invalid_code": "Code invalide. Veuillez essayer \u00e0 nouveau."
},
"step": { "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": { "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": { "totp": {
"error": { "error": {

View File

@ -1,5 +1,21 @@
{ {
"mfa_setup": { "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": { "totp": {
"error": { "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." "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."

View File

@ -26,7 +26,7 @@
"step": { "step": {
"init": { "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.", "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)" "title": "TOTP (\uc2dc\uac04 \uae30\ubc18 OTP)"

View File

@ -1,5 +1,24 @@
{ {
"mfa_setup": { "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": { "totp": {
"error": { "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." "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."

View File

@ -13,10 +13,11 @@
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144" "title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
}, },
"setup": { "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": "Sprawd\u017a konfiguracj\u0119"
} }
} },
"title": "Powiadomienie z has\u0142em jednorazowym"
}, },
"totp": { "totp": {
"error": { "error": {

View File

@ -1,5 +1,24 @@
{ {
"mfa_setup": { "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": { "totp": {
"error": { "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." "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."

View File

@ -8,11 +8,16 @@
"invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen." "invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen."
}, },
"step": { "step": {
"init": {
"description": "Var god v\u00e4lj en av notifieringstj\u00e4nsterna:",
"title": "Konfigurera ett eng\u00e5ngsl\u00f6senord levererat genom notifieringskomponenten"
},
"setup": { "setup": {
"description": "Ett eng\u00e5ngsl\u00f6senord har skickats av **notify.{notify_service}**. V\u00e4nligen ange det nedan:", "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": { "totp": {
"error": { "error": {

View File

@ -25,7 +25,7 @@
}, },
"step": { "step": {
"init": { "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" "title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u5169\u6b65\u9a5f\u9a57\u8b49"
} }
}, },

View File

@ -105,7 +105,6 @@ Home Assistant. User need to record the token in secure place.
"id": 11, "id": 11,
"type": "auth/long_lived_access_token", "type": "auth/long_lived_access_token",
"client_name": "GPS Logger", "client_name": "GPS Logger",
"client_icon": null,
"lifespan": 365 "lifespan": 365
} }
@ -433,7 +432,7 @@ def websocket_current_user(
"""Get current user.""" """Get current user."""
enabled_modules = await hass.auth.async_get_enabled_mfa(user) enabled_modules = await hass.auth.async_get_enabled_mfa(user)
connection.send_message_outside( connection.send_message(
websocket_api.result_message(msg['id'], { websocket_api.result_message(msg['id'], {
'id': user.id, 'id': user.id,
'name': user.name, 'name': user.name,
@ -468,7 +467,7 @@ def websocket_create_long_lived_access_token(
access_token = hass.auth.async_create_access_token( access_token = hass.auth.async_create_access_token(
refresh_token) refresh_token)
connection.send_message_outside( connection.send_message(
websocket_api.result_message(msg['id'], access_token)) websocket_api.result_message(msg['id'], access_token))
hass.async_create_task( hass.async_create_task(
@ -480,8 +479,8 @@ def websocket_create_long_lived_access_token(
def websocket_refresh_tokens( def websocket_refresh_tokens(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
"""Return metadata of users refresh tokens.""" """Return metadata of users refresh tokens."""
current_id = connection.request.get('refresh_token_id') current_id = connection.refresh_token_id
connection.to_write.put_nowait(websocket_api.result_message(msg['id'], [{ connection.send_message(websocket_api.result_message(msg['id'], [{
'id': refresh.id, 'id': refresh.id,
'client_id': refresh.client_id, 'client_id': refresh.client_id,
'client_name': refresh.client_name, 'client_name': refresh.client_name,
@ -509,7 +508,7 @@ def websocket_delete_refresh_token(
await hass.auth.async_remove_refresh_token(refresh_token) await hass.auth.async_remove_refresh_token(refresh_token)
connection.send_message_outside( connection.send_message(
websocket_api.result_message(msg['id'], {})) websocket_api.result_message(msg['id'], {}))
hass.async_create_task( hass.async_create_task(

View File

@ -1,24 +1,13 @@
"""Helpers to resolve client ID/secret.""" """Helpers to resolve client ID/secret."""
import asyncio import asyncio
from ipaddress import ip_address
from html.parser import HTMLParser from html.parser import HTMLParser
from ipaddress import ip_address, ip_network
from urllib.parse import urlparse, urljoin from urllib.parse import urlparse, urljoin
import aiohttp import aiohttp
from aiohttp.client_exceptions import ClientError from aiohttp.client_exceptions import ClientError
# IP addresses of loopback interfaces from homeassistant.util.network import is_local
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'),
)
async def verify_redirect_uri(hass, client_id, redirect_uri): async def verify_redirect_uri(hass, client_id, redirect_uri):
@ -185,9 +174,7 @@ def _parse_client_id(client_id):
# Not an ip address # Not an ip address
pass pass
if (address is None or if address is None or is_local(address):
address in ALLOWED_IPS or
any(address in network for network in ALLOWED_NETWORKS)):
return parts return parts
raise ValueError('Hostname should be a domain name or local IP address') raise ValueError('Hostname should be a domain name or local IP address')

View File

@ -64,7 +64,7 @@ def websocket_setup_mfa(
if flow_id is not None: if flow_id is not None:
result = await flow_manager.async_configure( result = await flow_manager.async_configure(
flow_id, msg.get('user_input')) flow_id, msg.get('user_input'))
connection.send_message_outside( connection.send_message(
websocket_api.result_message( websocket_api.result_message(
msg['id'], _prepare_result_json(result))) msg['id'], _prepare_result_json(result)))
return return
@ -72,7 +72,7 @@ def websocket_setup_mfa(
mfa_module_id = msg.get('mfa_module_id') mfa_module_id = msg.get('mfa_module_id')
mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id) mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id)
if mfa_module is None: if mfa_module is None:
connection.send_message_outside(websocket_api.error_message( connection.send_message(websocket_api.error_message(
msg['id'], 'no_module', msg['id'], 'no_module',
'MFA module {} is not found'.format(mfa_module_id))) 'MFA module {} is not found'.format(mfa_module_id)))
return return
@ -80,7 +80,7 @@ def websocket_setup_mfa(
result = await flow_manager.async_init( result = await flow_manager.async_init(
mfa_module_id, data={'user_id': connection.user.id}) mfa_module_id, data={'user_id': connection.user.id})
connection.send_message_outside( connection.send_message(
websocket_api.result_message( websocket_api.result_message(
msg['id'], _prepare_result_json(result))) msg['id'], _prepare_result_json(result)))
@ -99,13 +99,13 @@ def websocket_depose_mfa(
await hass.auth.async_disable_user_mfa( await hass.auth.async_disable_user_mfa(
connection.user, msg['mfa_module_id']) connection.user, msg['mfa_module_id'])
except ValueError as err: except ValueError as err:
connection.send_message_outside(websocket_api.error_message( connection.send_message(websocket_api.error_message(
msg['id'], 'disable_failed', msg['id'], 'disable_failed',
'Cannot disable MFA Module {}: {}'.format( 'Cannot disable MFA Module {}: {}'.format(
mfa_module_id, err))) mfa_module_id, err)))
return return
connection.send_message_outside( connection.send_message(
websocket_api.result_message( websocket_api.result_message(
msg['id'], 'done')) msg['id'], 'done'))

View File

@ -17,15 +17,15 @@
"step": { "step": {
"init": { "init": {
"title": "Set up one-time password delivered by notify component", "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": { "setup": {
"title": "Verify 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": { "abort": {
"no_available_service": "No available notify services." "no_available_service": "No notification services available."
}, },
"error": { "error": {
"invalid_code": "Invalid code, please try again." "invalid_code": "Invalid code, please try again."

View File

@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID) SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
from homeassistant.components import logbook
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import extract_domain_configs, script, condition from homeassistant.helpers import extract_domain_configs, script, condition
from homeassistant.helpers.entity import ToggleEntity 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) 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): async def async_setup(hass, config):
"""Set up the automation.""" """Set up the automation."""
component = EntityComponent(_LOGGER, DOMAIN, hass, component = EntityComponent(_LOGGER, DOMAIN, hass,
@ -412,8 +368,8 @@ def _async_get_action(hass, config, name):
async def action(entity_id, variables, context): async def action(entity_id, variables, context):
"""Execute an action.""" """Execute an action."""
_LOGGER.info('Executing %s', name) _LOGGER.info('Executing %s', name)
logbook.async_log_entry( hass.components.logbook.async_log_entry(
hass, name, 'has been triggered', DOMAIN, entity_id) name, 'has been triggered', DOMAIN, entity_id)
await script_obj.async_run(variables, context) await script_obj.async_run(variables, context)
return action return action

View File

@ -4,7 +4,6 @@ Offer event listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#event-trigger at https://home-assistant.io/docs/automation/trigger/#event-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
event_type = config.get(CONF_EVENT_TYPE) event_type = config.get(CONF_EVENT_TYPE)
event_data_schema = vol.Schema( event_data_schema = vol.Schema(

View File

@ -4,7 +4,6 @@ Offer Home Assistant core automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#homeassistant-trigger at https://home-assistant.io/components/automation/#homeassistant-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -23,8 +22,7 @@ TRIGGER_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
event = config.get(CONF_EVENT) event = config.get(CONF_EVENT)

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/automation.litejet/ https://home-assistant.io/components/automation.litejet/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -33,8 +32,7 @@ TRIGGER_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
number = config.get(CONF_NUMBER) number = config.get(CONF_NUMBER)
held_more_than = config.get(CONF_HELD_MORE_THAN) held_more_than = config.get(CONF_HELD_MORE_THAN)

View File

@ -4,7 +4,6 @@ Offer MQTT listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
""" """
import asyncio
import json import json
import voluptuous as vol import voluptuous as vol
@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
topic = config.get(CONF_TOPIC) topic = config.get(CONF_TOPIC)
payload = config.get(CONF_PAYLOAD) payload = config.get(CONF_PAYLOAD)
@ -51,6 +49,6 @@ def async_trigger(hass, config, action):
'trigger': data 'trigger': data
}) })
remove = yield from mqtt.async_subscribe( remove = await mqtt.async_subscribe(
hass, topic, mqtt_automation_listener) hass, topic, mqtt_automation_listener)
return remove return remove

View File

@ -4,7 +4,6 @@ Offer numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
below = config.get(CONF_BELOW) below = config.get(CONF_BELOW)

View File

@ -4,7 +4,6 @@ Offer state listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#state-trigger at https://home-assistant.io/docs/automation/trigger/#state-trigger
""" """
import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
}), cv.key_dependency(CONF_FOR, CONF_TO)) }), cv.key_dependency(CONF_FOR, CONF_TO))
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
from_state = config.get(CONF_FROM, MATCH_ALL) from_state = config.get(CONF_FROM, MATCH_ALL)

View File

@ -4,7 +4,6 @@ Offer sun based automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#sun-trigger at https://home-assistant.io/docs/automation/trigger/#sun-trigger
""" """
import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for events based on configuration.""" """Listen for events based on configuration."""
event = config.get(CONF_EVENT) event = config.get(CONF_EVENT)
offset = config.get(CONF_OFFSET) offset = config.get(CONF_OFFSET)

View File

@ -4,7 +4,6 @@ Offer template automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#template-trigger at https://home-assistant.io/docs/automation/trigger/#template-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -23,8 +22,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
value_template = config.get(CONF_VALUE_TEMPLATE) value_template = config.get(CONF_VALUE_TEMPLATE)
value_template.hass = hass value_template.hass = hass

View File

@ -4,7 +4,6 @@ Offer time listening automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#time-trigger at https://home-assistant.io/docs/automation/trigger/#time-trigger
""" """
import asyncio
import logging import logging
import voluptuous as vol 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)) }), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
if CONF_AT in config: if CONF_AT in config:
at_time = config.get(CONF_AT) at_time = config.get(CONF_AT)

View File

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

View File

@ -4,7 +4,6 @@ Offer zone automation rules.
For more details about this automation rule, please refer to the documentation For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/docs/automation/trigger/#zone-trigger at https://home-assistant.io/docs/automation/trigger/#zone-trigger
""" """
import asyncio
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.Schema({
}) })
@asyncio.coroutine async def async_trigger(hass, config, action):
def async_trigger(hass, config, action):
"""Listen for state changes based on configuration.""" """Listen for state changes based on configuration."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
zone_entity_id = config.get(CONF_ZONE) zone_entity_id = config.get(CONF_ZONE)

View File

@ -4,7 +4,6 @@ Support for ADS binary sensors.
For more details about this platform, please refer to the documentation. For more details about this platform, please refer to the documentation.
https://home-assistant.io/components/binary_sensor.ads/ https://home-assistant.io/components/binary_sensor.ads/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -50,8 +49,7 @@ class AdsBinarySensor(BinarySensorDevice):
self._ads_hub = ads_hub self._ads_hub = ads_hub
self.ads_var = ads_var self.ads_var = ads_var
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register device notification.""" """Register device notification."""
def update(name, value): def update(name, value):
"""Handle device notifications.""" """Handle device notifications."""

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.alarmdecoder/ https://home-assistant.io/components/binary_sensor.alarmdecoder/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -64,8 +63,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
self._relay_addr = relay_addr self._relay_addr = relay_addr
self._relay_chan = relay_chan self._relay_chan = relay_chan
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect( self.hass.helpers.dispatcher.async_dispatcher_connect(
SIGNAL_ZONE_FAULT, self._fault_callback) SIGNAL_ZONE_FAULT, self._fault_callback)

View File

@ -4,8 +4,6 @@ Support for IP Webcam binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.android_ip_webcam/ https://home-assistant.io/components/binary_sensor.android_ip_webcam/
""" """
import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.android_ip_webcam import ( from homeassistant.components.android_ip_webcam import (
KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME) 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'] DEPENDENCIES = ['android_ip_webcam']
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the IP Webcam binary sensors.""" """Set up the IP Webcam binary sensors."""
if discovery_info is None: if discovery_info is None:
return return
@ -51,8 +48,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
"""Return true if the binary sensor is on.""" """Return true if the binary sensor is on."""
return self._state return self._state
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Retrieve latest state.""" """Retrieve latest state."""
state, _ = self._ipcam.export_sensor(self._sensor) state, _ = self._ipcam.export_sensor(self._sensor)
self._state = state == 1.0 self._state = state == 1.0

View File

@ -4,7 +4,6 @@ Use Bayesian Inference to trigger a binary sensor.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.bayesian/ https://home-assistant.io/components/binary_sensor.bayesian/
""" """
import asyncio
import logging import logging
from collections import OrderedDict from collections import OrderedDict
@ -74,9 +73,8 @@ def update_probability(prior, prob_true, prob_false):
return probability return probability
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the Bayesian Binary sensor.""" """Set up the Bayesian Binary sensor."""
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
observations = config.get(CONF_OBSERVATIONS) observations = config.get(CONF_OBSERVATIONS)
@ -119,8 +117,7 @@ class BayesianBinarySensor(BinarySensorDevice):
'state': self._process_state 'state': self._process_state
} }
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Call when entity about to be added.""" """Call when entity about to be added."""
@callback @callback
def async_threshold_sensor_state_listener(entity, old_state, def async_threshold_sensor_state_listener(entity, old_state,
@ -214,7 +211,6 @@ class BayesianBinarySensor(BinarySensorDevice):
ATTR_PROBABILITY_THRESHOLD: self._probability_threshold, ATTR_PROBABILITY_THRESHOLD: self._probability_threshold,
} }
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Get the latest data and update the states.""" """Get the latest data and update the states."""
self._deviation = bool(self.probability >= self._probability_threshold) self._deviation = bool(self.probability >= self._probability_threshold)

View File

@ -2,10 +2,11 @@
Support for Blink system camera control. Support for Blink system camera control.
For more details about this platform, please refer to the documentation at 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.components.binary_sensor import BinarySensorDevice
from homeassistant.const import CONF_MONITORED_CONDITIONS
DEPENDENCIES = ['blink'] DEPENDENCIES = ['blink']
@ -14,24 +15,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up the blink binary sensors.""" """Set up the blink binary sensors."""
if discovery_info is None: if discovery_info is None:
return return
data = hass.data[BLINK_DATA]
data = hass.data[DOMAIN].blink devs = []
devs = list() for camera in data.sync.cameras:
for name in data.cameras: for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
devs.append(BlinkCameraMotionSensor(name, data)) devs.append(BlinkBinarySensor(data, camera, sensor_type))
devs.append(BlinkSystemSensor(data))
add_entities(devs, True) add_entities(devs, True)
class BlinkCameraMotionSensor(BinarySensorDevice): class BlinkBinarySensor(BinarySensorDevice):
"""Representation of a Blink binary sensor.""" """Representation of a Blink binary sensor."""
def __init__(self, name, data): def __init__(self, data, camera, sensor_type):
"""Initialize the sensor.""" """Initialize the sensor."""
self._name = 'blink_' + name + '_motion_enabled'
self._camera_name = name
self.data = data 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 @property
def name(self): def name(self):
@ -46,29 +50,4 @@ class BlinkCameraMotionSensor(BinarySensorDevice):
def update(self): def update(self):
"""Update sensor state.""" """Update sensor state."""
self.data.refresh() self.data.refresh()
self._state = self.data.cameras[self._camera_name].armed self._state = self._camera.attributes[self._type]
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

View File

@ -4,7 +4,6 @@ Reads vehicle status from BMW connected drive portal.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.bmw_connected_drive/ https://home-assistant.io/components/binary_sensor.bmw_connected_drive/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -124,7 +123,10 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
if not check_control_messages: if not check_control_messages:
result['check_control_messages'] = 'OK' result['check_control_messages'] = 'OK'
else: 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': elif self._attribute == 'charging_status':
result['charging_status'] = vehicle_state.charging_status.value result['charging_status'] = vehicle_state.charging_status.value
# pylint: disable=protected-access # pylint: disable=protected-access
@ -190,8 +192,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
"""Schedule a state update.""" """Schedule a state update."""
self.schedule_update_ha_state(True) self.schedule_update_ha_state(True)
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Add callback after being added to hass. """Add callback after being added to hass.
Show latest data after startup. Show latest data after startup.

View File

@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.egardia/ https://home-assistant.io/components/binary_sensor.egardia/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -18,9 +17,8 @@ EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion',
'IR': 'motion'} 'IR': 'motion'}
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Initialize the platform.""" """Initialize the platform."""
if (discovery_info is None or if (discovery_info is None or
discovery_info[ATTR_DISCOVER_DEVICES] is None): discovery_info[ATTR_DISCOVER_DEVICES] is None):

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.envisalink/ https://home-assistant.io/components/binary_sensor.envisalink/
""" """
import asyncio
import logging import logging
import datetime import datetime
@ -22,9 +21,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['envisalink'] DEPENDENCIES = ['envisalink']
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the Envisalink binary sensor devices.""" """Set up the Envisalink binary sensor devices."""
configured_zones = discovery_info['zones'] configured_zones = discovery_info['zones']
@ -56,8 +54,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
_LOGGER.debug('Setting up zone: %s', zone_name) _LOGGER.debug('Setting up zone: %s', zone_name)
super().__init__(zone_name, info, controller) super().__init__(zone_name, info, controller)
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
async_dispatcher_connect( async_dispatcher_connect(
self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) self.hass, SIGNAL_ZONE_UPDATE, self._update_callback)

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_motion/ https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -46,13 +45,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the FFmpeg binary motion sensor.""" """Set up the FFmpeg binary motion sensor."""
manager = hass.data[DATA_FFMPEG] 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 return
entity = FFmpegMotion(hass, manager, config) entity = FFmpegMotion(hass, manager, config)
@ -98,8 +96,7 @@ class FFmpegMotion(FFmpegBinarySensor):
self.ffmpeg = SensorMotion( self.ffmpeg = SensorMotion(
manager.binary, hass.loop, self._async_callback) manager.binary, hass.loop, self._async_callback)
@asyncio.coroutine async def _async_start_ffmpeg(self, entity_ids):
def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance. """Start a FFmpeg instance.
This method is a coroutine. This method is a coroutine.
@ -116,7 +113,7 @@ class FFmpegMotion(FFmpegBinarySensor):
) )
# run # run
yield from self.ffmpeg.open_sensor( await self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT), input_source=self._config.get(CONF_INPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS), extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
) )

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ffmpeg_noise/ https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -43,13 +42,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the FFmpeg noise binary sensor.""" """Set up the FFmpeg noise binary sensor."""
manager = hass.data[DATA_FFMPEG] 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 return
entity = FFmpegNoise(hass, manager, config) entity = FFmpegNoise(hass, manager, config)
@ -67,8 +65,7 @@ class FFmpegNoise(FFmpegBinarySensor):
self.ffmpeg = SensorNoise( self.ffmpeg = SensorNoise(
manager.binary, hass.loop, self._async_callback) manager.binary, hass.loop, self._async_callback)
@asyncio.coroutine async def _async_start_ffmpeg(self, entity_ids):
def _async_start_ffmpeg(self, entity_ids):
"""Start a FFmpeg instance. """Start a FFmpeg instance.
This method is a coroutine. This method is a coroutine.
@ -82,7 +79,7 @@ class FFmpegNoise(FFmpegBinarySensor):
peak=self._config.get(CONF_PEAK), peak=self._config.get(CONF_PEAK),
) )
yield from self.ffmpeg.open_sensor( await self.ffmpeg.open_sensor(
input_source=self._config.get(CONF_INPUT), input_source=self._config.get(CONF_INPUT),
output_dest=self._config.get(CONF_OUTPUT), output_dest=self._config.get(CONF_OUTPUT),
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS), extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),

View File

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

View File

@ -4,7 +4,6 @@ Support for INSTEON dimmers via PowerLinc Modem.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.insteon/ https://home-assistant.io/components/binary_sensor.insteon/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -22,9 +21,8 @@ SENSOR_TYPES = {'openClosedSensor': 'opening',
'batterySensor': 'battery'} 'batterySensor': 'battery'}
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the INSTEON device class for the hass platform.""" """Set up the INSTEON device class for the hass platform."""
insteon_modem = hass.data['insteon'].get('modem') insteon_modem = hass.data['insteon'].get('modem')

View File

@ -4,8 +4,6 @@ Support for ISY994 binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.isy994/ https://home-assistant.io/components/binary_sensor.isy994/
""" """
import asyncio
import logging import logging
from datetime import timedelta from datetime import timedelta
from typing import Callable from typing import Callable
@ -121,10 +119,9 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
self._computed_state = bool(self._node.status._val) self._computed_state = bool(self._node.status._val)
self._status_was_unknown = False self._status_was_unknown = False
@asyncio.coroutine async def async_added_to_hass(self) -> None:
def async_added_to_hass(self) -> None:
"""Subscribe to the node and subnode event emitters.""" """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) self._node.controlEvents.subscribe(self._positive_node_control_handler)
@ -261,10 +258,9 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
self._parent_device = parent_device self._parent_device = parent_device
self._heartbeat_timer = None self._heartbeat_timer = None
@asyncio.coroutine async def async_added_to_hass(self) -> None:
def async_added_to_hass(self) -> None:
"""Subscribe to the node and subnode event emitters.""" """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._node.controlEvents.subscribe(
self._heartbeat_node_control_handler) self._heartbeat_node_control_handler)

View File

@ -4,23 +4,26 @@ Support for MQTT binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mqtt/ https://home-assistant.io/components/binary_sensor.mqtt/
""" """
import asyncio
import logging import logging
from typing import Optional from typing import Optional
import voluptuous as vol import voluptuous as vol
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components import mqtt from homeassistant.components import mqtt, binary_sensor
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA) BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS) CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE, ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability) 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 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__) _LOGGER = logging.getLogger(__name__)
@ -44,13 +47,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) }).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@asyncio.coroutine async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
def async_setup_platform(hass, config, async_add_entities, async_add_entities, discovery_info=None):
discovery_info=None): """Set up MQTT binary sensor through configuration.yaml."""
"""Set up the MQTT binary sensor.""" await _async_setup_entity(hass, config, async_add_entities)
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
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) value_template = config.get(CONF_VALUE_TEMPLATE)
if value_template is not None: if value_template is not None:
value_template.hass = hass value_template.hass = hass
@ -68,19 +86,22 @@ def async_setup_platform(hass, config, async_add_entities,
config.get(CONF_PAYLOAD_NOT_AVAILABLE), config.get(CONF_PAYLOAD_NOT_AVAILABLE),
value_template, value_template,
config.get(CONF_UNIQUE_ID), 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.""" """Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class, def __init__(self, name, state_topic, availability_topic, device_class,
qos, force_update, payload_on, payload_off, payload_available, qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template, payload_not_available, value_template,
unique_id: Optional[str]): unique_id: Optional[str], discovery_hash):
"""Initialize the MQTT binary sensor.""" """Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available, MqttAvailability.__init__(self, availability_topic, qos,
payload_not_available) payload_available, payload_not_available)
MqttDiscoveryUpdate.__init__(self, discovery_hash)
self._name = name self._name = name
self._state = None self._state = None
self._state_topic = state_topic self._state_topic = state_topic
@ -91,11 +112,12 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._force_update = force_update self._force_update = force_update
self._template = value_template self._template = value_template
self._unique_id = unique_id self._unique_id = unique_id
self._discovery_hash = discovery_hash
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Subscribe mqtt events.""" """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 @callback
def state_message_received(topic, payload, qos): def state_message_received(topic, payload, qos):
@ -115,7 +137,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self.async_schedule_update_ha_state() 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) self.hass, self._state_topic, state_message_received, self._qos)
@property @property

View File

@ -3,8 +3,6 @@
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mychevy/ https://home-assistant.io/components/binary_sensor.mychevy/
""" """
import asyncio
import logging import logging
from homeassistant.components.mychevy import ( from homeassistant.components.mychevy import (
@ -22,9 +20,8 @@ SENSORS = [
] ]
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the MyChevy sensors.""" """Set up the MyChevy sensors."""
if discovery_info is None: if discovery_info is None:
return return
@ -75,8 +72,7 @@ class EVBinarySensor(BinarySensorDevice):
"""Return the car.""" """Return the car."""
return self._conn.get_car(self._car_vid) return self._conn.get_car(self._car_vid)
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
self.hass.helpers.dispatcher.async_dispatcher_connect( self.hass.helpers.dispatcher.async_dispatcher_connect(
UPDATE_TOPIC, self.async_update_callback) UPDATE_TOPIC, self.async_update_callback)

View File

@ -4,7 +4,6 @@ Support for the myStrom buttons.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mystrom/ https://home-assistant.io/components/binary_sensor.mystrom/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
@ -16,9 +15,8 @@ _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up myStrom Binary Sensor.""" """Set up myStrom Binary Sensor."""
hass.http.register_view(MyStromView(async_add_entities)) hass.http.register_view(MyStromView(async_add_entities))
@ -37,14 +35,12 @@ class MyStromView(HomeAssistantView):
self.buttons = {} self.buttons = {}
self.add_entities = add_entities self.add_entities = add_entities
@asyncio.coroutine async def get(self, request):
def get(self, request):
"""Handle the GET request received from a myStrom button.""" """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 return res
@asyncio.coroutine async def _handle(self, hass, data):
def _handle(self, hass, data):
"""Handle requests to the myStrom endpoint.""" """Handle requests to the myStrom endpoint."""
button_action = next(( button_action = next((
parameter for parameter in data parameter for parameter in data

View File

@ -93,7 +93,11 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
async def async_update(self): async def async_update(self):
"""Update the state.""" """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: if self._sensor_type == TYPE_PROTECTION_WINDOW:
self._state = parse_datetime( self._state = parse_datetime(
data['from_time']) <= utcnow() <= parse_datetime( data['from_time']) <= utcnow() <= parse_datetime(

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.ping/ https://home-assistant.io/components/binary_sensor.ping/
""" """
import logging import asyncio
import subprocess
import re
import sys
from datetime import timedelta from datetime import timedelta
import logging
import re
import subprocess
import sys
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.components.binary_sensor import ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, PLATFORM_SCHEMA) PLATFORM_SCHEMA, BinarySensorDevice)
from homeassistant.const import CONF_NAME, CONF_HOST from homeassistant.const import CONF_HOST, CONF_NAME
import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _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.""" """Set up the Ping Binary sensor."""
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
host = config.get(CONF_HOST) host = config.get(CONF_HOST)
count = config.get(CONF_PING_COUNT) 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): class PingBinarySensor(BinarySensorDevice):
@ -91,9 +93,9 @@ class PingBinarySensor(BinarySensorDevice):
ATTR_ROUND_TRIP_TIME_MIN: self.ping.data['min'], ATTR_ROUND_TRIP_TIME_MIN: self.ping.data['min'],
} }
def update(self): async def async_update(self):
"""Get the latest data.""" """Get the latest data."""
self.ping.update() await self.ping.update()
class PingData: class PingData:
@ -114,12 +116,13 @@ class PingData:
'ping', '-n', '-q', '-c', str(self._count), '-W1', 'ping', '-n', '-q', '-c', str(self._count), '-W1',
self._ip_address] self._ip_address]
def ping(self): async def ping(self):
"""Send ICMP echo request and return details if success.""" """Send ICMP echo request and return details if success."""
pinger = subprocess.Popen( pinger = await asyncio.create_subprocess_shell(
self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) ' '.join(self._ping_cmd), stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
try: try:
out = pinger.communicate() out = await pinger.communicate()
_LOGGER.debug("Output is %s", str(out)) _LOGGER.debug("Output is %s", str(out))
if sys.platform == 'win32': if sys.platform == 'win32':
match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1]) match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1])
@ -128,7 +131,8 @@ class PingData:
'min': rtt_min, 'min': rtt_min,
'avg': rtt_avg, 'avg': rtt_avg,
'max': rtt_max, 'max': rtt_max,
'mdev': ''} 'mdev': '',
}
if 'max/' not in str(out): if 'max/' not in str(out):
match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1]) match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max = match.groups() rtt_min, rtt_avg, rtt_max = match.groups()
@ -136,18 +140,20 @@ class PingData:
'min': rtt_min, 'min': rtt_min,
'avg': rtt_avg, 'avg': rtt_avg,
'max': rtt_max, 'max': rtt_max,
'mdev': ''} 'mdev': '',
}
match = PING_MATCHER.search(str(out).split('\n')[-1]) match = PING_MATCHER.search(str(out).split('\n')[-1])
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups() rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
return { return {
'min': rtt_min, 'min': rtt_min,
'avg': rtt_avg, 'avg': rtt_avg,
'max': rtt_max, 'max': rtt_max,
'mdev': rtt_mdev} 'mdev': rtt_mdev,
}
except (subprocess.CalledProcessError, AttributeError): except (subprocess.CalledProcessError, AttributeError):
return False return False
def update(self): async def update(self):
"""Retrieve the latest details from the host.""" """Retrieve the latest details from the host."""
self.data = self.ping() self.data = await self.ping()
self.available = bool(self.data) self.available = bool(self.data)

View File

@ -92,6 +92,11 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
"""Return the name of this sensor including the controller name.""" """Return the name of this sensor including the controller name."""
return "{} online".format(self._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 @property
def device_class(self) -> str: def device_class(self) -> str:
"""Return the class of this device, from component DEVICE_CLASSES.""" """Return the class of this device, from component DEVICE_CLASSES."""

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.satel_integra/ https://home-assistant.io/components/binary_sensor.satel_integra/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -20,9 +19,8 @@ DEPENDENCIES = ['satel_integra']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the Satel Integra binary sensor devices.""" """Set up the Satel Integra binary sensor devices."""
if not discovery_info: if not discovery_info:
return return
@ -50,8 +48,7 @@ class SatelIntegraBinarySensor(BinarySensorDevice):
self._zone_type = zone_type self._zone_type = zone_type
self._state = 0 self._state = 0
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
async_dispatcher_connect( async_dispatcher_connect(
self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated) self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated)

View File

@ -9,8 +9,7 @@ import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.core import callback from homeassistant.core import callback
from homeassistant.components.spc import ( from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_SENSOR)
ATTR_DISCOVER_DEVICES, SIGNAL_UPDATE_SENSOR)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -27,13 +26,12 @@ def _get_device_class(zone_type):
async def async_setup_platform(hass, config, async_add_entities, async def async_setup_platform(hass, config, async_add_entities,
discovery_info=None): discovery_info=None):
"""Set up the SPC binary sensor.""" """Set up the SPC binary sensor."""
if (discovery_info is None or if discovery_info is None:
discovery_info[ATTR_DISCOVER_DEVICES] is None):
return return
api = hass.data[DATA_API]
async_add_entities(SpcBinarySensor(zone) async_add_entities([SpcBinarySensor(zone)
for zone in discovery_info[ATTR_DISCOVER_DEVICES] for zone in api.zones.values()
if _get_device_class(zone.type)) if _get_device_class(zone.type)])
class SpcBinarySensor(BinarySensorDevice): class SpcBinarySensor(BinarySensorDevice):

View File

@ -4,7 +4,6 @@ Support for exposing a templated binary sensor.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.template/ https://home-assistant.io/components/binary_sensor.template/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -46,9 +45,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up template binary sensors.""" """Set up template binary sensors."""
sensors = [] sensors = []
@ -109,8 +107,7 @@ class BinarySensorTemplate(BinarySensorDevice):
self._delay_on = delay_on self._delay_on = delay_on
self._delay_off = delay_off self._delay_off = delay_off
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Register callbacks.""" """Register callbacks."""
@callback @callback
def template_bsensor_state_listener(entity, old_state, new_state): def template_bsensor_state_listener(entity, old_state, new_state):

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.threshold/ https://home-assistant.io/components/sensor.threshold/
""" """
import asyncio
import logging import logging
import voluptuous as vol import voluptuous as vol
@ -54,9 +53,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the Threshold sensor.""" """Set up the Threshold sensor."""
entity_id = config.get(CONF_ENTITY_ID) entity_id = config.get(CONF_ENTITY_ID)
name = config.get(CONF_NAME) name = config.get(CONF_NAME)
@ -147,8 +145,7 @@ class ThresholdSensor(BinarySensorDevice):
ATTR_UPPER: self._threshold_upper, ATTR_UPPER: self._threshold_upper,
} }
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Get the latest data and updates the states.""" """Get the latest data and updates the states."""
def below(threshold): def below(threshold):
"""Determine if the sensor value is below a threshold.""" """Determine if the sensor value is below a threshold."""

View File

@ -4,7 +4,6 @@ A sensor that monitors trends in other components.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.trend/ https://home-assistant.io/components/sensor.trend/
""" """
import asyncio
from collections import deque from collections import deque
import logging import logging
import math import math
@ -138,8 +137,7 @@ class SensorTrend(BinarySensorDevice):
"""No polling needed.""" """No polling needed."""
return False return False
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Complete device setup after being added to hass.""" """Complete device setup after being added to hass."""
@callback @callback
def trend_sensor_state_listener(entity, old_state, new_state): def trend_sensor_state_listener(entity, old_state, new_state):
@ -160,8 +158,7 @@ class SensorTrend(BinarySensorDevice):
self.hass, self._entity_id, self.hass, self._entity_id,
trend_sensor_state_listener) trend_sensor_state_listener)
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Get the latest data and update the states.""" """Get the latest data and update the states."""
# Remove outdated samples # Remove outdated samples
if self._sample_duration > 0: if self._sample_duration > 0:
@ -173,7 +170,7 @@ class SensorTrend(BinarySensorDevice):
return return
# Calculate gradient of linear trend # 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 # Update state
self._state = ( self._state = (

View File

@ -4,7 +4,6 @@ Support for Wink binary sensors.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
at https://home-assistant.io/components/binary_sensor.wink/ at https://home-assistant.io/components/binary_sensor.wink/
""" """
import asyncio
import logging import logging
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
@ -101,8 +100,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
else: else:
self.capability = None self.capability = None
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Call when entity is added to hass.""" """Call when entity is added to hass."""
self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self) self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self)

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.workday/ https://home-assistant.io/components/binary_sensor.workday/
""" """
import asyncio
import logging import logging
from datetime import datetime, timedelta from datetime import datetime, timedelta
@ -162,8 +161,7 @@ class IsWorkdaySensor(BinarySensorDevice):
CONF_OFFSET: self._days_offset CONF_OFFSET: self._days_offset
} }
@asyncio.coroutine async def async_update(self):
def async_update(self):
"""Get date and look whether it is a holiday.""" """Get date and look whether it is a holiday."""
# Default is no workday # Default is no workday
self._state = False self._state = False

View File

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

View File

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

View File

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

View File

@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['bimmer_connected==0.5.2'] REQUIREMENTS = ['bimmer_connected==0.5.3']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

@ -83,62 +83,6 @@ class Image:
content = attr.ib(type=bytes) 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 @bind_hass
async def async_get_image(hass, entity_id, timeout=10): async def async_get_image(hass, entity_id, timeout=10):
"""Fetch an image from a camera entity.""" """Fetch an image from a camera entity."""
@ -239,7 +183,7 @@ async def async_setup(hass, config):
"""Update tokens of the entities.""" """Update tokens of the entities."""
for entity in component.entities: for entity in component.entities:
entity.async_update_token() 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( hass.helpers.event.async_track_time_interval(
update_tokens, TOKEN_CHANGE_INTERVAL) update_tokens, TOKEN_CHANGE_INTERVAL)
@ -508,27 +452,23 @@ class CameraMjpegStream(CameraView):
raise web.HTTPBadRequest() raise web.HTTPBadRequest()
@callback @websocket_api.async_response
def websocket_camera_thumbnail(hass, connection, msg): async def websocket_camera_thumbnail(hass, connection, msg):
"""Handle get camera thumbnail websocket command. """Handle get camera thumbnail websocket command.
Async friendly. Async friendly.
""" """
async def send_camera_still(): try:
"""Send a camera still.""" image = await async_get_image(hass, msg['entity_id'])
try: connection.send_message(websocket_api.result_message(
image = await async_get_image(hass, msg['entity_id']) msg['id'], {
connection.send_message_outside(websocket_api.result_message( 'content_type': image.content_type,
msg['id'], { 'content': base64.b64encode(image.content).decode('utf-8')
'content_type': image.content_type, }
'content': base64.b64encode(image.content).decode('utf-8') ))
} except HomeAssistantError:
)) connection.send_message(websocket_api.error_message(
except HomeAssistantError: msg['id'], 'image_fetch_failed', 'Unable to fetch image'))
connection.send_message_outside(websocket_api.error_message(
msg['id'], 'image_fetch_failed', 'Unable to fetch image'))
hass.async_add_job(send_camera_still())
async def async_handle_snapshot_service(camera, service): async def async_handle_snapshot_service(camera, service):

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.abode/ https://home-assistant.io/components/camera.abode/
""" """
import asyncio
import logging import logging
from datetime import timedelta from datetime import timedelta
@ -51,10 +50,9 @@ class AbodeCamera(AbodeDevice, Camera):
self._event = event self._event = event
self._response = None self._response = None
@asyncio.coroutine async def async_added_to_hass(self):
def async_added_to_hass(self):
"""Subscribe Abode events.""" """Subscribe Abode events."""
yield from super().async_added_to_hass() await super().async_added_to_hass()
self.hass.async_add_job( self.hass.async_add_job(
self._data.abode.events.add_timeline_callback, self._data.abode.events.add_timeline_callback,

View File

@ -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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.amcrest/ https://home-assistant.io/components/camera.amcrest/
""" """
import asyncio
import logging import logging
from homeassistant.components.amcrest import ( from homeassistant.components.amcrest import (
@ -21,9 +20,8 @@ DEPENDENCIES = ['amcrest', 'ffmpeg']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up an Amcrest IP Camera.""" """Set up an Amcrest IP Camera."""
if discovery_info is None: if discovery_info is None:
return return
@ -57,12 +55,11 @@ class AmcrestCam(Camera):
response = self._camera.snapshot(channel=self._resolution) response = self._camera.snapshot(channel=self._resolution)
return response.data return response.data
@asyncio.coroutine async def handle_async_mjpeg_stream(self, request):
def handle_async_mjpeg_stream(self, request):
"""Return an MJPEG stream.""" """Return an MJPEG stream."""
# The snapshot implementation is handled by the parent class # The snapshot implementation is handled by the parent class
if self._stream_source == STREAM_SOURCE_LIST['snapshot']: if self._stream_source == STREAM_SOURCE_LIST['snapshot']:
yield from super().handle_async_mjpeg_stream(request) await super().handle_async_mjpeg_stream(request)
return return
if self._stream_source == STREAM_SOURCE_LIST['mjpeg']: if self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
@ -72,7 +69,7 @@ class AmcrestCam(Camera):
stream_coro = websession.get( stream_coro = websession.get(
streaming_url, auth=self._token, timeout=TIMEOUT) 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: else:
# streaming via fmpeg # streaming via fmpeg
@ -80,13 +77,13 @@ class AmcrestCam(Camera):
streaming_url = self._camera.rtsp_url(typeno=self._resolution) streaming_url = self._camera.rtsp_url(typeno=self._resolution)
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) 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) streaming_url, extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream( await async_aiohttp_proxy_stream(
self.hass, request, stream, self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver') 'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close() await stream.close()
@property @property
def name(self): def name(self):

View File

@ -4,31 +4,27 @@ Support for Blink system camera.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.blink/ https://home-assistant.io/components/camera.blink/
""" """
from datetime import timedelta
import logging import logging
import requests from homeassistant.components.blink import BLINK_DATA, DEFAULT_BRAND
from homeassistant.components.blink import DOMAIN
from homeassistant.components.camera import Camera from homeassistant.components.camera import Camera
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['blink'] 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): def setup_platform(hass, config, add_entities, discovery_info=None):
"""Set up a Blink Camera.""" """Set up a Blink Camera."""
if discovery_info is None: if discovery_info is None:
return return
data = hass.data[BLINK_DATA]
data = hass.data[DOMAIN].blink devs = []
devs = list() for name, camera in data.sync.cameras.items():
for name in data.cameras: devs.append(BlinkCamera(data, name, camera))
devs.append(BlinkCamera(hass, config, data, name))
add_entities(devs) add_entities(devs)
@ -36,15 +32,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
class BlinkCamera(Camera): class BlinkCamera(Camera):
"""An implementation of a Blink Camera.""" """An implementation of a Blink Camera."""
def __init__(self, hass, config, data, name): def __init__(self, data, name, camera):
"""Initialize a camera.""" """Initialize a camera."""
super().__init__() super().__init__()
self.data = data self.data = data
self.hass = hass self._name = "{} {}".format(BLINK_DATA, name)
self._name = name self._camera = camera
self.notifications = self.data.cameras[self._name].notifications
self.response = None self.response = None
self.current_image = None
self.last_image = None
_LOGGER.debug("Initialized blink camera %s", self._name) _LOGGER.debug("Initialized blink camera %s", self._name)
@property @property
@ -52,30 +48,29 @@ class BlinkCamera(Camera):
"""Return the camera name.""" """Return the camera name."""
return self._name return self._name
@Throttle(MIN_TIME_BETWEEN_UPDATES) @property
def request_image(self): def device_state_attributes(self):
"""Request a new image from Blink servers.""" """Return the camera attributes."""
_LOGGER.debug("Requesting new image from blink servers") return self._camera.attributes
image_url = self.check_for_motion()
header = self.data.cameras[self._name].header
self.response = requests.get(image_url, headers=header, stream=True)
def check_for_motion(self): def enable_motion_detection(self):
"""Check if motion has been detected since last update.""" """Enable motion detection for the camera."""
self.data.refresh() self._camera.set_motion_detect(True)
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
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): def camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
self.request_image() return self._camera.image_from_cache.content
return self.response.content

View File

@ -75,35 +75,33 @@ class CanaryCamera(Camera):
"""Return the camera motion detection status.""" """Return the camera motion detection status."""
return not self._location.is_recording return not self._location.is_recording
@asyncio.coroutine async def async_camera_image(self):
def async_camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
self.renew_live_stream_session() self.renew_live_stream_session()
from haffmpeg import ImageFrame, IMAGE_JPEG from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) 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, self._live_stream_session.live_stream_url,
output_format=IMAGE_JPEG, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image return image
@asyncio.coroutine async def handle_async_mjpeg_stream(self, request):
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera.""" """Generate an HTTP MJPEG stream from the camera."""
if self._live_stream_session is None: if self._live_stream_session is None:
return return
from haffmpeg import CameraMjpeg from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) 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, self._live_stream_session.live_stream_url,
extra_cmd=self._ffmpeg_arguments) extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream( await async_aiohttp_proxy_stream(
self.hass, request, stream, self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver') 'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close() await stream.close()
@Throttle(MIN_TIME_BETWEEN_SESSION_RENEW) @Throttle(MIN_TIME_BETWEEN_SESSION_RENEW)
def renew_live_stream_session(self): def renew_live_stream_session(self):

View File

@ -27,9 +27,8 @@ _LOGGER = logging.getLogger(__name__)
_TIMEOUT = 10 # seconds _TIMEOUT = 10 # seconds
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up the DoorBird camera platform.""" """Set up the DoorBird camera platform."""
for doorstation in hass.data[DOORBIRD_DOMAIN]: for doorstation in hass.data[DOORBIRD_DOMAIN]:
device = doorstation.device device = doorstation.device
@ -66,8 +65,7 @@ class DoorBirdCamera(Camera):
"""Get the name of the camera.""" """Get the name of the camera."""
return self._name return self._name
@asyncio.coroutine async def async_camera_image(self):
def async_camera_image(self):
"""Pull a still image from the camera.""" """Pull a still image from the camera."""
now = datetime.datetime.now() now = datetime.datetime.now()
@ -77,9 +75,9 @@ class DoorBirdCamera(Camera):
try: try:
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
with async_timeout.timeout(_TIMEOUT, loop=self.hass.loop): 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 self._last_update = now
return self._last_image return self._last_image
except asyncio.TimeoutError: except asyncio.TimeoutError:

View File

@ -46,9 +46,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up a generic IP Camera.""" """Set up a generic IP Camera."""
async_add_entities([GenericCamera(hass, config)]) async_add_entities([GenericCamera(hass, config)])
@ -93,8 +92,7 @@ class GenericCamera(Camera):
return run_coroutine_threadsafe( return run_coroutine_threadsafe(
self.async_camera_image(), self.hass.loop).result() self.async_camera_image(), self.hass.loop).result()
@asyncio.coroutine async def async_camera_image(self):
def async_camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
try: try:
url = self._still_image_url.async_render() url = self._still_image_url.async_render()
@ -118,7 +116,7 @@ class GenericCamera(Camera):
_LOGGER.error("Error getting camera image: %s", error) _LOGGER.error("Error getting camera image: %s", error)
return self._last_image return self._last_image
self._last_image = yield from self.hass.async_add_job( self._last_image = await self.hass.async_add_job(
fetch) fetch)
# async # async
else: else:
@ -126,9 +124,9 @@ class GenericCamera(Camera):
websession = async_get_clientsession( websession = async_get_clientsession(
self.hass, verify_ssl=self.verify_ssl) self.hass, verify_ssl=self.verify_ssl)
with async_timeout.timeout(10, loop=self.hass.loop): with async_timeout.timeout(10, loop=self.hass.loop):
response = yield from websession.get( response = await websession.get(
url, auth=self._auth) url, auth=self._auth)
self._last_image = yield from response.read() self._last_image = await response.read()
except asyncio.TimeoutError: except asyncio.TimeoutError:
_LOGGER.error("Timeout getting camera image") _LOGGER.error("Timeout getting camera image")
return self._last_image return self._last_image

View File

@ -41,10 +41,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up a MJPEG IP Camera.""" """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: if discovery_info:
config = PLATFORM_SCHEMA(discovery_info) config = PLATFORM_SCHEMA(discovery_info)
async_add_entities([MjpegCamera(config)]) async_add_entities([MjpegCamera(config)])
@ -82,23 +89,22 @@ class MjpegCamera(Camera):
self._username, password=self._password self._username, password=self._password
) )
@asyncio.coroutine async def async_camera_image(self):
def async_camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
# DigestAuth is not supported # DigestAuth is not supported
if self._authentication == HTTP_DIGEST_AUTHENTICATION or \ if self._authentication == HTTP_DIGEST_AUTHENTICATION or \
self._still_image_url is None: self._still_image_url is None:
image = yield from self.hass.async_add_job( image = await self.hass.async_add_job(
self.camera_image) self.camera_image)
return image return image
websession = async_get_clientsession(self.hass) websession = async_get_clientsession(self.hass)
try: try:
with async_timeout.timeout(10, loop=self.hass.loop): 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) self._still_image_url, auth=self._auth)
image = yield from response.read() image = await response.read()
return image return image
except asyncio.TimeoutError: except asyncio.TimeoutError:
@ -141,3 +147,11 @@ class MjpegCamera(Camera):
def name(self): def name(self):
"""Return the name of this camera.""" """Return the name of this camera."""
return self._name 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()

View File

@ -10,10 +10,13 @@ import logging
import voluptuous as vol 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.core import callback
from homeassistant.components import mqtt
from homeassistant.const import CONF_NAME from homeassistant.const import CONF_NAME
from homeassistant.components import mqtt, camera
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA 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 from homeassistant.helpers import config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -31,13 +34,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
def async_setup_platform(hass, config, async_add_entities, async_add_entities, discovery_info=None):
discovery_info=None): """Set up MQTT camera through configuration.yaml."""
"""Set up the MQTT Camera.""" await _async_setup_entity(hass, config, async_add_entities)
if discovery_info is not None:
config = PLATFORM_SCHEMA(discovery_info)
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( async_add_entities([MqttCamera(
config.get(CONF_NAME), config.get(CONF_NAME),
config.get(CONF_UNIQUE_ID), config.get(CONF_UNIQUE_ID),

View File

@ -46,6 +46,7 @@ DIR_LEFT = "LEFT"
DIR_RIGHT = "RIGHT" DIR_RIGHT = "RIGHT"
ZOOM_OUT = "ZOOM_OUT" ZOOM_OUT = "ZOOM_OUT"
ZOOM_IN = "ZOOM_IN" ZOOM_IN = "ZOOM_IN"
PTZ_NONE = "NONE"
SERVICE_PTZ = "onvif_ptz" SERVICE_PTZ = "onvif_ptz"
@ -65,9 +66,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
SERVICE_PTZ_SCHEMA = vol.Schema({ SERVICE_PTZ_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids, ATTR_ENTITY_ID: cv.entity_ids,
ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]), ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT, PTZ_NONE]),
ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]), ATTR_TILT: vol.In([DIR_UP, DIR_DOWN, PTZ_NONE]),
ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN]) ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN, PTZ_NONE])
}) })

View File

@ -110,8 +110,7 @@ class RingCam(Camera):
'video_url': self._video_url, 'video_url': self._video_url,
} }
@asyncio.coroutine async def async_camera_image(self):
def async_camera_image(self):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
from haffmpeg import ImageFrame, IMAGE_JPEG from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
@ -119,13 +118,12 @@ class RingCam(Camera):
if self._video_url is None: if self._video_url is None:
return return
image = yield from asyncio.shield(ffmpeg.get_image( image = await asyncio.shield(ffmpeg.get_image(
self._video_url, output_format=IMAGE_JPEG, self._video_url, output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image return image
@asyncio.coroutine async def handle_async_mjpeg_stream(self, request):
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera.""" """Generate an HTTP MJPEG stream from the camera."""
from haffmpeg import CameraMjpeg from haffmpeg import CameraMjpeg
@ -133,13 +131,13 @@ class RingCam(Camera):
return return
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) 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) self._video_url, extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream( await async_aiohttp_proxy_stream(
self.hass, request, stream, self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver') 'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close() await stream.close()
@property @property
def should_poll(self): def should_poll(self):

View File

@ -4,7 +4,6 @@ Support for Synology Surveillance Station Cameras.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.synology/ https://home-assistant.io/components/camera.synology/
""" """
import asyncio
import logging import logging
import requests import requests
@ -38,9 +37,8 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
}) })
@asyncio.coroutine async def async_setup_platform(hass, config, async_add_entities,
def async_setup_platform(hass, config, async_add_entities, discovery_info=None):
discovery_info=None):
"""Set up a Synology IP Camera.""" """Set up a Synology IP Camera."""
verify_ssl = config.get(CONF_VERIFY_SSL) verify_ssl = config.get(CONF_VERIFY_SSL)
timeout = config.get(CONF_TIMEOUT) timeout = config.get(CONF_TIMEOUT)
@ -87,15 +85,14 @@ class SynologyCamera(Camera):
"""Return bytes of camera image.""" """Return bytes of camera image."""
return self._surveillance.get_camera_image(self._camera_id) return self._surveillance.get_camera_image(self._camera_id)
@asyncio.coroutine async def handle_async_mjpeg_stream(self, request):
def handle_async_mjpeg_stream(self, request):
"""Return a MJPEG stream image response directly from the camera.""" """Return a MJPEG stream image response directly from the camera."""
streaming_url = self._camera.video_stream_url streaming_url = self._camera.video_stream_url
websession = async_get_clientsession(self.hass, self._verify_ssl) websession = async_get_clientsession(self.hass, self._verify_ssl)
stream_coro = websession.get(streaming_url) 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 @property
def name(self): def name(self):

View File

@ -2,11 +2,11 @@
"config": { "config": {
"abort": { "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.", "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": { "step": {
"confirm": { "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" "title": "Google Cast"
} }
}, },

View File

@ -1,7 +1,7 @@
{ {
"config": { "config": {
"abort": { "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" "single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Google Cast \u5373\u53ef\u3002"
}, },
"step": { "step": {

View File

@ -10,7 +10,6 @@ import functools as ft
import voluptuous as vol import voluptuous as vol
from homeassistant.loader import bind_hass
from homeassistant.helpers.temperature import display_temp as show_temp from homeassistant.helpers.temperature import display_temp as show_temp
from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.temperature import convert as convert_temperature
from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity_component import EntityComponent
@ -20,7 +19,7 @@ import homeassistant.helpers.config_validation as cv
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE, STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
PRECISION_TENTHS, ) PRECISION_TENTHS)
DEFAULT_MIN_TEMP = 7 DEFAULT_MIN_TEMP = 7
DEFAULT_MAX_TEMP = 35 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): async def async_setup(hass, config):
"""Set up climate devices.""" """Set up climate devices."""
component = hass.data[DOMAIN] = \ component = hass.data[DOMAIN] = \

View File

@ -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 + "]"
)

View File

@ -10,13 +10,15 @@ import requests
from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN
from homeassistant.components.fritzbox import ( 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 ( from homeassistant.components.climate import (
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL, ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE, STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_TEMPERATURE) SUPPORT_TARGET_TEMPERATURE)
from homeassistant.const import ( from homeassistant.const import (
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS) ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
DEPENDENCIES = ['fritzbox'] DEPENDENCIES = ['fritzbox']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -151,10 +153,21 @@ class FritzboxThermostat(ClimateDevice):
def device_state_attributes(self): def device_state_attributes(self):
"""Return the device specific state attributes.""" """Return the device specific state attributes."""
attrs = { attrs = {
ATTR_STATE_BATTERY_LOW: self._device.battery_low,
ATTR_STATE_DEVICE_LOCKED: self._device.device_lock, ATTR_STATE_DEVICE_LOCKED: self._device.device_lock,
ATTR_STATE_LOCKED: self._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 return attrs
def update(self): def update(self):

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