mirror of
https://github.com/home-assistant/core.git
synced 2025-07-19 11:17:21 +00:00
commit
8eb4e77365
20
.coveragerc
20
.coveragerc
@ -28,6 +28,9 @@ omit =
|
||||
homeassistant/components/apple_tv.py
|
||||
homeassistant/components/*/apple_tv.py
|
||||
|
||||
homeassistant/components/aqualogic.py
|
||||
homeassistant/components/*/aqualogic.py
|
||||
|
||||
homeassistant/components/arduino.py
|
||||
homeassistant/components/*/arduino.py
|
||||
|
||||
@ -53,7 +56,7 @@ omit =
|
||||
homeassistant/components/bbb_gpio.py
|
||||
homeassistant/components/*/bbb_gpio.py
|
||||
|
||||
homeassistant/components/blink.py
|
||||
homeassistant/components/blink/*
|
||||
homeassistant/components/*/blink.py
|
||||
|
||||
homeassistant/components/bloomsky.py
|
||||
@ -105,8 +108,11 @@ omit =
|
||||
homeassistant/components/envisalink.py
|
||||
homeassistant/components/*/envisalink.py
|
||||
|
||||
homeassistant/components/evohome.py
|
||||
homeassistant/components/*/evohome.py
|
||||
|
||||
homeassistant/components/fritzbox.py
|
||||
homeassistant/components/switch/fritzbox.py
|
||||
homeassistant/components/*/fritzbox.py
|
||||
|
||||
homeassistant/components/ecovacs.py
|
||||
homeassistant/components/*/ecovacs.py
|
||||
@ -310,6 +316,9 @@ omit =
|
||||
|
||||
homeassistant/components/*/thinkingcleaner.py
|
||||
|
||||
homeassistant/components/tibber/*
|
||||
homeassistant/components/*/tibber.py
|
||||
|
||||
homeassistant/components/toon.py
|
||||
homeassistant/components/*/toon.py
|
||||
|
||||
@ -510,6 +519,7 @@ omit =
|
||||
homeassistant/components/light/lw12wifi.py
|
||||
homeassistant/components/light/mystrom.py
|
||||
homeassistant/components/light/nanoleaf_aurora.py
|
||||
homeassistant/components/light/opple.py
|
||||
homeassistant/components/light/osramlightify.py
|
||||
homeassistant/components/light/piglow.py
|
||||
homeassistant/components/light/rpi_gpio_pwm.py
|
||||
@ -620,7 +630,6 @@ omit =
|
||||
homeassistant/components/notify/telstra.py
|
||||
homeassistant/components/notify/twitter.py
|
||||
homeassistant/components/notify/xmpp.py
|
||||
homeassistant/components/notify/yessssms.py
|
||||
homeassistant/components/nuimo_controller.py
|
||||
homeassistant/components/prometheus.py
|
||||
homeassistant/components/rainbird.py
|
||||
@ -679,6 +688,7 @@ omit =
|
||||
homeassistant/components/sensor/fritzbox_netmonitor.py
|
||||
homeassistant/components/sensor/gearbest.py
|
||||
homeassistant/components/sensor/geizhals.py
|
||||
homeassistant/components/sensor/gitlab_ci.py
|
||||
homeassistant/components/sensor/gitter.py
|
||||
homeassistant/components/sensor/glances.py
|
||||
homeassistant/components/sensor/google_travel_time.py
|
||||
@ -687,6 +697,7 @@ omit =
|
||||
homeassistant/components/sensor/haveibeenpwned.py
|
||||
homeassistant/components/sensor/hp_ilo.py
|
||||
homeassistant/components/sensor/htu21d.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/imap_email_content.py
|
||||
homeassistant/components/sensor/imap.py
|
||||
homeassistant/components/sensor/influxdb.py
|
||||
@ -709,6 +720,7 @@ omit =
|
||||
homeassistant/components/sensor/mqtt_room.py
|
||||
homeassistant/components/sensor/mvglive.py
|
||||
homeassistant/components/sensor/nederlandse_spoorwegen.py
|
||||
homeassistant/components/sensor/netatmo_public.py
|
||||
homeassistant/components/sensor/netdata.py
|
||||
homeassistant/components/sensor/netdata_public.py
|
||||
homeassistant/components/sensor/neurio_energy.py
|
||||
@ -764,7 +776,6 @@ omit =
|
||||
homeassistant/components/sensor/tank_utility.py
|
||||
homeassistant/components/sensor/ted5000.py
|
||||
homeassistant/components/sensor/temper.py
|
||||
homeassistant/components/sensor/tibber.py
|
||||
homeassistant/components/sensor/time_date.py
|
||||
homeassistant/components/sensor/torque.py
|
||||
homeassistant/components/sensor/trafikverket_weatherstation.py
|
||||
@ -772,7 +783,6 @@ omit =
|
||||
homeassistant/components/sensor/travisci.py
|
||||
homeassistant/components/sensor/twitch.py
|
||||
homeassistant/components/sensor/uber.py
|
||||
homeassistant/components/sensor/upnp.py
|
||||
homeassistant/components/sensor/ups.py
|
||||
homeassistant/components/sensor/uscis.py
|
||||
homeassistant/components/sensor/vasttrafik.py
|
||||
|
12
CODEOWNERS
12
CODEOWNERS
@ -41,6 +41,7 @@ homeassistant/components/hassio.py @home-assistant/hassio
|
||||
# Individual components
|
||||
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
|
||||
homeassistant/components/alarm_control_panel/manual_mqtt.py @colinodell
|
||||
homeassistant/components/alarm_control_panel/simplisafe.py @bachya
|
||||
homeassistant/components/binary_sensor/hikvision.py @mezz64
|
||||
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/camera/yi.py @bachya
|
||||
@ -50,6 +51,7 @@ homeassistant/components/climate/sensibo.py @andrey-git
|
||||
homeassistant/components/cover/group.py @cdce8p
|
||||
homeassistant/components/cover/template.py @PhracturedBlue
|
||||
homeassistant/components/device_tracker/automatic.py @armills
|
||||
homeassistant/components/device_tracker/huawei_router.py @abmantis
|
||||
homeassistant/components/device_tracker/tile.py @bachya
|
||||
homeassistant/components/history_graph.py @andrey-git
|
||||
homeassistant/components/light/lifx.py @amelchio
|
||||
@ -80,19 +82,21 @@ homeassistant/components/sensor/qnap.py @colinodell
|
||||
homeassistant/components/sensor/sma.py @kellerza
|
||||
homeassistant/components/sensor/sql.py @dgomes
|
||||
homeassistant/components/sensor/sytadin.py @gautric
|
||||
homeassistant/components/sensor/tibber.py @danielhiversen
|
||||
homeassistant/components/sensor/upnp.py @dgomes
|
||||
homeassistant/components/sensor/waqi.py @andrey-git
|
||||
homeassistant/components/switch/tplink.py @rytilahti
|
||||
homeassistant/components/vacuum/roomba.py @pschmitt
|
||||
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
|
||||
|
||||
homeassistant/components/*/axis.py @kane610
|
||||
homeassistant/components/blink/* @fronzbot
|
||||
homeassistant/components/*/blink.py @fronzbot
|
||||
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
|
||||
homeassistant/components/*/broadlink.py @danielhiversen
|
||||
homeassistant/components/*/deconz.py @kane610
|
||||
homeassistant/components/ecovacs.py @OverloadUT
|
||||
homeassistant/components/*/ecovacs.py @OverloadUT
|
||||
homeassistant/components/edp_redy.py @abmantis
|
||||
homeassistant/components/*/edp_redy.py @abmantis
|
||||
homeassistant/components/eight_sleep.py @mezz64
|
||||
homeassistant/components/*/eight_sleep.py @mezz64
|
||||
homeassistant/components/hive.py @Rendili @KJonline
|
||||
@ -106,7 +110,7 @@ homeassistant/components/konnected.py @heythisisnate
|
||||
homeassistant/components/*/konnected.py @heythisisnate
|
||||
homeassistant/components/matrix.py @tinloaf
|
||||
homeassistant/components/*/matrix.py @tinloaf
|
||||
homeassistant/components/openuv.py @bachya
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/*/openuv.py @bachya
|
||||
homeassistant/components/qwikswitch.py @kellerza
|
||||
homeassistant/components/*/qwikswitch.py @kellerza
|
||||
@ -119,6 +123,8 @@ homeassistant/components/tesla.py @zabuldon
|
||||
homeassistant/components/*/tesla.py @zabuldon
|
||||
homeassistant/components/tellduslive.py @molobrakos @fredrike
|
||||
homeassistant/components/*/tellduslive.py @molobrakos @fredrike
|
||||
homeassistant/components/tibber/* @danielhiversen
|
||||
homeassistant/components/*/tibber.py @danielhiversen
|
||||
homeassistant/components/*/tradfri.py @ggravlingen
|
||||
homeassistant/components/upcloud.py @scop
|
||||
homeassistant/components/*/upcloud.py @scop
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""Storage for auth models."""
|
||||
from collections import OrderedDict
|
||||
from datetime import timedelta
|
||||
import hmac
|
||||
from logging import getLogger
|
||||
from typing import Any, Dict, List, Optional # noqa: F401
|
||||
import hmac
|
||||
|
||||
from homeassistant.auth.const import ACCESS_TOKEN_EXPIRATION
|
||||
from homeassistant.core import HomeAssistant, callback
|
||||
@ -28,7 +28,8 @@ class AuthStore:
|
||||
"""Initialize the auth store."""
|
||||
self.hass = hass
|
||||
self._users = None # type: Optional[Dict[str, models.User]]
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||
private=True)
|
||||
|
||||
async def async_get_users(self) -> List[models.User]:
|
||||
"""Retrieve all users."""
|
||||
@ -213,14 +214,24 @@ class AuthStore:
|
||||
if self._users is not None:
|
||||
return
|
||||
|
||||
users = OrderedDict() # type: Dict[str, models.User]
|
||||
|
||||
if data is None:
|
||||
self._users = users
|
||||
self._set_defaults()
|
||||
return
|
||||
|
||||
users = OrderedDict() # type: Dict[str, models.User]
|
||||
|
||||
# When creating objects we mention each attribute explicetely. This
|
||||
# prevents crashing if user rolls back HA version after a new property
|
||||
# was added.
|
||||
|
||||
for user_dict in data['users']:
|
||||
users[user_dict['id']] = models.User(**user_dict)
|
||||
users[user_dict['id']] = models.User(
|
||||
name=user_dict['name'],
|
||||
id=user_dict['id'],
|
||||
is_owner=user_dict['is_owner'],
|
||||
is_active=user_dict['is_active'],
|
||||
system_generated=user_dict['system_generated'],
|
||||
)
|
||||
|
||||
for cred_dict in data['credentials']:
|
||||
users[cred_dict['user_id']].credentials.append(models.Credentials(
|
||||
@ -340,3 +351,7 @@ class AuthStore:
|
||||
'credentials': credentials,
|
||||
'refresh_tokens': refresh_tokens,
|
||||
}
|
||||
|
||||
def _set_defaults(self) -> None:
|
||||
"""Set default values for auth store."""
|
||||
self._users = OrderedDict() # type: Dict[str, models.User]
|
||||
|
@ -85,7 +85,7 @@ class NotifyAuthModule(MultiFactorAuthModule):
|
||||
super().__init__(hass, config)
|
||||
self._user_settings = None # type: Optional[_UsersDict]
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY)
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True)
|
||||
self._include = config.get(CONF_INCLUDE, [])
|
||||
self._exclude = config.get(CONF_EXCLUDE, [])
|
||||
self._message_template = config[CONF_MESSAGE]
|
||||
|
@ -67,7 +67,7 @@ class TotpAuthModule(MultiFactorAuthModule):
|
||||
super().__init__(hass, config)
|
||||
self._users = None # type: Optional[Dict[str, str]]
|
||||
self._user_store = hass.helpers.storage.Store(
|
||||
STORAGE_VERSION, STORAGE_KEY)
|
||||
STORAGE_VERSION, STORAGE_KEY, private=True)
|
||||
|
||||
@property
|
||||
def input_schema(self) -> vol.Schema:
|
||||
|
@ -19,19 +19,19 @@ class User:
|
||||
"""A user."""
|
||||
|
||||
name = attr.ib(type=str) # type: Optional[str]
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
is_owner = attr.ib(type=bool, default=False)
|
||||
is_active = attr.ib(type=bool, default=False)
|
||||
system_generated = attr.ib(type=bool, default=False)
|
||||
|
||||
# List of credentials of a user.
|
||||
credentials = attr.ib(
|
||||
type=list, default=attr.Factory(list), cmp=False
|
||||
type=list, factory=list, cmp=False
|
||||
) # type: List[Credentials]
|
||||
|
||||
# Tokens associated with a user.
|
||||
refresh_tokens = attr.ib(
|
||||
type=dict, default=attr.Factory(dict), cmp=False
|
||||
type=dict, factory=dict, cmp=False
|
||||
) # type: Dict[str, RefreshToken]
|
||||
|
||||
|
||||
@ -48,12 +48,10 @@ class RefreshToken:
|
||||
validator=attr.validators.in_((
|
||||
TOKEN_TYPE_NORMAL, TOKEN_TYPE_SYSTEM,
|
||||
TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN)))
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
created_at = attr.ib(type=datetime, default=attr.Factory(dt_util.utcnow))
|
||||
token = attr.ib(type=str,
|
||||
default=attr.Factory(lambda: generate_secret(64)))
|
||||
jwt_key = attr.ib(type=str,
|
||||
default=attr.Factory(lambda: generate_secret(64)))
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
created_at = attr.ib(type=datetime, factory=dt_util.utcnow)
|
||||
token = attr.ib(type=str, factory=lambda: generate_secret(64))
|
||||
jwt_key = attr.ib(type=str, factory=lambda: generate_secret(64))
|
||||
|
||||
last_used_at = attr.ib(type=Optional[datetime], default=None)
|
||||
last_used_ip = attr.ib(type=Optional[str], default=None)
|
||||
@ -69,7 +67,7 @@ class Credentials:
|
||||
# Allow the auth provider to store data to represent their auth.
|
||||
data = attr.ib(type=dict)
|
||||
|
||||
id = attr.ib(type=str, default=attr.Factory(lambda: uuid.uuid4().hex))
|
||||
id = attr.ib(type=str, factory=lambda: uuid.uuid4().hex)
|
||||
is_new = attr.ib(type=bool, default=True)
|
||||
|
||||
|
||||
|
@ -52,7 +52,8 @@ class Data:
|
||||
def __init__(self, hass: HomeAssistant) -> None:
|
||||
"""Initialize the user data store."""
|
||||
self.hass = hass
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY)
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||
private=True)
|
||||
self._data = None # type: Optional[Dict[str, Any]]
|
||||
|
||||
async def async_load(self) -> None:
|
||||
|
@ -59,61 +59,9 @@ def is_on(hass, entity_id=None):
|
||||
return False
|
||||
|
||||
|
||||
def turn_on(hass, entity_id=None, **service_data):
|
||||
"""Turn specified entity on if possible."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(ha.DOMAIN, SERVICE_TURN_ON, service_data)
|
||||
|
||||
|
||||
def turn_off(hass, entity_id=None, **service_data):
|
||||
"""Turn specified entity off."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(ha.DOMAIN, SERVICE_TURN_OFF, service_data)
|
||||
|
||||
|
||||
def toggle(hass, entity_id=None, **service_data):
|
||||
"""Toggle specified entity."""
|
||||
if entity_id is not None:
|
||||
service_data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(ha.DOMAIN, SERVICE_TOGGLE, service_data)
|
||||
|
||||
|
||||
def stop(hass):
|
||||
"""Stop Home Assistant."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP)
|
||||
|
||||
|
||||
def restart(hass):
|
||||
"""Stop Home Assistant."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART)
|
||||
|
||||
|
||||
def check_config(hass):
|
||||
"""Check the config files."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_CHECK_CONFIG)
|
||||
|
||||
|
||||
def reload_core_config(hass):
|
||||
"""Reload the core config."""
|
||||
hass.services.call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_reload_core_config(hass):
|
||||
"""Reload the core config."""
|
||||
yield from hass.services.async_call(ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
async def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
"""Set up general services related to Home Assistant."""
|
||||
@asyncio.coroutine
|
||||
def async_handle_turn_service(service):
|
||||
async def async_handle_turn_service(service):
|
||||
"""Handle calls to homeassistant.turn_on/off."""
|
||||
entity_ids = extract_entity_ids(hass, service)
|
||||
|
||||
@ -148,7 +96,7 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
tasks.append(hass.services.async_call(
|
||||
domain, service.service, data, blocking))
|
||||
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service)
|
||||
@ -164,15 +112,14 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
|
||||
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_core_service(call):
|
||||
async def async_handle_core_service(call):
|
||||
"""Service handler for handling core services."""
|
||||
if call.service == SERVICE_HOMEASSISTANT_STOP:
|
||||
hass.async_create_task(hass.async_stop())
|
||||
return
|
||||
|
||||
try:
|
||||
errors = yield from conf_util.async_check_ha_config_file(hass)
|
||||
errors = await conf_util.async_check_ha_config_file(hass)
|
||||
except HomeAssistantError:
|
||||
return
|
||||
|
||||
@ -193,16 +140,15 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]:
|
||||
hass.services.async_register(
|
||||
ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_reload_config(call):
|
||||
async def async_handle_reload_config(call):
|
||||
"""Service handler for reloading core config."""
|
||||
try:
|
||||
conf = yield from conf_util.async_hass_config_yaml(hass)
|
||||
conf = await conf_util.async_hass_config_yaml(hass)
|
||||
except HomeAssistantError as err:
|
||||
_LOGGER.error(err)
|
||||
return
|
||||
|
||||
yield from conf_util.async_process_ha_core_config(
|
||||
await conf_util.async_process_ha_core_config(
|
||||
hass, conf.get(ha.DOMAIN) or {})
|
||||
|
||||
hass.services.async_register(
|
||||
|
@ -4,7 +4,6 @@ This component provides basic support for Abode Home Security system.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/abode/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from functools import partial
|
||||
from requests.exceptions import HTTPError, ConnectTimeout
|
||||
@ -261,8 +260,7 @@ class AbodeDevice(Entity):
|
||||
self._data = data
|
||||
self._device = device
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
self.hass.async_add_job(
|
||||
self._data.abode.events.add_device_callback,
|
||||
@ -308,8 +306,7 @@ class AbodeAutomation(Entity):
|
||||
self._automation = automation
|
||||
self._event = event
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
if self._event:
|
||||
self.hass.async_add_job(
|
||||
|
@ -4,7 +4,6 @@ Component to interface with an alarm control panel.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel/
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
@ -14,7 +13,6 @@ from homeassistant.const import (
|
||||
ATTR_CODE, ATTR_CODE_FORMAT, ATTR_ENTITY_ID, SERVICE_ALARM_TRIGGER,
|
||||
SERVICE_ALARM_DISARM, SERVICE_ALARM_ARM_HOME, SERVICE_ALARM_ARM_AWAY,
|
||||
SERVICE_ALARM_ARM_NIGHT, SERVICE_ALARM_ARM_CUSTOM_BYPASS)
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.entity import Entity
|
||||
@ -32,85 +30,12 @@ ALARM_SERVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_disarm(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for disarm."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_DISARM, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_home(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm home."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_HOME, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_away(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm away."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_AWAY, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_night(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm night."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_NIGHT, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_trigger(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for trigger."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_TRIGGER, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def alarm_arm_custom_bypass(hass, code=None, entity_id=None):
|
||||
"""Send the alarm the command for arm custom bypass."""
|
||||
data = {}
|
||||
if code:
|
||||
data[ATTR_CODE] = code
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_ALARM_ARM_CUSTOM_BYPASS, data)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Track states and offer events for sensors."""
|
||||
component = hass.data[DOMAIN] = EntityComponent(
|
||||
logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL)
|
||||
|
||||
yield from component.async_setup(config)
|
||||
await component.async_setup(config)
|
||||
|
||||
component.async_register_entity_service(
|
||||
SERVICE_ALARM_DISARM, ALARM_SERVICE_SCHEMA,
|
||||
|
@ -4,7 +4,6 @@ Support for AlarmDecoder-based alarm control panels (Honeywell/DSC).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.alarmdecoder/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -59,8 +58,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
self._ready = None
|
||||
self._zone_bypassed = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
SIGNAL_PANEL_MESSAGE, self._message_callback)
|
||||
|
@ -4,7 +4,6 @@ Interfaces with Alarm.com alarm control panels.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.alarmdotcom/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
|
||||
@ -32,8 +31,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up a Alarm.com control panel."""
|
||||
name = config.get(CONF_NAME)
|
||||
@ -42,7 +40,7 @@ def async_setup_platform(hass, config, async_add_entities,
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
alarmdotcom = AlarmDotCom(hass, name, code, username, password)
|
||||
yield from alarmdotcom.async_login()
|
||||
await alarmdotcom.async_login()
|
||||
async_add_entities([alarmdotcom])
|
||||
|
||||
|
||||
@ -63,15 +61,13 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
self._alarm = Alarmdotcom(
|
||||
username, password, self._websession, hass.loop)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_login(self):
|
||||
async def async_login(self):
|
||||
"""Login to Alarm.com."""
|
||||
yield from self._alarm.async_login()
|
||||
await self._alarm.async_login()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Fetch the latest state."""
|
||||
yield from self._alarm.async_update()
|
||||
await self._alarm.async_update()
|
||||
return self._alarm.state
|
||||
|
||||
@property
|
||||
@ -106,23 +102,20 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
'sensor_status': self._alarm.sensor_status
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if self._validate_code(code):
|
||||
yield from self._alarm.async_alarm_disarm()
|
||||
await self._alarm.async_alarm_disarm()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm hom command."""
|
||||
if self._validate_code(code):
|
||||
yield from self._alarm.async_alarm_arm_home()
|
||||
await self._alarm.async_alarm_arm_home()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if self._validate_code(code):
|
||||
yield from self._alarm.async_alarm_arm_away()
|
||||
await self._alarm.async_alarm_arm_away()
|
||||
|
||||
def _validate_code(self, code):
|
||||
"""Validate given code."""
|
||||
|
86
homeassistant/components/alarm_control_panel/blink.py
Normal file
86
homeassistant/components/alarm_control_panel/blink.py
Normal 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()
|
@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.egardia/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import requests
|
||||
@ -61,8 +60,7 @@ class EgardiaAlarm(alarm.AlarmControlPanel):
|
||||
self._rs_codes = rs_codes
|
||||
self._rs_port = rs_port
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Add Egardiaserver callback if enabled."""
|
||||
if self._rs_enabled:
|
||||
_LOGGER.debug("Registering callback to Egardiaserver")
|
||||
|
@ -4,7 +4,6 @@ Support for Envisalink-based alarm control panels (Honeywell/DSC).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.envisalink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -32,8 +31,7 @@ ALARM_KEYPRESS_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Perform the setup for Envisalink alarm panels."""
|
||||
configured_partitions = discovery_info['partitions']
|
||||
@ -88,8 +86,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
_LOGGER.debug("Setting up alarm: %s", alarm_name)
|
||||
super().__init__(alarm_name, info, controller)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_KEYPAD_UPDATE, self._update_callback)
|
||||
@ -128,8 +125,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
state = STATE_ALARM_DISARMED
|
||||
return state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if code:
|
||||
self.hass.data[DATA_EVL].disarm_partition(
|
||||
@ -138,8 +134,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self.hass.data[DATA_EVL].disarm_partition(
|
||||
str(self._code), self._partition_number)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
self.hass.data[DATA_EVL].arm_stay_partition(
|
||||
@ -148,8 +143,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self.hass.data[DATA_EVL].arm_stay_partition(
|
||||
str(self._code), self._partition_number)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if code:
|
||||
self.hass.data[DATA_EVL].arm_away_partition(
|
||||
@ -158,8 +152,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
self.hass.data[DATA_EVL].arm_away_partition(
|
||||
str(self._code), self._partition_number)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_trigger(self, code=None):
|
||||
async def async_alarm_trigger(self, code=None):
|
||||
"""Alarm trigger command. Will be used to trigger a panic alarm."""
|
||||
self.hass.data[DATA_EVL].panic_alarm(self._panic_type)
|
||||
|
||||
|
@ -4,7 +4,6 @@ Support for manual alarms controllable via MQTT.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.manual_mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import copy
|
||||
import datetime
|
||||
import logging
|
||||
@ -363,8 +362,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
return mqtt.async_subscribe(
|
||||
self.hass, self._command_topic, message_received, self._qos)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_state_changed_listener(self, entity_id, old_state, new_state):
|
||||
async def _async_state_changed_listener(self, entity_id, old_state,
|
||||
new_state):
|
||||
"""Publish state change to MQTT."""
|
||||
mqtt.async_publish(
|
||||
self.hass, self._state_topic, new_state.state, self._qos, True)
|
||||
|
@ -4,7 +4,6 @@ This platform enables the possibility to control a MQTT alarm.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import re
|
||||
|
||||
@ -18,10 +17,13 @@ from homeassistant.const import (
|
||||
STATE_ALARM_PENDING, STATE_ALARM_TRIGGERED, STATE_UNKNOWN,
|
||||
CONF_NAME, CONF_CODE)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC,
|
||||
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
|
||||
CONF_RETAIN, MqttAvailability)
|
||||
ATTR_DISCOVERY_HASH, CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC,
|
||||
CONF_COMMAND_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE,
|
||||
CONF_QOS, CONF_RETAIN, MqttAvailability, MqttDiscoveryUpdate)
|
||||
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -46,13 +48,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the MQTT Alarm Control Panel platform."""
|
||||
if discovery_info is not None:
|
||||
config = PLATFORM_SCHEMA(discovery_info)
|
||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||
async_add_entities, discovery_info=None):
|
||||
"""Set up MQTT alarm control panel through configuration.yaml."""
|
||||
await _async_setup_entity(hass, config, async_add_entities)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up MQTT alarm control panel dynamically through MQTT discovery."""
|
||||
async def async_discover(discovery_payload):
|
||||
"""Discover and add an MQTT alarm control panel."""
|
||||
config = PLATFORM_SCHEMA(discovery_payload)
|
||||
await _async_setup_entity(hass, config, async_add_entities,
|
||||
discovery_payload[ATTR_DISCOVERY_HASH])
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, MQTT_DISCOVERY_NEW.format(alarm.DOMAIN, 'mqtt'),
|
||||
async_discover)
|
||||
|
||||
|
||||
async def _async_setup_entity(hass, config, async_add_entities,
|
||||
discovery_hash=None):
|
||||
"""Set up the MQTT Alarm Control Panel platform."""
|
||||
async_add_entities([MqttAlarm(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_STATE_TOPIC),
|
||||
@ -65,18 +82,22 @@ def async_setup_platform(hass, config, async_add_entities,
|
||||
config.get(CONF_CODE),
|
||||
config.get(CONF_AVAILABILITY_TOPIC),
|
||||
config.get(CONF_PAYLOAD_AVAILABLE),
|
||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE))])
|
||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
|
||||
discovery_hash,)])
|
||||
|
||||
|
||||
class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
class MqttAlarm(MqttAvailability, MqttDiscoveryUpdate,
|
||||
alarm.AlarmControlPanel):
|
||||
"""Representation of a MQTT alarm status."""
|
||||
|
||||
def __init__(self, name, state_topic, command_topic, qos, retain,
|
||||
payload_disarm, payload_arm_home, payload_arm_away, code,
|
||||
availability_topic, payload_available, payload_not_available):
|
||||
availability_topic, payload_available, payload_not_available,
|
||||
discovery_hash):
|
||||
"""Init the MQTT Alarm Control Panel."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
payload_not_available)
|
||||
MqttAvailability.__init__(self, availability_topic, qos,
|
||||
payload_available, payload_not_available)
|
||||
MqttDiscoveryUpdate.__init__(self, discovery_hash)
|
||||
self._state = STATE_UNKNOWN
|
||||
self._name = name
|
||||
self._state_topic = state_topic
|
||||
@ -87,11 +108,12 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self._payload_arm_home = payload_arm_home
|
||||
self._payload_arm_away = payload_arm_away
|
||||
self._code = code
|
||||
self._discovery_hash = discovery_hash
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe mqtt events."""
|
||||
yield from super().async_added_to_hass()
|
||||
await MqttAvailability.async_added_to_hass(self)
|
||||
await MqttDiscoveryUpdate.async_added_to_hass(self)
|
||||
|
||||
@callback
|
||||
def message_received(topic, payload, qos):
|
||||
@ -104,7 +126,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self._state = payload
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
yield from mqtt.async_subscribe(
|
||||
await mqtt.async_subscribe(
|
||||
self.hass, self._state_topic, message_received, self._qos)
|
||||
|
||||
@property
|
||||
@ -131,8 +153,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command.
|
||||
|
||||
This method is a coroutine.
|
||||
@ -143,8 +164,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self.hass, self._command_topic, self._payload_disarm, self._qos,
|
||||
self._retain)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command.
|
||||
|
||||
This method is a coroutine.
|
||||
@ -155,8 +175,7 @@ class MqttAlarm(MqttAvailability, alarm.AlarmControlPanel):
|
||||
self.hass, self._command_topic, self._payload_arm_home, self._qos,
|
||||
self._retain)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command.
|
||||
|
||||
This method is a coroutine.
|
||||
|
@ -4,7 +4,6 @@ Support for Satel Integra alarm, using ETHM module: https://www.satel.pl/en/ .
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.satel_integra/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
@ -18,8 +17,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['satel_integra']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up for Satel Integra alarm panels."""
|
||||
if not discovery_info:
|
||||
@ -39,8 +37,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
self._state = None
|
||||
self._arm_home_mode = arm_home_mode
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_PANEL_MESSAGE, self._message_callback)
|
||||
@ -74,21 +71,18 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_disarm(self, code=None):
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].disarm(code)
|
||||
await self.hass.data[DATA_SATEL].disarm(code)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_away(self, code=None):
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].arm(code)
|
||||
await self.hass.data[DATA_SATEL].arm(code)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_alarm_arm_home(self, code=None):
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if code:
|
||||
yield from self.hass.data[DATA_SATEL].arm(
|
||||
await self.hass.data[DATA_SATEL].arm(
|
||||
code, self._arm_home_mode)
|
||||
|
@ -12,75 +12,100 @@ import voluptuous as vol
|
||||
from homeassistant.components.alarm_control_panel import (
|
||||
PLATFORM_SCHEMA, AlarmControlPanel)
|
||||
from homeassistant.const import (
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME,
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED, STATE_UNKNOWN)
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
CONF_CODE, CONF_NAME, CONF_PASSWORD, CONF_USERNAME, STATE_ALARM_ARMED_AWAY,
|
||||
STATE_ALARM_ARMED_HOME, STATE_ALARM_DISARMED)
|
||||
from homeassistant.helpers import aiohttp_client, config_validation as cv
|
||||
from homeassistant.util.json import load_json, save_json
|
||||
|
||||
REQUIREMENTS = ['simplisafe-python==2.0.2']
|
||||
REQUIREMENTS = ['simplisafe-python==3.1.2']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEFAULT_NAME = 'SimpliSafe'
|
||||
|
||||
ATTR_ALARM_ACTIVE = "alarm_active"
|
||||
ATTR_TEMPERATURE = "temperature"
|
||||
|
||||
DATA_FILE = '.simplisafe'
|
||||
|
||||
DEFAULT_NAME = 'SimpliSafe'
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Required(CONF_USERNAME): cv.string,
|
||||
vol.Optional(CONF_CODE): cv.string,
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
|
||||
vol.Optional(CONF_CODE): cv.string,
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the SimpliSafe platform."""
|
||||
from simplipy.api import SimpliSafeApiInterface, SimpliSafeAPIException
|
||||
from simplipy import API
|
||||
from simplipy.errors import SimplipyError
|
||||
|
||||
username = config[CONF_USERNAME]
|
||||
password = config[CONF_PASSWORD]
|
||||
name = config.get(CONF_NAME)
|
||||
code = config.get(CONF_CODE)
|
||||
username = config.get(CONF_USERNAME)
|
||||
password = config.get(CONF_PASSWORD)
|
||||
|
||||
websession = aiohttp_client.async_get_clientsession(hass)
|
||||
|
||||
config_data = await hass.async_add_executor_job(
|
||||
load_json, hass.config.path(DATA_FILE))
|
||||
|
||||
try:
|
||||
simplisafe = SimpliSafeApiInterface(username, password)
|
||||
except SimpliSafeAPIException:
|
||||
_LOGGER.error("Failed to set up SimpliSafe")
|
||||
if config_data:
|
||||
try:
|
||||
simplisafe = await API.login_via_token(
|
||||
config_data['refresh_token'], websession)
|
||||
_LOGGER.debug('Logging in with refresh token')
|
||||
except SimplipyError:
|
||||
_LOGGER.info('Refresh token expired; attempting credentials')
|
||||
simplisafe = await API.login_via_credentials(
|
||||
username, password, websession)
|
||||
else:
|
||||
simplisafe = await API.login_via_credentials(
|
||||
username, password, websession)
|
||||
_LOGGER.debug('Logging in with credentials')
|
||||
except SimplipyError as err:
|
||||
_LOGGER.error("There was an error during setup: %s", err)
|
||||
return
|
||||
|
||||
systems = []
|
||||
config_data = {'refresh_token': simplisafe.refresh_token}
|
||||
await hass.async_add_executor_job(
|
||||
save_json, hass.config.path(DATA_FILE), config_data)
|
||||
|
||||
for system in simplisafe.get_systems():
|
||||
systems.append(SimpliSafeAlarm(system, name, code))
|
||||
|
||||
add_entities(systems)
|
||||
systems = await simplisafe.get_systems()
|
||||
async_add_entities(
|
||||
[SimpliSafeAlarm(system, name, code) for system in systems], True)
|
||||
|
||||
|
||||
class SimpliSafeAlarm(AlarmControlPanel):
|
||||
"""Representation of a SimpliSafe alarm."""
|
||||
|
||||
def __init__(self, simplisafe, name, code):
|
||||
def __init__(self, system, name, code):
|
||||
"""Initialize the SimpliSafe alarm."""
|
||||
self.simplisafe = simplisafe
|
||||
self._name = name
|
||||
self._attrs = {}
|
||||
self._code = str(code) if code else None
|
||||
self._name = name
|
||||
self._system = system
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return the unique ID."""
|
||||
return self.simplisafe.location_id
|
||||
return self._system.system_id
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
if self._name is not None:
|
||||
if self._name:
|
||||
return self._name
|
||||
return 'Alarm {}'.format(self.simplisafe.location_id)
|
||||
return 'Alarm {}'.format(self._system.system_id)
|
||||
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
if self._code is None:
|
||||
if not self._code:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
@ -89,53 +114,12 @@ class SimpliSafeAlarm(AlarmControlPanel):
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
status = self.simplisafe.state
|
||||
if status.lower() == 'off':
|
||||
state = STATE_ALARM_DISARMED
|
||||
elif status.lower() == 'home' or status.lower() == 'home_count':
|
||||
state = STATE_ALARM_ARMED_HOME
|
||||
elif (status.lower() == 'away' or status.lower() == 'exitDelay' or
|
||||
status.lower() == 'away_count'):
|
||||
state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
state = STATE_UNKNOWN
|
||||
return state
|
||||
return self._state
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes."""
|
||||
attributes = {}
|
||||
|
||||
attributes[ATTR_ALARM_ACTIVE] = self.simplisafe.alarm_active
|
||||
if self.simplisafe.temperature is not None:
|
||||
attributes[ATTR_TEMPERATURE] = self.simplisafe.temperature
|
||||
|
||||
return attributes
|
||||
|
||||
def update(self):
|
||||
"""Update alarm status."""
|
||||
self.simplisafe.update()
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
self.simplisafe.set_state('off')
|
||||
_LOGGER.info("SimpliSafe alarm disarming")
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
self.simplisafe.set_state('home')
|
||||
_LOGGER.info("SimpliSafe alarm arming home")
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
self.simplisafe.set_state('away')
|
||||
_LOGGER.info("SimpliSafe alarm arming away")
|
||||
return self._attrs
|
||||
|
||||
def _validate_code(self, code, state):
|
||||
"""Validate given code."""
|
||||
@ -143,3 +127,46 @@ class SimpliSafeAlarm(AlarmControlPanel):
|
||||
if not check:
|
||||
_LOGGER.warning("Wrong code entered for %s", state)
|
||||
return check
|
||||
|
||||
async def async_alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
if not self._validate_code(code, 'disarming'):
|
||||
return
|
||||
|
||||
await self._system.set_off()
|
||||
|
||||
async def async_alarm_arm_home(self, code=None):
|
||||
"""Send arm home command."""
|
||||
if not self._validate_code(code, 'arming home'):
|
||||
return
|
||||
|
||||
await self._system.set_home()
|
||||
|
||||
async def async_alarm_arm_away(self, code=None):
|
||||
"""Send arm away command."""
|
||||
if not self._validate_code(code, 'arming away'):
|
||||
return
|
||||
|
||||
await self._system.set_away()
|
||||
|
||||
async def async_update(self):
|
||||
"""Update alarm status."""
|
||||
await self._system.update()
|
||||
|
||||
if self._system.state == self._system.SystemStates.off:
|
||||
self._state = STATE_ALARM_DISARMED
|
||||
elif self._system.state in (
|
||||
self._system.SystemStates.home,
|
||||
self._system.SystemStates.home_count):
|
||||
self._state = STATE_ALARM_ARMED_HOME
|
||||
elif self._system.state in (
|
||||
self._system.SystemStates.away,
|
||||
self._system.SystemStates.away_count,
|
||||
self._system.SystemStates.exit_delay):
|
||||
self._state = STATE_ALARM_ARMED_AWAY
|
||||
else:
|
||||
self._state = None
|
||||
|
||||
self._attrs[ATTR_ALARM_ACTIVE] = self._system.alarm_going_off
|
||||
if self._system.temperature:
|
||||
self._attrs[ATTR_TEMPERATURE] = self._system.temperature
|
||||
|
@ -9,8 +9,7 @@ import logging
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.spc import (
|
||||
ATTR_DISCOVER_AREAS, DATA_API, SIGNAL_UPDATE_ALARM)
|
||||
from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_ALARM)
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_NIGHT,
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_TRIGGERED)
|
||||
@ -37,12 +36,11 @@ def _get_alarm_state(area):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the SPC alarm control panel platform."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_AREAS] is None):
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
async_add_entities([SpcAlarm(area=area, api=hass.data[DATA_API])
|
||||
for area in discovery_info[ATTR_DISCOVER_AREAS]])
|
||||
api = hass.data[DATA_API]
|
||||
async_add_entities([SpcAlarm(area=area, api=api)
|
||||
for area in api.areas.values()])
|
||||
|
||||
|
||||
class SpcAlarm(alarm.AlarmControlPanel):
|
||||
|
@ -4,7 +4,6 @@ Interfaces with Wink Cameras.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.wink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import homeassistant.components.alarm_control_panel as alarm
|
||||
@ -38,8 +37,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
class WinkCameraDevice(WinkDevice, alarm.AlarmControlPanel):
|
||||
"""Representation a Wink camera alarm."""
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]['entities']['alarm_control_panel'].append(self)
|
||||
|
||||
|
@ -10,7 +10,8 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.notify import (
|
||||
ATTR_MESSAGE, DOMAIN as DOMAIN_NOTIFY)
|
||||
from homeassistant.const import (
|
||||
CONF_ENTITY_ID, STATE_IDLE, CONF_NAME, CONF_STATE, STATE_ON, STATE_OFF,
|
||||
SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID)
|
||||
@ -59,53 +60,12 @@ def is_on(hass, entity_id):
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
def turn_on(hass, entity_id):
|
||||
"""Reset the alert."""
|
||||
hass.add_job(async_turn_on, hass, entity_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_turn_on(hass, entity_id):
|
||||
"""Async reset the alert."""
|
||||
data = {ATTR_ENTITY_ID: entity_id}
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data))
|
||||
|
||||
|
||||
def turn_off(hass, entity_id):
|
||||
"""Acknowledge alert."""
|
||||
hass.add_job(async_turn_off, hass, entity_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_turn_off(hass, entity_id):
|
||||
"""Async acknowledge the alert."""
|
||||
data = {ATTR_ENTITY_ID: entity_id}
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data))
|
||||
|
||||
|
||||
def toggle(hass, entity_id):
|
||||
"""Toggle acknowledgement of alert."""
|
||||
hass.add_job(async_toggle, hass, entity_id)
|
||||
|
||||
|
||||
@callback
|
||||
def async_toggle(hass, entity_id):
|
||||
"""Async toggle acknowledgement of alert."""
|
||||
data = {ATTR_ENTITY_ID: entity_id}
|
||||
hass.async_create_task(
|
||||
hass.services.async_call(DOMAIN, SERVICE_TOGGLE, data))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Alert component."""
|
||||
alerts = config.get(DOMAIN)
|
||||
all_alerts = {}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_alert_service(service_call):
|
||||
async def async_handle_alert_service(service_call):
|
||||
"""Handle calls to alert services."""
|
||||
alert_ids = service.extract_entity_ids(hass, service_call)
|
||||
|
||||
@ -113,11 +73,11 @@ def async_setup(hass, config):
|
||||
alert = all_alerts[alert_id]
|
||||
alert.async_set_context(service_call.context)
|
||||
if service_call.service == SERVICE_TURN_ON:
|
||||
yield from alert.async_turn_on()
|
||||
await alert.async_turn_on()
|
||||
elif service_call.service == SERVICE_TOGGLE:
|
||||
yield from alert.async_toggle()
|
||||
await alert.async_toggle()
|
||||
else:
|
||||
yield from alert.async_turn_off()
|
||||
await alert.async_turn_off()
|
||||
|
||||
# Setup alerts
|
||||
for entity_id, alert in alerts.items():
|
||||
@ -141,7 +101,7 @@ def async_setup(hass, config):
|
||||
|
||||
tasks = [alert.async_update_ha_state() for alert in all_alerts.values()]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
return True
|
||||
|
||||
@ -196,17 +156,15 @@ class Alert(ToggleEntity):
|
||||
"""Hide the alert when it is not firing."""
|
||||
return not self._can_ack or not self._firing
|
||||
|
||||
@asyncio.coroutine
|
||||
def watched_entity_change(self, entity, from_state, to_state):
|
||||
async def watched_entity_change(self, entity, from_state, to_state):
|
||||
"""Determine if the alert should start or stop."""
|
||||
_LOGGER.debug("Watched entity (%s) has changed", entity)
|
||||
if to_state.state == self._alert_state and not self._firing:
|
||||
yield from self.begin_alerting()
|
||||
await self.begin_alerting()
|
||||
if to_state.state != self._alert_state and self._firing:
|
||||
yield from self.end_alerting()
|
||||
await self.end_alerting()
|
||||
|
||||
@asyncio.coroutine
|
||||
def begin_alerting(self):
|
||||
async def begin_alerting(self):
|
||||
"""Begin the alert procedures."""
|
||||
_LOGGER.debug("Beginning Alert: %s", self._name)
|
||||
self._ack = False
|
||||
@ -214,25 +172,23 @@ class Alert(ToggleEntity):
|
||||
self._next_delay = 0
|
||||
|
||||
if not self._skip_first:
|
||||
yield from self._notify()
|
||||
await self._notify()
|
||||
else:
|
||||
yield from self._schedule_notify()
|
||||
await self._schedule_notify()
|
||||
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def end_alerting(self):
|
||||
async def end_alerting(self):
|
||||
"""End the alert procedures."""
|
||||
_LOGGER.debug("Ending Alert: %s", self._name)
|
||||
self._cancel()
|
||||
self._ack = False
|
||||
self._firing = False
|
||||
if self._done_message and self._send_done_message:
|
||||
yield from self._notify_done_message()
|
||||
await self._notify_done_message()
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _schedule_notify(self):
|
||||
async def _schedule_notify(self):
|
||||
"""Schedule a notification."""
|
||||
delay = self._delay[self._next_delay]
|
||||
next_msg = datetime.now() + delay
|
||||
@ -240,8 +196,7 @@ class Alert(ToggleEntity):
|
||||
event.async_track_point_in_time(self.hass, self._notify, next_msg)
|
||||
self._next_delay = min(self._next_delay + 1, len(self._delay) - 1)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _notify(self, *args):
|
||||
async def _notify(self, *args):
|
||||
"""Send the alert notification."""
|
||||
if not self._firing:
|
||||
return
|
||||
@ -250,36 +205,32 @@ class Alert(ToggleEntity):
|
||||
_LOGGER.info("Alerting: %s", self._name)
|
||||
self._send_done_message = True
|
||||
for target in self._notifiers:
|
||||
yield from self.hass.services.async_call(
|
||||
'notify', target, {'message': self._name})
|
||||
yield from self._schedule_notify()
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._name})
|
||||
await self._schedule_notify()
|
||||
|
||||
@asyncio.coroutine
|
||||
def _notify_done_message(self, *args):
|
||||
async def _notify_done_message(self, *args):
|
||||
"""Send notification of complete alert."""
|
||||
_LOGGER.info("Alerting: %s", self._done_message)
|
||||
self._send_done_message = False
|
||||
for target in self._notifiers:
|
||||
yield from self.hass.services.async_call(
|
||||
'notify', target, {'message': self._done_message})
|
||||
await self.hass.services.async_call(
|
||||
DOMAIN_NOTIFY, target, {ATTR_MESSAGE: self._done_message})
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_on(self, **kwargs):
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Async Unacknowledge alert."""
|
||||
_LOGGER.debug("Reset Alert: %s", self._name)
|
||||
self._ack = False
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_turn_off(self, **kwargs):
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Async Acknowledge alert."""
|
||||
_LOGGER.debug("Acknowledged Alert: %s", self._name)
|
||||
self._ack = True
|
||||
yield from self.async_update_ha_state()
|
||||
await self.async_update_ha_state()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_toggle(self, **kwargs):
|
||||
async def async_toggle(self, **kwargs):
|
||||
"""Async toggle alert."""
|
||||
if self._ack:
|
||||
return self.async_turn_on()
|
||||
return self.async_turn_off()
|
||||
return await self.async_turn_on()
|
||||
return await self.async_turn_off()
|
||||
|
@ -4,7 +4,6 @@ Support for Alexa skill service end point.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -53,8 +52,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Activate Alexa component."""
|
||||
config = config.get(DOMAIN, {})
|
||||
flash_briefings_config = config.get(CONF_FLASH_BRIEFINGS)
|
||||
|
@ -4,7 +4,6 @@ Support for Alexa skill service end point.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/alexa/
|
||||
"""
|
||||
import asyncio
|
||||
import enum
|
||||
import logging
|
||||
|
||||
@ -59,16 +58,15 @@ class AlexaIntentsView(http.HomeAssistantView):
|
||||
url = INTENTS_API_ENDPOINT
|
||||
name = 'api:alexa'
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
async def post(self, request):
|
||||
"""Handle Alexa."""
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
message = await request.json()
|
||||
|
||||
_LOGGER.debug("Received Alexa request: %s", message)
|
||||
|
||||
try:
|
||||
response = yield from async_handle_message(hass, message)
|
||||
response = await async_handle_message(hass, message)
|
||||
return b'' if response is None else self.json(response)
|
||||
except UnknownRequest as err:
|
||||
_LOGGER.warning(str(err))
|
||||
@ -101,8 +99,7 @@ def intent_error_response(hass, message, error):
|
||||
return alexa_response.as_dict()
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_handle_message(hass, message):
|
||||
async def async_handle_message(hass, message):
|
||||
"""Handle an Alexa intent.
|
||||
|
||||
Raises:
|
||||
@ -120,20 +117,18 @@ def async_handle_message(hass, message):
|
||||
if not handler:
|
||||
raise UnknownRequest('Received unknown request {}'.format(req_type))
|
||||
|
||||
return (yield from handler(hass, message))
|
||||
return await handler(hass, message)
|
||||
|
||||
|
||||
@HANDLERS.register('SessionEndedRequest')
|
||||
@asyncio.coroutine
|
||||
def async_handle_session_end(hass, message):
|
||||
async def async_handle_session_end(hass, message):
|
||||
"""Handle a session end request."""
|
||||
return None
|
||||
|
||||
|
||||
@HANDLERS.register('IntentRequest')
|
||||
@HANDLERS.register('LaunchRequest')
|
||||
@asyncio.coroutine
|
||||
def async_handle_intent(hass, message):
|
||||
async def async_handle_intent(hass, message):
|
||||
"""Handle an intent request.
|
||||
|
||||
Raises:
|
||||
@ -153,7 +148,7 @@ def async_handle_intent(hass, message):
|
||||
else:
|
||||
intent_name = alexa_intent_info['name']
|
||||
|
||||
intent_response = yield from intent.async_handle(
|
||||
intent_response = await intent.async_handle(
|
||||
hass, DOMAIN, intent_name,
|
||||
{key: {'value': value} for key, value
|
||||
in alexa_response.variables.items()})
|
||||
|
@ -1,5 +1,4 @@
|
||||
"""Support for alexa Smart Home Skill API."""
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
from datetime import datetime
|
||||
@ -695,8 +694,7 @@ class SmartHomeView(http.HomeAssistantView):
|
||||
"""Initialize."""
|
||||
self.smart_home_config = smart_home_config
|
||||
|
||||
@asyncio.coroutine
|
||||
def post(self, request):
|
||||
async def post(self, request):
|
||||
"""Handle Alexa Smart Home requests.
|
||||
|
||||
The Smart Home API requires the endpoint to be implemented in AWS
|
||||
@ -704,11 +702,11 @@ class SmartHomeView(http.HomeAssistantView):
|
||||
the response.
|
||||
"""
|
||||
hass = request.app['hass']
|
||||
message = yield from request.json()
|
||||
message = await request.json()
|
||||
|
||||
_LOGGER.debug("Received Alexa Smart Home request: %s", message)
|
||||
|
||||
response = yield from async_handle_message(
|
||||
response = await async_handle_message(
|
||||
hass, self.smart_home_config, message)
|
||||
_LOGGER.debug("Sending Alexa Smart Home response: %s", response)
|
||||
return b'' if response is None else self.json(response)
|
||||
|
@ -149,16 +149,14 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the IP Webcam component."""
|
||||
from pydroid_ipcam import PyDroidIPCam
|
||||
|
||||
webcams = hass.data[DATA_IP_WEBCAM] = {}
|
||||
websession = async_get_clientsession(hass)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_ipcamera(cam_config):
|
||||
async def async_setup_ipcamera(cam_config):
|
||||
"""Set up an IP camera."""
|
||||
host = cam_config[CONF_HOST]
|
||||
username = cam_config.get(CONF_USERNAME)
|
||||
@ -188,16 +186,15 @@ def async_setup(hass, config):
|
||||
if motion is None:
|
||||
motion = 'motion_active' in cam.enabled_sensors
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update_data(now):
|
||||
async def async_update_data(now):
|
||||
"""Update data from IP camera in SCAN_INTERVAL."""
|
||||
yield from cam.update()
|
||||
await cam.update()
|
||||
async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host)
|
||||
|
||||
async_track_point_in_utc_time(
|
||||
hass, async_update_data, utcnow() + interval)
|
||||
|
||||
yield from async_update_data(None)
|
||||
await async_update_data(None)
|
||||
|
||||
# Load platforms
|
||||
webcams[host] = cam
|
||||
@ -242,7 +239,7 @@ def async_setup(hass, config):
|
||||
|
||||
tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
return True
|
||||
|
||||
@ -255,8 +252,7 @@ class AndroidIPCamEntity(Entity):
|
||||
self._host = host
|
||||
self._ipcam = ipcam
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register update dispatcher."""
|
||||
@callback
|
||||
def async_ipcam_update(host):
|
||||
|
@ -77,14 +77,13 @@ def request_configuration(hass, config, atv, credentials):
|
||||
"""Request configuration steps from the user."""
|
||||
configurator = hass.components.configurator
|
||||
|
||||
@asyncio.coroutine
|
||||
def configuration_callback(callback_data):
|
||||
async def configuration_callback(callback_data):
|
||||
"""Handle the submitted configuration."""
|
||||
from pyatv import exceptions
|
||||
pin = callback_data.get('pin')
|
||||
|
||||
try:
|
||||
yield from atv.airplay.finish_authentication(pin)
|
||||
await atv.airplay.finish_authentication(pin)
|
||||
hass.components.persistent_notification.async_create(
|
||||
'Authentication succeeded!<br /><br />Add the following '
|
||||
'to credentials: in your apple_tv configuration:<br /><br />'
|
||||
@ -108,11 +107,10 @@ def request_configuration(hass, config, atv, credentials):
|
||||
)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def scan_for_apple_tvs(hass):
|
||||
async def scan_for_apple_tvs(hass):
|
||||
"""Scan for devices and present a notification of the ones found."""
|
||||
import pyatv
|
||||
atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
|
||||
atvs = await pyatv.scan_for_apple_tvs(hass.loop, timeout=3)
|
||||
|
||||
devices = []
|
||||
for atv in atvs:
|
||||
@ -132,14 +130,12 @@ def scan_for_apple_tvs(hass):
|
||||
notification_id=NOTIFICATION_SCAN_ID)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the Apple TV component."""
|
||||
if DATA_APPLE_TV not in hass.data:
|
||||
hass.data[DATA_APPLE_TV] = {}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_service_handler(service):
|
||||
async def async_service_handler(service):
|
||||
"""Handle service calls."""
|
||||
entity_ids = service.data.get(ATTR_ENTITY_ID)
|
||||
|
||||
@ -158,17 +154,16 @@ def async_setup(hass, config):
|
||||
continue
|
||||
|
||||
atv = device.atv
|
||||
credentials = yield from atv.airplay.generate_credentials()
|
||||
yield from atv.airplay.load_credentials(credentials)
|
||||
credentials = await atv.airplay.generate_credentials()
|
||||
await atv.airplay.load_credentials(credentials)
|
||||
_LOGGER.debug('Generated new credentials: %s', credentials)
|
||||
yield from atv.airplay.start_authentication()
|
||||
await atv.airplay.start_authentication()
|
||||
hass.async_add_job(request_configuration,
|
||||
hass, config, atv, credentials)
|
||||
|
||||
@asyncio.coroutine
|
||||
def atv_discovered(service, info):
|
||||
async def atv_discovered(service, info):
|
||||
"""Set up an Apple TV that was auto discovered."""
|
||||
yield from _setup_atv(hass, {
|
||||
await _setup_atv(hass, {
|
||||
CONF_NAME: info['name'],
|
||||
CONF_HOST: info['host'],
|
||||
CONF_LOGIN_ID: info['properties']['hG'],
|
||||
@ -179,7 +174,7 @@ def async_setup(hass, config):
|
||||
|
||||
tasks = [_setup_atv(hass, conf) for conf in config.get(DOMAIN, [])]
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
await asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SCAN, async_service_handler,
|
||||
@ -192,8 +187,7 @@ def async_setup(hass, config):
|
||||
return True
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def _setup_atv(hass, atv_config):
|
||||
async def _setup_atv(hass, atv_config):
|
||||
"""Set up an Apple TV."""
|
||||
import pyatv
|
||||
name = atv_config.get(CONF_NAME)
|
||||
@ -209,7 +203,7 @@ def _setup_atv(hass, atv_config):
|
||||
session = async_get_clientsession(hass)
|
||||
atv = pyatv.connect_to_apple_tv(details, hass.loop, session=session)
|
||||
if credentials:
|
||||
yield from atv.airplay.load_credentials(credentials)
|
||||
await atv.airplay.load_credentials(credentials)
|
||||
|
||||
power = AppleTVPowerManager(hass, atv, start_off)
|
||||
hass.data[DATA_APPLE_TV][host] = {
|
||||
@ -258,4 +252,4 @@ class AppleTVPowerManager:
|
||||
self.atv.push_updater.start()
|
||||
|
||||
for listener in self.listeners:
|
||||
self.hass.async_add_job(listener.async_update_ha_state())
|
||||
self.hass.async_create_task(listener.async_update_ha_state())
|
||||
|
95
homeassistant/components/aqualogic.py
Normal file
95
homeassistant/components/aqualogic.py
Normal 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
|
@ -16,7 +16,8 @@
|
||||
"description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:",
|
||||
"title": "\u00dcberpr\u00fcfe das Setup"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Benachrichtig f\u00fcr One-Time Password"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
|
@ -1,12 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Aucun service de notification disponible."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Code invalide. Veuillez essayer \u00e0 nouveau."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Veuillez s\u00e9lectionner l'un des services de notification:",
|
||||
"title": "Configurer un mot de passe \u00e0 usage unique d\u00e9livr\u00e9 par le composant notify"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Un mot de passe unique a \u00e9t\u00e9 envoy\u00e9 par **notify.{notify_service}**. Veuillez le saisir ci-dessous :"
|
||||
}
|
||||
"description": "Un mot de passe unique a \u00e9t\u00e9 envoy\u00e9 par **notify.{notify_service}**. Veuillez le saisir ci-dessous :",
|
||||
"title": "V\u00e9rifier la configuration"
|
||||
}
|
||||
},
|
||||
"title": "Notifier un mot de passe unique"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Code invalide. Veuillez essayez \u00e0 nouveau. Si cette erreur persiste, assurez-vous que l'horloge de votre syst\u00e8me Home Assistant est correcte."
|
||||
|
@ -1,5 +1,21 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Nincs el\u00e9rhet\u0151 \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1s."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "V\u00e1lassz \u00e9rtes\u00edt\u00e9si szolg\u00e1ltat\u00e1st:"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Be\u00e1ll\u00edt\u00e1s ellen\u0151rz\u00e9se"
|
||||
}
|
||||
}
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "\u00c9rv\u00e9nytelen k\u00f3d, pr\u00f3b\u00e1ld \u00fajra. Ha ez a hiba folyamatosan el\u0151fordul, akkor gy\u0151z\u0151dj meg r\u00f3la, hogy a Home Assistant rendszered \u00f3r\u00e1ja pontosan j\u00e1r."
|
||||
|
@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Geen meldingsservices beschikbaar."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Ongeldige code, probeer opnieuw."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Selecteer een van de meldingsdiensten:",
|
||||
"title": "Stel een \u00e9\u00e9nmalig wachtwoord in dat wordt afgegeven door een meldingscomponent"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Een \u00e9\u00e9nmalig wachtwoord is verzonden via **notify. {notify_service}**. Voer het hieronder in:",
|
||||
"title": "Controleer de instellingen"
|
||||
}
|
||||
},
|
||||
"title": "Eenmalig wachtwoord melden"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ongeldige code, probeer het opnieuw. Als u deze fout blijft krijgen, controleer dan of de klok van uw Home Assistant systeem correct is ingesteld."
|
||||
|
@ -13,10 +13,11 @@
|
||||
"title": "Skonfiguruj has\u0142o jednorazowe dostarczone przez komponent powiadomie\u0144"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez ** powiadom. {notify_service} **. Wpisz je poni\u017cej:",
|
||||
"description": "Has\u0142o jednorazowe zosta\u0142o wys\u0142ane przez **notify.{notify_service}**. Wpisz je poni\u017cej:",
|
||||
"title": "Sprawd\u017a konfiguracj\u0119"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "Powiadomienie z has\u0142em jednorazowym"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
|
@ -1,5 +1,24 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"notify": {
|
||||
"abort": {
|
||||
"no_available_service": "Nenhum servi\u00e7o de notifica\u00e7\u00e3o dispon\u00edvel."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor tente novamente."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Por favor, selecione um dos servi\u00e7os de notifica\u00e7\u00e3o:",
|
||||
"title": "Configurar uma palavra passe entregue pela componente de notifica\u00e7\u00e3o"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Foi enviada uma palavra passe atrav\u00e9s de **notify.{notify_service}**. Por favor, insira-a:",
|
||||
"title": "Verificar a configura\u00e7\u00e3o"
|
||||
}
|
||||
},
|
||||
"title": "Notificar palavra passe de uso \u00fanico"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "C\u00f3digo inv\u00e1lido, por favor, tente novamente. Se receber este erro constantemente, por favor, certifique-se de que o rel\u00f3gio do sistema que hospeda o Home Assistent \u00e9 preciso."
|
||||
|
@ -8,12 +8,17 @@
|
||||
"invalid_code": "Ogiltig kod, var god f\u00f6rs\u00f6k igen."
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Var god v\u00e4lj en av notifieringstj\u00e4nsterna:",
|
||||
"title": "Konfigurera ett eng\u00e5ngsl\u00f6senord levererat genom notifieringskomponenten"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Ett eng\u00e5ngsl\u00f6senord har skickats av **notify.{notify_service}**. V\u00e4nligen ange det nedan:",
|
||||
"title": "Verifiera installationen"
|
||||
}
|
||||
"title": "Verifiera inst\u00e4llningen"
|
||||
}
|
||||
},
|
||||
"title": "Meddela eng\u00e5ngsl\u00f6senord"
|
||||
},
|
||||
"totp": {
|
||||
"error": {
|
||||
"invalid_code": "Ogiltig kod, f\u00f6rs\u00f6k igen. Om du flera g\u00e5nger i rad f\u00e5r detta fel, se till att klockan i din Home Assistant \u00e4r korrekt inst\u00e4lld."
|
||||
|
@ -25,7 +25,7 @@
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u6383\u63cf\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u6383\u63cf\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
|
||||
"description": "\u6b32\u555f\u7528\u4e00\u6b21\u6027\u4e14\u5177\u6642\u6548\u6027\u7684\u5bc6\u78bc\u4e4b\u5169\u6b65\u9a5f\u9a57\u8b49\u529f\u80fd\uff0c\u8acb\u4f7f\u7528\u60a8\u7684\u9a57\u8b49 App \u6383\u7784\u4e0b\u65b9\u7684 QR code \u3002\u5018\u82e5\u60a8\u5c1a\u672a\u5b89\u88dd\u4efb\u4f55 App\uff0c\u63a8\u85a6\u60a8\u4f7f\u7528 [Google Authenticator](https://support.google.com/accounts/answer/1066447) \u6216 [Authy](https://authy.com/)\u3002\n\n{qr_code}\n\n\u65bc\u641c\u5c0b\u4e4b\u5f8c\uff0c\u8f38\u5165 App \u4e2d\u7684\u516d\u4f4d\u6578\u5b57\u9032\u884c\u8a2d\u5b9a\u9a57\u8b49\u3002\u5047\u5982\u641c\u5c0b\u51fa\u73fe\u554f\u984c\uff0c\u8acb\u624b\u52d5\u8f38\u5165\u4ee5\u4e0b\u9a57\u8b49\u78bc **`{code}`**\u3002",
|
||||
"title": "\u4f7f\u7528 TOTP \u8a2d\u5b9a\u5169\u6b65\u9a5f\u9a57\u8b49"
|
||||
}
|
||||
},
|
||||
|
@ -105,7 +105,6 @@ Home Assistant. User need to record the token in secure place.
|
||||
"id": 11,
|
||||
"type": "auth/long_lived_access_token",
|
||||
"client_name": "GPS Logger",
|
||||
"client_icon": null,
|
||||
"lifespan": 365
|
||||
}
|
||||
|
||||
@ -433,7 +432,7 @@ def websocket_current_user(
|
||||
"""Get current user."""
|
||||
enabled_modules = await hass.auth.async_get_enabled_mfa(user)
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg['id'], {
|
||||
'id': user.id,
|
||||
'name': user.name,
|
||||
@ -468,7 +467,7 @@ def websocket_create_long_lived_access_token(
|
||||
access_token = hass.auth.async_create_access_token(
|
||||
refresh_token)
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg['id'], access_token))
|
||||
|
||||
hass.async_create_task(
|
||||
@ -480,8 +479,8 @@ def websocket_create_long_lived_access_token(
|
||||
def websocket_refresh_tokens(
|
||||
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg):
|
||||
"""Return metadata of users refresh tokens."""
|
||||
current_id = connection.request.get('refresh_token_id')
|
||||
connection.to_write.put_nowait(websocket_api.result_message(msg['id'], [{
|
||||
current_id = connection.refresh_token_id
|
||||
connection.send_message(websocket_api.result_message(msg['id'], [{
|
||||
'id': refresh.id,
|
||||
'client_id': refresh.client_id,
|
||||
'client_name': refresh.client_name,
|
||||
@ -509,7 +508,7 @@ def websocket_delete_refresh_token(
|
||||
|
||||
await hass.auth.async_remove_refresh_token(refresh_token)
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(msg['id'], {}))
|
||||
|
||||
hass.async_create_task(
|
||||
|
@ -1,24 +1,13 @@
|
||||
"""Helpers to resolve client ID/secret."""
|
||||
import asyncio
|
||||
from ipaddress import ip_address
|
||||
from html.parser import HTMLParser
|
||||
from ipaddress import ip_address, ip_network
|
||||
from urllib.parse import urlparse, urljoin
|
||||
|
||||
import aiohttp
|
||||
from aiohttp.client_exceptions import ClientError
|
||||
|
||||
# IP addresses of loopback interfaces
|
||||
ALLOWED_IPS = (
|
||||
ip_address('127.0.0.1'),
|
||||
ip_address('::1'),
|
||||
)
|
||||
|
||||
# RFC1918 - Address allocation for Private Internets
|
||||
ALLOWED_NETWORKS = (
|
||||
ip_network('10.0.0.0/8'),
|
||||
ip_network('172.16.0.0/12'),
|
||||
ip_network('192.168.0.0/16'),
|
||||
)
|
||||
from homeassistant.util.network import is_local
|
||||
|
||||
|
||||
async def verify_redirect_uri(hass, client_id, redirect_uri):
|
||||
@ -185,9 +174,7 @@ def _parse_client_id(client_id):
|
||||
# Not an ip address
|
||||
pass
|
||||
|
||||
if (address is None or
|
||||
address in ALLOWED_IPS or
|
||||
any(address in network for network in ALLOWED_NETWORKS)):
|
||||
if address is None or is_local(address):
|
||||
return parts
|
||||
|
||||
raise ValueError('Hostname should be a domain name or local IP address')
|
||||
|
@ -64,7 +64,7 @@ def websocket_setup_mfa(
|
||||
if flow_id is not None:
|
||||
result = await flow_manager.async_configure(
|
||||
flow_id, msg.get('user_input'))
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg['id'], _prepare_result_json(result)))
|
||||
return
|
||||
@ -72,7 +72,7 @@ def websocket_setup_mfa(
|
||||
mfa_module_id = msg.get('mfa_module_id')
|
||||
mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id)
|
||||
if mfa_module is None:
|
||||
connection.send_message_outside(websocket_api.error_message(
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'no_module',
|
||||
'MFA module {} is not found'.format(mfa_module_id)))
|
||||
return
|
||||
@ -80,7 +80,7 @@ def websocket_setup_mfa(
|
||||
result = await flow_manager.async_init(
|
||||
mfa_module_id, data={'user_id': connection.user.id})
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg['id'], _prepare_result_json(result)))
|
||||
|
||||
@ -99,13 +99,13 @@ def websocket_depose_mfa(
|
||||
await hass.auth.async_disable_user_mfa(
|
||||
connection.user, msg['mfa_module_id'])
|
||||
except ValueError as err:
|
||||
connection.send_message_outside(websocket_api.error_message(
|
||||
connection.send_message(websocket_api.error_message(
|
||||
msg['id'], 'disable_failed',
|
||||
'Cannot disable MFA Module {}: {}'.format(
|
||||
mfa_module_id, err)))
|
||||
return
|
||||
|
||||
connection.send_message_outside(
|
||||
connection.send_message(
|
||||
websocket_api.result_message(
|
||||
msg['id'], 'done'))
|
||||
|
||||
|
@ -17,15 +17,15 @@
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Set up one-time password delivered by notify component",
|
||||
"description": "Please select one of notify service:"
|
||||
"description": "Please select one of the notification services:"
|
||||
},
|
||||
"setup": {
|
||||
"title": "Verify setup",
|
||||
"description": "A one-time password have sent by **notify.{notify_service}**. Please input it in below:"
|
||||
"description": "A one-time password has been sent via **notify.{notify_service}**. Please enter it below:"
|
||||
}
|
||||
},
|
||||
"abort": {
|
||||
"no_available_service": "No available notify services."
|
||||
"no_available_service": "No notification services available."
|
||||
},
|
||||
"error": {
|
||||
"invalid_code": "Invalid code, please try again."
|
||||
|
@ -17,7 +17,6 @@ from homeassistant.loader import bind_hass
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID)
|
||||
from homeassistant.components import logbook
|
||||
from homeassistant.exceptions import HomeAssistantError
|
||||
from homeassistant.helpers import extract_domain_configs, script, condition
|
||||
from homeassistant.helpers.entity import ToggleEntity
|
||||
@ -115,49 +114,6 @@ def is_on(hass, entity_id):
|
||||
return hass.states.is_state(entity_id, STATE_ON)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_on(hass, entity_id=None):
|
||||
"""Turn on specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_off(hass, entity_id=None):
|
||||
"""Turn off specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def toggle(hass, entity_id=None):
|
||||
"""Toggle specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TOGGLE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def trigger(hass, entity_id=None):
|
||||
"""Trigger specified automation or all."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
hass.services.call(DOMAIN, SERVICE_TRIGGER, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def reload(hass):
|
||||
"""Reload the automation from config."""
|
||||
hass.services.call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def async_reload(hass):
|
||||
"""Reload the automation from config.
|
||||
|
||||
Returns a coroutine object.
|
||||
"""
|
||||
return hass.services.async_call(DOMAIN, SERVICE_RELOAD)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the automation."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass,
|
||||
@ -412,8 +368,8 @@ def _async_get_action(hass, config, name):
|
||||
async def action(entity_id, variables, context):
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
logbook.async_log_entry(
|
||||
hass, name, 'has been triggered', DOMAIN, entity_id)
|
||||
hass.components.logbook.async_log_entry(
|
||||
name, 'has been triggered', DOMAIN, entity_id)
|
||||
await script_obj.async_run(variables, context)
|
||||
|
||||
return action
|
||||
|
@ -4,7 +4,6 @@ Offer event listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#event-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
event_type = config.get(CONF_EVENT_TYPE)
|
||||
event_data_schema = vol.Schema(
|
||||
|
@ -4,7 +4,6 @@ Offer Home Assistant core automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/components/automation/#homeassistant-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -23,8 +22,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
event = config.get(CONF_EVENT)
|
||||
|
||||
|
@ -4,7 +4,6 @@ Trigger an automation when a LiteJet switch is released.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/automation.litejet/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -33,8 +32,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
number = config.get(CONF_NUMBER)
|
||||
held_more_than = config.get(CONF_HELD_MORE_THAN)
|
||||
|
@ -4,7 +4,6 @@ Offer MQTT listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#mqtt-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import json
|
||||
|
||||
import voluptuous as vol
|
||||
@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
topic = config.get(CONF_TOPIC)
|
||||
payload = config.get(CONF_PAYLOAD)
|
||||
@ -51,6 +49,6 @@ def async_trigger(hass, config, action):
|
||||
'trigger': data
|
||||
})
|
||||
|
||||
remove = yield from mqtt.async_subscribe(
|
||||
remove = await mqtt.async_subscribe(
|
||||
hass, topic, mqtt_automation_listener)
|
||||
return remove
|
||||
|
@ -4,7 +4,6 @@ Offer numeric state listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#numeric-state-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
below = config.get(CONF_BELOW)
|
||||
|
@ -4,7 +4,6 @@ Offer state listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#state-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
}), cv.key_dependency(CONF_FOR, CONF_TO))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
from_state = config.get(CONF_FROM, MATCH_ALL)
|
||||
|
@ -4,7 +4,6 @@ Offer sun based automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#sun-trigger
|
||||
"""
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
@ -25,8 +24,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for events based on configuration."""
|
||||
event = config.get(CONF_EVENT)
|
||||
offset = config.get(CONF_OFFSET)
|
||||
|
@ -4,7 +4,6 @@ Offer template automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#template-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -23,8 +22,7 @@ TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
value_template.hass = hass
|
||||
|
@ -4,7 +4,6 @@ Offer time listening automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#time-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -29,8 +28,7 @@ TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
if CONF_AT in config:
|
||||
at_time = config.get(CONF_AT)
|
||||
|
54
homeassistant/components/automation/webhook.py
Normal file
54
homeassistant/components/automation/webhook.py
Normal 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
|
@ -4,7 +4,6 @@ Offer zone automation rules.
|
||||
For more details about this automation rule, please refer to the documentation
|
||||
at https://home-assistant.io/docs/automation/trigger/#zone-trigger
|
||||
"""
|
||||
import asyncio
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
@ -27,8 +26,7 @@ TRIGGER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_trigger(hass, config, action):
|
||||
async def async_trigger(hass, config, action):
|
||||
"""Listen for state changes based on configuration."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
zone_entity_id = config.get(CONF_ZONE)
|
||||
|
@ -4,7 +4,6 @@ Support for ADS binary sensors.
|
||||
For more details about this platform, please refer to the documentation.
|
||||
https://home-assistant.io/components/binary_sensor.ads/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -50,8 +49,7 @@ class AdsBinarySensor(BinarySensorDevice):
|
||||
self._ads_hub = ads_hub
|
||||
self.ads_var = ads_var
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register device notification."""
|
||||
def update(name, value):
|
||||
"""Handle device notifications."""
|
||||
|
@ -4,7 +4,6 @@ Support for AlarmDecoder zone states- represented as binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.alarmdecoder/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -64,8 +63,7 @@ class AlarmDecoderBinarySensor(BinarySensorDevice):
|
||||
self._relay_addr = relay_addr
|
||||
self._relay_chan = relay_chan
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
SIGNAL_ZONE_FAULT, self._fault_callback)
|
||||
|
@ -4,8 +4,6 @@ Support for IP Webcam binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.android_ip_webcam/
|
||||
"""
|
||||
import asyncio
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.android_ip_webcam import (
|
||||
KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME)
|
||||
@ -13,8 +11,7 @@ from homeassistant.components.android_ip_webcam import (
|
||||
DEPENDENCIES = ['android_ip_webcam']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the IP Webcam binary sensors."""
|
||||
if discovery_info is None:
|
||||
@ -51,8 +48,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice):
|
||||
"""Return true if the binary sensor is on."""
|
||||
return self._state
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Retrieve latest state."""
|
||||
state, _ = self._ipcam.export_sensor(self._sensor)
|
||||
self._state = state == 1.0
|
||||
|
@ -4,7 +4,6 @@ Use Bayesian Inference to trigger a binary sensor.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.bayesian/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from collections import OrderedDict
|
||||
|
||||
@ -74,8 +73,7 @@ def update_probability(prior, prob_true, prob_false):
|
||||
return probability
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Bayesian Binary sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
@ -119,8 +117,7 @@ class BayesianBinarySensor(BinarySensorDevice):
|
||||
'state': self._process_state
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity about to be added."""
|
||||
@callback
|
||||
def async_threshold_sensor_state_listener(entity, old_state,
|
||||
@ -214,7 +211,6 @@ class BayesianBinarySensor(BinarySensorDevice):
|
||||
ATTR_PROBABILITY_THRESHOLD: self._probability_threshold,
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Get the latest data and update the states."""
|
||||
self._deviation = bool(self.probability >= self._probability_threshold)
|
||||
|
@ -2,10 +2,11 @@
|
||||
Support for Blink system camera control.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.blink/
|
||||
https://home-assistant.io/components/binary_sensor.blink.
|
||||
"""
|
||||
from homeassistant.components.blink import DOMAIN
|
||||
from homeassistant.components.blink import BLINK_DATA, BINARY_SENSORS
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import CONF_MONITORED_CONDITIONS
|
||||
|
||||
DEPENDENCIES = ['blink']
|
||||
|
||||
@ -14,24 +15,27 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the blink binary sensors."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
data = hass.data[BLINK_DATA]
|
||||
|
||||
data = hass.data[DOMAIN].blink
|
||||
devs = list()
|
||||
for name in data.cameras:
|
||||
devs.append(BlinkCameraMotionSensor(name, data))
|
||||
devs.append(BlinkSystemSensor(data))
|
||||
devs = []
|
||||
for camera in data.sync.cameras:
|
||||
for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]:
|
||||
devs.append(BlinkBinarySensor(data, camera, sensor_type))
|
||||
add_entities(devs, True)
|
||||
|
||||
|
||||
class BlinkCameraMotionSensor(BinarySensorDevice):
|
||||
class BlinkBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a Blink binary sensor."""
|
||||
|
||||
def __init__(self, name, data):
|
||||
def __init__(self, data, camera, sensor_type):
|
||||
"""Initialize the sensor."""
|
||||
self._name = 'blink_' + name + '_motion_enabled'
|
||||
self._camera_name = name
|
||||
self.data = data
|
||||
self._state = self.data.cameras[self._camera_name].armed
|
||||
self._type = sensor_type
|
||||
name, icon = BINARY_SENSORS[sensor_type]
|
||||
self._name = "{} {} {}".format(BLINK_DATA, camera, name)
|
||||
self._icon = icon
|
||||
self._camera = data.sync.cameras[camera]
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
@ -46,29 +50,4 @@ class BlinkCameraMotionSensor(BinarySensorDevice):
|
||||
def update(self):
|
||||
"""Update sensor state."""
|
||||
self.data.refresh()
|
||||
self._state = self.data.cameras[self._camera_name].armed
|
||||
|
||||
|
||||
class BlinkSystemSensor(BinarySensorDevice):
|
||||
"""A representation of a Blink system sensor."""
|
||||
|
||||
def __init__(self, data):
|
||||
"""Initialize the sensor."""
|
||||
self._name = 'blink armed status'
|
||||
self.data = data
|
||||
self._state = self.data.arm
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the blink sensor."""
|
||||
return self._name.replace(" ", "_")
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return the status of the sensor."""
|
||||
return self._state
|
||||
|
||||
def update(self):
|
||||
"""Update sensor state."""
|
||||
self.data.refresh()
|
||||
self._state = self.data.arm
|
||||
self._state = self._camera.attributes[self._type]
|
||||
|
@ -4,7 +4,6 @@ Reads vehicle status from BMW connected drive portal.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.bmw_connected_drive/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -124,7 +123,10 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
if not check_control_messages:
|
||||
result['check_control_messages'] = 'OK'
|
||||
else:
|
||||
result['check_control_messages'] = check_control_messages
|
||||
cbs_list = []
|
||||
for message in check_control_messages:
|
||||
cbs_list.append(message['ccmDescriptionShort'])
|
||||
result['check_control_messages'] = cbs_list
|
||||
elif self._attribute == 'charging_status':
|
||||
result['charging_status'] = vehicle_state.charging_status.value
|
||||
# pylint: disable=protected-access
|
||||
@ -190,8 +192,7 @@ class BMWConnectedDriveSensor(BinarySensorDevice):
|
||||
"""Schedule a state update."""
|
||||
self.schedule_update_ha_state(True)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Add callback after being added to hass.
|
||||
|
||||
Show latest data after startup.
|
||||
|
@ -4,7 +4,6 @@ Interfaces with Egardia/Woonveilig alarm control panel.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.egardia/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -18,8 +17,7 @@ EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion',
|
||||
'IR': 'motion'}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Initialize the platform."""
|
||||
if (discovery_info is None or
|
||||
|
@ -4,7 +4,6 @@ Support for Envisalink zone states- represented as binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.envisalink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
@ -22,8 +21,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['envisalink']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Envisalink binary sensor devices."""
|
||||
configured_zones = discovery_info['zones']
|
||||
@ -56,8 +54,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
|
||||
_LOGGER.debug('Setting up zone: %s', zone_name)
|
||||
super().__init__(zone_name, info, controller)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_ZONE_UPDATE, self._update_callback)
|
||||
|
@ -4,7 +4,6 @@ Provides a binary sensor which is a collection of ffmpeg tools.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.ffmpeg_motion/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -46,13 +45,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the FFmpeg binary motion sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
if not await manager.async_run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
|
||||
entity = FFmpegMotion(hass, manager, config)
|
||||
@ -98,8 +96,7 @@ class FFmpegMotion(FFmpegBinarySensor):
|
||||
self.ffmpeg = SensorMotion(
|
||||
manager.binary, hass.loop, self._async_callback)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_start_ffmpeg(self, entity_ids):
|
||||
async def _async_start_ffmpeg(self, entity_ids):
|
||||
"""Start a FFmpeg instance.
|
||||
|
||||
This method is a coroutine.
|
||||
@ -116,7 +113,7 @@ class FFmpegMotion(FFmpegBinarySensor):
|
||||
)
|
||||
|
||||
# run
|
||||
yield from self.ffmpeg.open_sensor(
|
||||
await self.ffmpeg.open_sensor(
|
||||
input_source=self._config.get(CONF_INPUT),
|
||||
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
||||
)
|
||||
|
@ -4,7 +4,6 @@ Provides a binary sensor which is a collection of ffmpeg tools.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.ffmpeg_noise/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -43,13 +42,12 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the FFmpeg noise binary sensor."""
|
||||
manager = hass.data[DATA_FFMPEG]
|
||||
|
||||
if not manager.async_run_test(config.get(CONF_INPUT)):
|
||||
if not await manager.async_run_test(config.get(CONF_INPUT)):
|
||||
return
|
||||
|
||||
entity = FFmpegNoise(hass, manager, config)
|
||||
@ -67,8 +65,7 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||
self.ffmpeg = SensorNoise(
|
||||
manager.binary, hass.loop, self._async_callback)
|
||||
|
||||
@asyncio.coroutine
|
||||
def _async_start_ffmpeg(self, entity_ids):
|
||||
async def _async_start_ffmpeg(self, entity_ids):
|
||||
"""Start a FFmpeg instance.
|
||||
|
||||
This method is a coroutine.
|
||||
@ -82,7 +79,7 @@ class FFmpegNoise(FFmpegBinarySensor):
|
||||
peak=self._config.get(CONF_PEAK),
|
||||
)
|
||||
|
||||
yield from self.ffmpeg.open_sensor(
|
||||
await self.ffmpeg.open_sensor(
|
||||
input_source=self._config.get(CONF_INPUT),
|
||||
output_dest=self._config.get(CONF_OUTPUT),
|
||||
extra_cmd=self._config.get(CONF_EXTRA_ARGUMENTS),
|
||||
|
64
homeassistant/components/binary_sensor/fritzbox.py
Normal file
64
homeassistant/components/binary_sensor/fritzbox.py
Normal 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()
|
@ -4,7 +4,6 @@ Support for INSTEON dimmers via PowerLinc Modem.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.insteon/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -22,8 +21,7 @@ SENSOR_TYPES = {'openClosedSensor': 'opening',
|
||||
'batterySensor': 'battery'}
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the INSTEON device class for the hass platform."""
|
||||
insteon_modem = hass.data['insteon'].get('modem')
|
||||
|
@ -4,8 +4,6 @@ Support for ISY994 binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.isy994/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import timedelta
|
||||
from typing import Callable
|
||||
@ -121,10 +119,9 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice):
|
||||
self._computed_state = bool(self._node.status._val)
|
||||
self._status_was_unknown = False
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self) -> None:
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to the node and subnode event emitters."""
|
||||
yield from super().async_added_to_hass()
|
||||
await super().async_added_to_hass()
|
||||
|
||||
self._node.controlEvents.subscribe(self._positive_node_control_handler)
|
||||
|
||||
@ -261,10 +258,9 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice):
|
||||
self._parent_device = parent_device
|
||||
self._heartbeat_timer = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self) -> None:
|
||||
async def async_added_to_hass(self) -> None:
|
||||
"""Subscribe to the node and subnode event emitters."""
|
||||
yield from super().async_added_to_hass()
|
||||
await super().async_added_to_hass()
|
||||
|
||||
self._node.controlEvents.subscribe(
|
||||
self._heartbeat_node_control_handler)
|
||||
|
@ -4,23 +4,26 @@ Support for MQTT binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mqtt/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import Optional
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.components import mqtt, binary_sensor
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
|
||||
from homeassistant.const import (
|
||||
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
|
||||
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
|
||||
from homeassistant.components.mqtt import (
|
||||
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
|
||||
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
|
||||
ATTR_DISCOVERY_HASH, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC,
|
||||
CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS,
|
||||
MqttAvailability, MqttDiscoveryUpdate)
|
||||
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -44,13 +47,28 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
|
||||
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the MQTT binary sensor."""
|
||||
if discovery_info is not None:
|
||||
config = PLATFORM_SCHEMA(discovery_info)
|
||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||
async_add_entities, discovery_info=None):
|
||||
"""Set up MQTT binary sensor through configuration.yaml."""
|
||||
await _async_setup_entity(hass, config, async_add_entities)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up MQTT binary sensor dynamically through MQTT discovery."""
|
||||
async def async_discover(discovery_payload):
|
||||
"""Discover and add a MQTT binary sensor."""
|
||||
config = PLATFORM_SCHEMA(discovery_payload)
|
||||
await _async_setup_entity(hass, config, async_add_entities,
|
||||
discovery_payload[ATTR_DISCOVERY_HASH])
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, MQTT_DISCOVERY_NEW.format(binary_sensor.DOMAIN, 'mqtt'),
|
||||
async_discover)
|
||||
|
||||
|
||||
async def _async_setup_entity(hass, config, async_add_entities,
|
||||
discovery_hash=None):
|
||||
"""Set up the MQTT binary sensor."""
|
||||
value_template = config.get(CONF_VALUE_TEMPLATE)
|
||||
if value_template is not None:
|
||||
value_template.hass = hass
|
||||
@ -68,19 +86,22 @@ def async_setup_platform(hass, config, async_add_entities,
|
||||
config.get(CONF_PAYLOAD_NOT_AVAILABLE),
|
||||
value_template,
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
discovery_hash,
|
||||
)])
|
||||
|
||||
|
||||
class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
class MqttBinarySensor(MqttAvailability, MqttDiscoveryUpdate,
|
||||
BinarySensorDevice):
|
||||
"""Representation a binary sensor that is updated by MQTT."""
|
||||
|
||||
def __init__(self, name, state_topic, availability_topic, device_class,
|
||||
qos, force_update, payload_on, payload_off, payload_available,
|
||||
payload_not_available, value_template,
|
||||
unique_id: Optional[str]):
|
||||
unique_id: Optional[str], discovery_hash):
|
||||
"""Initialize the MQTT binary sensor."""
|
||||
super().__init__(availability_topic, qos, payload_available,
|
||||
payload_not_available)
|
||||
MqttAvailability.__init__(self, availability_topic, qos,
|
||||
payload_available, payload_not_available)
|
||||
MqttDiscoveryUpdate.__init__(self, discovery_hash)
|
||||
self._name = name
|
||||
self._state = None
|
||||
self._state_topic = state_topic
|
||||
@ -91,11 +112,12 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
self._force_update = force_update
|
||||
self._template = value_template
|
||||
self._unique_id = unique_id
|
||||
self._discovery_hash = discovery_hash
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe mqtt events."""
|
||||
yield from super().async_added_to_hass()
|
||||
await MqttAvailability.async_added_to_hass(self)
|
||||
await MqttDiscoveryUpdate.async_added_to_hass(self)
|
||||
|
||||
@callback
|
||||
def state_message_received(topic, payload, qos):
|
||||
@ -115,7 +137,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
|
||||
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
yield from mqtt.async_subscribe(
|
||||
await mqtt.async_subscribe(
|
||||
self.hass, self._state_topic, state_message_received, self._qos)
|
||||
|
||||
@property
|
||||
|
@ -3,8 +3,6 @@
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mychevy/
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.mychevy import (
|
||||
@ -22,8 +20,7 @@ SENSORS = [
|
||||
]
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the MyChevy sensors."""
|
||||
if discovery_info is None:
|
||||
@ -75,8 +72,7 @@ class EVBinarySensor(BinarySensorDevice):
|
||||
"""Return the car."""
|
||||
return self._conn.get_car(self._car_vid)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
self.hass.helpers.dispatcher.async_dispatcher_connect(
|
||||
UPDATE_TOPIC, self.async_update_callback)
|
||||
|
@ -4,7 +4,6 @@ Support for the myStrom buttons.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.mystrom/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice
|
||||
@ -16,8 +15,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up myStrom Binary Sensor."""
|
||||
hass.http.register_view(MyStromView(async_add_entities))
|
||||
@ -37,14 +35,12 @@ class MyStromView(HomeAssistantView):
|
||||
self.buttons = {}
|
||||
self.add_entities = add_entities
|
||||
|
||||
@asyncio.coroutine
|
||||
def get(self, request):
|
||||
async def get(self, request):
|
||||
"""Handle the GET request received from a myStrom button."""
|
||||
res = yield from self._handle(request.app['hass'], request.query)
|
||||
res = await self._handle(request.app['hass'], request.query)
|
||||
return res
|
||||
|
||||
@asyncio.coroutine
|
||||
def _handle(self, hass, data):
|
||||
async def _handle(self, hass, data):
|
||||
"""Handle requests to the myStrom endpoint."""
|
||||
button_action = next((
|
||||
parameter for parameter in data
|
||||
|
@ -93,7 +93,11 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice):
|
||||
|
||||
async def async_update(self):
|
||||
"""Update the state."""
|
||||
data = self.openuv.data[DATA_PROTECTION_WINDOW]['result']
|
||||
data = self.openuv.data[DATA_PROTECTION_WINDOW]
|
||||
|
||||
if not data:
|
||||
return
|
||||
|
||||
if self._sensor_type == TYPE_PROTECTION_WINDOW:
|
||||
self._state = parse_datetime(
|
||||
data['from_time']) <= utcnow() <= parse_datetime(
|
||||
|
@ -4,18 +4,19 @@ Tracks the latency of a host by sending ICMP echo requests (ping).
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.ping/
|
||||
"""
|
||||
import logging
|
||||
import subprocess
|
||||
import re
|
||||
import sys
|
||||
import asyncio
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, PLATFORM_SCHEMA)
|
||||
from homeassistant.const import CONF_NAME, CONF_HOST
|
||||
PLATFORM_SCHEMA, BinarySensorDevice)
|
||||
from homeassistant.const import CONF_HOST, CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -48,13 +49,14 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
async def async_setup_platform(
|
||||
hass, config, async_add_entities, discovery_info=None):
|
||||
"""Set up the Ping Binary sensor."""
|
||||
name = config.get(CONF_NAME)
|
||||
host = config.get(CONF_HOST)
|
||||
count = config.get(CONF_PING_COUNT)
|
||||
|
||||
add_entities([PingBinarySensor(name, PingData(host, count))], True)
|
||||
async_add_entities([PingBinarySensor(name, PingData(host, count))], True)
|
||||
|
||||
|
||||
class PingBinarySensor(BinarySensorDevice):
|
||||
@ -91,9 +93,9 @@ class PingBinarySensor(BinarySensorDevice):
|
||||
ATTR_ROUND_TRIP_TIME_MIN: self.ping.data['min'],
|
||||
}
|
||||
|
||||
def update(self):
|
||||
async def async_update(self):
|
||||
"""Get the latest data."""
|
||||
self.ping.update()
|
||||
await self.ping.update()
|
||||
|
||||
|
||||
class PingData:
|
||||
@ -114,12 +116,13 @@ class PingData:
|
||||
'ping', '-n', '-q', '-c', str(self._count), '-W1',
|
||||
self._ip_address]
|
||||
|
||||
def ping(self):
|
||||
async def ping(self):
|
||||
"""Send ICMP echo request and return details if success."""
|
||||
pinger = subprocess.Popen(
|
||||
self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pinger = await asyncio.create_subprocess_shell(
|
||||
' '.join(self._ping_cmd), stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE)
|
||||
try:
|
||||
out = pinger.communicate()
|
||||
out = await pinger.communicate()
|
||||
_LOGGER.debug("Output is %s", str(out))
|
||||
if sys.platform == 'win32':
|
||||
match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1])
|
||||
@ -128,7 +131,8 @@ class PingData:
|
||||
'min': rtt_min,
|
||||
'avg': rtt_avg,
|
||||
'max': rtt_max,
|
||||
'mdev': ''}
|
||||
'mdev': '',
|
||||
}
|
||||
if 'max/' not in str(out):
|
||||
match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1])
|
||||
rtt_min, rtt_avg, rtt_max = match.groups()
|
||||
@ -136,18 +140,20 @@ class PingData:
|
||||
'min': rtt_min,
|
||||
'avg': rtt_avg,
|
||||
'max': rtt_max,
|
||||
'mdev': ''}
|
||||
'mdev': '',
|
||||
}
|
||||
match = PING_MATCHER.search(str(out).split('\n')[-1])
|
||||
rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups()
|
||||
return {
|
||||
'min': rtt_min,
|
||||
'avg': rtt_avg,
|
||||
'max': rtt_max,
|
||||
'mdev': rtt_mdev}
|
||||
'mdev': rtt_mdev,
|
||||
}
|
||||
except (subprocess.CalledProcessError, AttributeError):
|
||||
return False
|
||||
|
||||
def update(self):
|
||||
async def update(self):
|
||||
"""Retrieve the latest details from the host."""
|
||||
self.data = self.ping()
|
||||
self.data = await self.ping()
|
||||
self.available = bool(self.data)
|
||||
|
@ -92,6 +92,11 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor):
|
||||
"""Return the name of this sensor including the controller name."""
|
||||
return "{} online".format(self._controller.name)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
"""Return a unique id for this entity."""
|
||||
return "{}-online".format(self._controller.controller_id)
|
||||
|
||||
@property
|
||||
def device_class(self) -> str:
|
||||
"""Return the class of this device, from component DEVICE_CLASSES."""
|
||||
|
@ -4,7 +4,6 @@ Support for Satel Integra zone states- represented as binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.satel_integra/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -20,8 +19,7 @@ DEPENDENCIES = ['satel_integra']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Satel Integra binary sensor devices."""
|
||||
if not discovery_info:
|
||||
@ -50,8 +48,7 @@ class SatelIntegraBinarySensor(BinarySensorDevice):
|
||||
self._zone_type = zone_type
|
||||
self._state = 0
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
async_dispatcher_connect(
|
||||
self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated)
|
||||
|
@ -9,8 +9,7 @@ import logging
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components.spc import (
|
||||
ATTR_DISCOVER_DEVICES, SIGNAL_UPDATE_SENSOR)
|
||||
from homeassistant.components.spc import (DATA_API, SIGNAL_UPDATE_SENSOR)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -27,13 +26,12 @@ def _get_device_class(zone_type):
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the SPC binary sensor."""
|
||||
if (discovery_info is None or
|
||||
discovery_info[ATTR_DISCOVER_DEVICES] is None):
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
async_add_entities(SpcBinarySensor(zone)
|
||||
for zone in discovery_info[ATTR_DISCOVER_DEVICES]
|
||||
if _get_device_class(zone.type))
|
||||
api = hass.data[DATA_API]
|
||||
async_add_entities([SpcBinarySensor(zone)
|
||||
for zone in api.zones.values()
|
||||
if _get_device_class(zone.type)])
|
||||
|
||||
|
||||
class SpcBinarySensor(BinarySensorDevice):
|
||||
|
@ -4,7 +4,6 @@ Support for exposing a templated binary sensor.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.template/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -46,8 +45,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up template binary sensors."""
|
||||
sensors = []
|
||||
@ -109,8 +107,7 @@ class BinarySensorTemplate(BinarySensorDevice):
|
||||
self._delay_on = delay_on
|
||||
self._delay_off = delay_off
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Register callbacks."""
|
||||
@callback
|
||||
def template_bsensor_state_listener(entity, old_state, new_state):
|
||||
|
@ -4,7 +4,6 @@ Support for monitoring if a sensor value is below/above a threshold.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.threshold/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import voluptuous as vol
|
||||
@ -54,8 +53,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the Threshold sensor."""
|
||||
entity_id = config.get(CONF_ENTITY_ID)
|
||||
@ -147,8 +145,7 @@ class ThresholdSensor(BinarySensorDevice):
|
||||
ATTR_UPPER: self._threshold_upper,
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Get the latest data and updates the states."""
|
||||
def below(threshold):
|
||||
"""Determine if the sensor value is below a threshold."""
|
||||
|
@ -4,7 +4,6 @@ A sensor that monitors trends in other components.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.trend/
|
||||
"""
|
||||
import asyncio
|
||||
from collections import deque
|
||||
import logging
|
||||
import math
|
||||
@ -138,8 +137,7 @@ class SensorTrend(BinarySensorDevice):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Complete device setup after being added to hass."""
|
||||
@callback
|
||||
def trend_sensor_state_listener(entity, old_state, new_state):
|
||||
@ -160,8 +158,7 @@ class SensorTrend(BinarySensorDevice):
|
||||
self.hass, self._entity_id,
|
||||
trend_sensor_state_listener)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Get the latest data and update the states."""
|
||||
# Remove outdated samples
|
||||
if self._sample_duration > 0:
|
||||
@ -173,7 +170,7 @@ class SensorTrend(BinarySensorDevice):
|
||||
return
|
||||
|
||||
# Calculate gradient of linear trend
|
||||
yield from self.hass.async_add_job(self._calculate_gradient)
|
||||
await self.hass.async_add_job(self._calculate_gradient)
|
||||
|
||||
# Update state
|
||||
self._state = (
|
||||
|
@ -4,7 +4,6 @@ Support for Wink binary sensors.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
at https://home-assistant.io/components/binary_sensor.wink/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
@ -101,8 +100,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice):
|
||||
else:
|
||||
self.capability = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Call when entity is added to hass."""
|
||||
self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self)
|
||||
|
||||
|
@ -4,7 +4,6 @@ Sensor to indicate whether the current day is a workday.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.workday/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
@ -162,8 +161,7 @@ class IsWorkdaySensor(BinarySensorDevice):
|
||||
CONF_OFFSET: self._days_offset
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_update(self):
|
||||
async def async_update(self):
|
||||
"""Get date and look whether it is a holiday."""
|
||||
# Default is no workday
|
||||
self._state = False
|
||||
|
@ -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
|
161
homeassistant/components/blink/__init__.py
Normal file
161
homeassistant/components/blink/__init__.py
Normal 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)
|
21
homeassistant/components/blink/services.yaml
Normal file
21
homeassistant/components/blink/services.yaml
Normal 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'
|
@ -14,7 +14,7 @@ from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.event import track_utc_time_change
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['bimmer_connected==0.5.2']
|
||||
REQUIREMENTS = ['bimmer_connected==0.5.3']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -83,62 +83,6 @@ class Image:
|
||||
content = attr.ib(type=bytes)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_off(hass, entity_id=None):
|
||||
"""Turn off camera."""
|
||||
hass.add_job(async_turn_off, hass, entity_id)
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_turn_off(hass, entity_id=None):
|
||||
"""Turn off camera."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
await hass.services.async_call(DOMAIN, SERVICE_TURN_OFF, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def turn_on(hass, entity_id=None):
|
||||
"""Turn on camera."""
|
||||
hass.add_job(async_turn_on, hass, entity_id)
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_turn_on(hass, entity_id=None):
|
||||
"""Turn on camera, and set operation mode."""
|
||||
data = {}
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
await hass.services.async_call(DOMAIN, SERVICE_TURN_ON, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def enable_motion_detection(hass, entity_id=None):
|
||||
"""Enable Motion Detection."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
hass.async_add_job(hass.services.async_call(
|
||||
DOMAIN, SERVICE_ENABLE_MOTION, data))
|
||||
|
||||
|
||||
@bind_hass
|
||||
def disable_motion_detection(hass, entity_id=None):
|
||||
"""Disable Motion Detection."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else None
|
||||
hass.async_add_job(hass.services.async_call(
|
||||
DOMAIN, SERVICE_DISABLE_MOTION, data))
|
||||
|
||||
|
||||
@bind_hass
|
||||
@callback
|
||||
def async_snapshot(hass, filename, entity_id=None):
|
||||
"""Make a snapshot from a camera."""
|
||||
data = {ATTR_ENTITY_ID: entity_id} if entity_id else {}
|
||||
data[ATTR_FILENAME] = filename
|
||||
|
||||
hass.async_add_job(hass.services.async_call(
|
||||
DOMAIN, SERVICE_SNAPSHOT, data))
|
||||
|
||||
|
||||
@bind_hass
|
||||
async def async_get_image(hass, entity_id, timeout=10):
|
||||
"""Fetch an image from a camera entity."""
|
||||
@ -239,7 +183,7 @@ async def async_setup(hass, config):
|
||||
"""Update tokens of the entities."""
|
||||
for entity in component.entities:
|
||||
entity.async_update_token()
|
||||
hass.async_add_job(entity.async_update_ha_state())
|
||||
hass.async_create_task(entity.async_update_ha_state())
|
||||
|
||||
hass.helpers.event.async_track_time_interval(
|
||||
update_tokens, TOKEN_CHANGE_INTERVAL)
|
||||
@ -508,28 +452,24 @@ class CameraMjpegStream(CameraView):
|
||||
raise web.HTTPBadRequest()
|
||||
|
||||
|
||||
@callback
|
||||
def websocket_camera_thumbnail(hass, connection, msg):
|
||||
@websocket_api.async_response
|
||||
async def websocket_camera_thumbnail(hass, connection, msg):
|
||||
"""Handle get camera thumbnail websocket command.
|
||||
|
||||
Async friendly.
|
||||
"""
|
||||
async def send_camera_still():
|
||||
"""Send a camera still."""
|
||||
try:
|
||||
image = await async_get_image(hass, msg['entity_id'])
|
||||
connection.send_message_outside(websocket_api.result_message(
|
||||
connection.send_message(websocket_api.result_message(
|
||||
msg['id'], {
|
||||
'content_type': image.content_type,
|
||||
'content': base64.b64encode(image.content).decode('utf-8')
|
||||
}
|
||||
))
|
||||
except HomeAssistantError:
|
||||
connection.send_message_outside(websocket_api.error_message(
|
||||
connection.send_message(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):
|
||||
"""Handle snapshot services calls."""
|
||||
|
@ -4,7 +4,6 @@ This component provides HA camera support for Abode Security System.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.abode/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
@ -51,10 +50,9 @@ class AbodeCamera(AbodeDevice, Camera):
|
||||
self._event = event
|
||||
self._response = None
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe Abode events."""
|
||||
yield from super().async_added_to_hass()
|
||||
await super().async_added_to_hass()
|
||||
|
||||
self.hass.async_add_job(
|
||||
self._data.abode.events.add_timeline_callback,
|
||||
|
@ -4,7 +4,6 @@ This component provides basic support for Amcrest IP cameras.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.amcrest/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from homeassistant.components.amcrest import (
|
||||
@ -21,8 +20,7 @@ DEPENDENCIES = ['amcrest', 'ffmpeg']
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up an Amcrest IP Camera."""
|
||||
if discovery_info is None:
|
||||
@ -57,12 +55,11 @@ class AmcrestCam(Camera):
|
||||
response = self._camera.snapshot(channel=self._resolution)
|
||||
return response.data
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_async_mjpeg_stream(self, request):
|
||||
async def handle_async_mjpeg_stream(self, request):
|
||||
"""Return an MJPEG stream."""
|
||||
# The snapshot implementation is handled by the parent class
|
||||
if self._stream_source == STREAM_SOURCE_LIST['snapshot']:
|
||||
yield from super().handle_async_mjpeg_stream(request)
|
||||
await super().handle_async_mjpeg_stream(request)
|
||||
return
|
||||
|
||||
if self._stream_source == STREAM_SOURCE_LIST['mjpeg']:
|
||||
@ -72,7 +69,7 @@ class AmcrestCam(Camera):
|
||||
stream_coro = websession.get(
|
||||
streaming_url, auth=self._token, timeout=TIMEOUT)
|
||||
|
||||
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
||||
await async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
||||
|
||||
else:
|
||||
# streaming via fmpeg
|
||||
@ -80,13 +77,13 @@ class AmcrestCam(Camera):
|
||||
|
||||
streaming_url = self._camera.rtsp_url(typeno=self._resolution)
|
||||
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
yield from stream.open_camera(
|
||||
await stream.open_camera(
|
||||
streaming_url, extra_cmd=self._ffmpeg_arguments)
|
||||
|
||||
yield from async_aiohttp_proxy_stream(
|
||||
await async_aiohttp_proxy_stream(
|
||||
self.hass, request, stream,
|
||||
'multipart/x-mixed-replace;boundary=ffserver')
|
||||
yield from stream.close()
|
||||
await stream.close()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -4,31 +4,27 @@ Support for Blink system camera.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.blink/
|
||||
"""
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
import requests
|
||||
|
||||
from homeassistant.components.blink import DOMAIN
|
||||
from homeassistant.components.blink import BLINK_DATA, DEFAULT_BRAND
|
||||
from homeassistant.components.camera import Camera
|
||||
from homeassistant.util import Throttle
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['blink']
|
||||
|
||||
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
|
||||
ATTR_VIDEO_CLIP = 'video'
|
||||
ATTR_IMAGE = 'image'
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up a Blink Camera."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
|
||||
data = hass.data[DOMAIN].blink
|
||||
devs = list()
|
||||
for name in data.cameras:
|
||||
devs.append(BlinkCamera(hass, config, data, name))
|
||||
data = hass.data[BLINK_DATA]
|
||||
devs = []
|
||||
for name, camera in data.sync.cameras.items():
|
||||
devs.append(BlinkCamera(data, name, camera))
|
||||
|
||||
add_entities(devs)
|
||||
|
||||
@ -36,15 +32,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
class BlinkCamera(Camera):
|
||||
"""An implementation of a Blink Camera."""
|
||||
|
||||
def __init__(self, hass, config, data, name):
|
||||
def __init__(self, data, name, camera):
|
||||
"""Initialize a camera."""
|
||||
super().__init__()
|
||||
self.data = data
|
||||
self.hass = hass
|
||||
self._name = name
|
||||
self.notifications = self.data.cameras[self._name].notifications
|
||||
self._name = "{} {}".format(BLINK_DATA, name)
|
||||
self._camera = camera
|
||||
self.response = None
|
||||
|
||||
self.current_image = None
|
||||
self.last_image = None
|
||||
_LOGGER.debug("Initialized blink camera %s", self._name)
|
||||
|
||||
@property
|
||||
@ -52,30 +48,29 @@ class BlinkCamera(Camera):
|
||||
"""Return the camera name."""
|
||||
return self._name
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_UPDATES)
|
||||
def request_image(self):
|
||||
"""Request a new image from Blink servers."""
|
||||
_LOGGER.debug("Requesting new image from blink servers")
|
||||
image_url = self.check_for_motion()
|
||||
header = self.data.cameras[self._name].header
|
||||
self.response = requests.get(image_url, headers=header, stream=True)
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the camera attributes."""
|
||||
return self._camera.attributes
|
||||
|
||||
def check_for_motion(self):
|
||||
"""Check if motion has been detected since last update."""
|
||||
self.data.refresh()
|
||||
notifs = self.data.cameras[self._name].notifications
|
||||
if notifs > self.notifications:
|
||||
# We detected motion at some point
|
||||
self.data.last_motion()
|
||||
self.notifications = notifs
|
||||
# Returning motion image currently not working
|
||||
# return self.data.cameras[self._name].motion['image']
|
||||
elif notifs < self.notifications:
|
||||
self.notifications = notifs
|
||||
def enable_motion_detection(self):
|
||||
"""Enable motion detection for the camera."""
|
||||
self._camera.set_motion_detect(True)
|
||||
|
||||
return self.data.camera_thumbs[self._name]
|
||||
def disable_motion_detection(self):
|
||||
"""Disable motion detection for the camera."""
|
||||
self._camera.set_motion_detect(False)
|
||||
|
||||
@property
|
||||
def motion_detection_enabled(self):
|
||||
"""Return the state of the camera."""
|
||||
return self._camera.armed
|
||||
|
||||
@property
|
||||
def brand(self):
|
||||
"""Return the camera brand."""
|
||||
return DEFAULT_BRAND
|
||||
|
||||
def camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
self.request_image()
|
||||
return self.response.content
|
||||
return self._camera.image_from_cache.content
|
||||
|
@ -75,35 +75,33 @@ class CanaryCamera(Camera):
|
||||
"""Return the camera motion detection status."""
|
||||
return not self._location.is_recording
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
self.renew_live_stream_session()
|
||||
|
||||
from haffmpeg import ImageFrame, IMAGE_JPEG
|
||||
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
image = yield from asyncio.shield(ffmpeg.get_image(
|
||||
image = await asyncio.shield(ffmpeg.get_image(
|
||||
self._live_stream_session.live_stream_url,
|
||||
output_format=IMAGE_JPEG,
|
||||
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
|
||||
return image
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_async_mjpeg_stream(self, request):
|
||||
async def handle_async_mjpeg_stream(self, request):
|
||||
"""Generate an HTTP MJPEG stream from the camera."""
|
||||
if self._live_stream_session is None:
|
||||
return
|
||||
|
||||
from haffmpeg import CameraMjpeg
|
||||
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
yield from stream.open_camera(
|
||||
await stream.open_camera(
|
||||
self._live_stream_session.live_stream_url,
|
||||
extra_cmd=self._ffmpeg_arguments)
|
||||
|
||||
yield from async_aiohttp_proxy_stream(
|
||||
await async_aiohttp_proxy_stream(
|
||||
self.hass, request, stream,
|
||||
'multipart/x-mixed-replace;boundary=ffserver')
|
||||
yield from stream.close()
|
||||
await stream.close()
|
||||
|
||||
@Throttle(MIN_TIME_BETWEEN_SESSION_RENEW)
|
||||
def renew_live_stream_session(self):
|
||||
|
@ -27,8 +27,7 @@ _LOGGER = logging.getLogger(__name__)
|
||||
_TIMEOUT = 10 # seconds
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the DoorBird camera platform."""
|
||||
for doorstation in hass.data[DOORBIRD_DOMAIN]:
|
||||
@ -66,8 +65,7 @@ class DoorBirdCamera(Camera):
|
||||
"""Get the name of the camera."""
|
||||
return self._name
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
async def async_camera_image(self):
|
||||
"""Pull a still image from the camera."""
|
||||
now = datetime.datetime.now()
|
||||
|
||||
@ -77,9 +75,9 @@ class DoorBirdCamera(Camera):
|
||||
try:
|
||||
websession = async_get_clientsession(self.hass)
|
||||
with async_timeout.timeout(_TIMEOUT, loop=self.hass.loop):
|
||||
response = yield from websession.get(self._url)
|
||||
response = await websession.get(self._url)
|
||||
|
||||
self._last_image = yield from response.read()
|
||||
self._last_image = await response.read()
|
||||
self._last_update = now
|
||||
return self._last_image
|
||||
except asyncio.TimeoutError:
|
||||
|
@ -46,8 +46,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up a generic IP Camera."""
|
||||
async_add_entities([GenericCamera(hass, config)])
|
||||
@ -93,8 +92,7 @@ class GenericCamera(Camera):
|
||||
return run_coroutine_threadsafe(
|
||||
self.async_camera_image(), self.hass.loop).result()
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
try:
|
||||
url = self._still_image_url.async_render()
|
||||
@ -118,7 +116,7 @@ class GenericCamera(Camera):
|
||||
_LOGGER.error("Error getting camera image: %s", error)
|
||||
return self._last_image
|
||||
|
||||
self._last_image = yield from self.hass.async_add_job(
|
||||
self._last_image = await self.hass.async_add_job(
|
||||
fetch)
|
||||
# async
|
||||
else:
|
||||
@ -126,9 +124,9 @@ class GenericCamera(Camera):
|
||||
websession = async_get_clientsession(
|
||||
self.hass, verify_ssl=self.verify_ssl)
|
||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||
response = yield from websession.get(
|
||||
response = await websession.get(
|
||||
url, auth=self._auth)
|
||||
self._last_image = yield from response.read()
|
||||
self._last_image = await response.read()
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.error("Timeout getting camera image")
|
||||
return self._last_image
|
||||
|
@ -41,10 +41,17 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up a MJPEG IP Camera."""
|
||||
# Filter header errors from urllib3 due to a urllib3 bug
|
||||
urllib3_logger = logging.getLogger("urllib3.connectionpool")
|
||||
if not any(isinstance(x, NoHeaderErrorFilter)
|
||||
for x in urllib3_logger.filters):
|
||||
urllib3_logger.addFilter(
|
||||
NoHeaderErrorFilter()
|
||||
)
|
||||
|
||||
if discovery_info:
|
||||
config = PLATFORM_SCHEMA(discovery_info)
|
||||
async_add_entities([MjpegCamera(config)])
|
||||
@ -82,23 +89,22 @@ class MjpegCamera(Camera):
|
||||
self._username, password=self._password
|
||||
)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
# DigestAuth is not supported
|
||||
if self._authentication == HTTP_DIGEST_AUTHENTICATION or \
|
||||
self._still_image_url is None:
|
||||
image = yield from self.hass.async_add_job(
|
||||
image = await self.hass.async_add_job(
|
||||
self.camera_image)
|
||||
return image
|
||||
|
||||
websession = async_get_clientsession(self.hass)
|
||||
try:
|
||||
with async_timeout.timeout(10, loop=self.hass.loop):
|
||||
response = yield from websession.get(
|
||||
response = await websession.get(
|
||||
self._still_image_url, auth=self._auth)
|
||||
|
||||
image = yield from response.read()
|
||||
image = await response.read()
|
||||
return image
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
@ -141,3 +147,11 @@ class MjpegCamera(Camera):
|
||||
def name(self):
|
||||
"""Return the name of this camera."""
|
||||
return self._name
|
||||
|
||||
|
||||
class NoHeaderErrorFilter(logging.Filter):
|
||||
"""Filter out urllib3 Header Parsing Errors due to a urllib3 bug."""
|
||||
|
||||
def filter(self, record):
|
||||
"""Filter out Header Parsing Errors."""
|
||||
return "Failed to parse headers" not in record.getMessage()
|
||||
|
@ -10,10 +10,13 @@ import logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.components import mqtt
|
||||
from homeassistant.const import CONF_NAME
|
||||
from homeassistant.components import mqtt, camera
|
||||
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
|
||||
from homeassistant.components.mqtt.discovery import MQTT_DISCOVERY_NEW
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -31,13 +34,26 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the MQTT Camera."""
|
||||
if discovery_info is not None:
|
||||
config = PLATFORM_SCHEMA(discovery_info)
|
||||
async def async_setup_platform(hass: HomeAssistantType, config: ConfigType,
|
||||
async_add_entities, discovery_info=None):
|
||||
"""Set up MQTT camera through configuration.yaml."""
|
||||
await _async_setup_entity(hass, config, async_add_entities)
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up MQTT camera dynamically through MQTT discovery."""
|
||||
async def async_discover(discovery_payload):
|
||||
"""Discover and add a MQTT camera."""
|
||||
config = PLATFORM_SCHEMA(discovery_payload)
|
||||
await _async_setup_entity(hass, config, async_add_entities)
|
||||
|
||||
async_dispatcher_connect(
|
||||
hass, MQTT_DISCOVERY_NEW.format(camera.DOMAIN, 'mqtt'),
|
||||
async_discover)
|
||||
|
||||
|
||||
async def _async_setup_entity(hass, config, async_add_entities):
|
||||
"""Set up the MQTT Camera."""
|
||||
async_add_entities([MqttCamera(
|
||||
config.get(CONF_NAME),
|
||||
config.get(CONF_UNIQUE_ID),
|
||||
|
@ -46,6 +46,7 @@ DIR_LEFT = "LEFT"
|
||||
DIR_RIGHT = "RIGHT"
|
||||
ZOOM_OUT = "ZOOM_OUT"
|
||||
ZOOM_IN = "ZOOM_IN"
|
||||
PTZ_NONE = "NONE"
|
||||
|
||||
SERVICE_PTZ = "onvif_ptz"
|
||||
|
||||
@ -65,9 +66,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
|
||||
SERVICE_PTZ_SCHEMA = vol.Schema({
|
||||
ATTR_ENTITY_ID: cv.entity_ids,
|
||||
ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]),
|
||||
ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]),
|
||||
ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN])
|
||||
ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT, PTZ_NONE]),
|
||||
ATTR_TILT: vol.In([DIR_UP, DIR_DOWN, PTZ_NONE]),
|
||||
ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN, PTZ_NONE])
|
||||
})
|
||||
|
||||
|
||||
|
@ -110,8 +110,7 @@ class RingCam(Camera):
|
||||
'video_url': self._video_url,
|
||||
}
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_camera_image(self):
|
||||
async def async_camera_image(self):
|
||||
"""Return a still image response from the camera."""
|
||||
from haffmpeg import ImageFrame, IMAGE_JPEG
|
||||
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
@ -119,13 +118,12 @@ class RingCam(Camera):
|
||||
if self._video_url is None:
|
||||
return
|
||||
|
||||
image = yield from asyncio.shield(ffmpeg.get_image(
|
||||
image = await asyncio.shield(ffmpeg.get_image(
|
||||
self._video_url, output_format=IMAGE_JPEG,
|
||||
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
|
||||
return image
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_async_mjpeg_stream(self, request):
|
||||
async def handle_async_mjpeg_stream(self, request):
|
||||
"""Generate an HTTP MJPEG stream from the camera."""
|
||||
from haffmpeg import CameraMjpeg
|
||||
|
||||
@ -133,13 +131,13 @@ class RingCam(Camera):
|
||||
return
|
||||
|
||||
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
|
||||
yield from stream.open_camera(
|
||||
await stream.open_camera(
|
||||
self._video_url, extra_cmd=self._ffmpeg_arguments)
|
||||
|
||||
yield from async_aiohttp_proxy_stream(
|
||||
await async_aiohttp_proxy_stream(
|
||||
self.hass, request, stream,
|
||||
'multipart/x-mixed-replace;boundary=ffserver')
|
||||
yield from stream.close()
|
||||
await stream.close()
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
|
@ -4,7 +4,6 @@ Support for Synology Surveillance Station Cameras.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/camera.synology/
|
||||
"""
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
import requests
|
||||
@ -38,8 +37,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up a Synology IP Camera."""
|
||||
verify_ssl = config.get(CONF_VERIFY_SSL)
|
||||
@ -87,15 +85,14 @@ class SynologyCamera(Camera):
|
||||
"""Return bytes of camera image."""
|
||||
return self._surveillance.get_camera_image(self._camera_id)
|
||||
|
||||
@asyncio.coroutine
|
||||
def handle_async_mjpeg_stream(self, request):
|
||||
async def handle_async_mjpeg_stream(self, request):
|
||||
"""Return a MJPEG stream image response directly from the camera."""
|
||||
streaming_url = self._camera.video_stream_url
|
||||
|
||||
websession = async_get_clientsession(self.hass, self._verify_ssl)
|
||||
stream_coro = websession.get(streaming_url)
|
||||
|
||||
yield from async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
||||
await async_aiohttp_proxy_web(self.hass, request, stream_coro)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
|
@ -2,11 +2,11 @@
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "\u0423\u0441\u0442\u0440\u043e\u0439\u0441\u0442\u0432\u0430 Google Cast \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u044b.",
|
||||
"single_instance_allowed": "\u041d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u0430 \u0442\u043e\u043b\u044c\u043a\u043e \u043e\u0434\u043d\u0430 \u043a\u043e\u043d\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u044f Google Cast."
|
||||
"single_instance_allowed": "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0430 \u043a\u043e\u043c\u043f\u043e\u043d\u0435\u043d\u0442\u0430 \u0443\u0436\u0435 \u0432\u044b\u043f\u043e\u043b\u043d\u0435\u043d\u0430."
|
||||
},
|
||||
"step": {
|
||||
"confirm": {
|
||||
"description": "\u0412\u044b \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
|
||||
"description": "\u0412\u044b \u0443\u0432\u0435\u0440\u0435\u043d\u044b, \u0447\u0442\u043e \u0445\u043e\u0442\u0438\u0442\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0438\u0442\u044c Google Cast?",
|
||||
"title": "Google Cast"
|
||||
}
|
||||
},
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u8a2d\u5099\u3002",
|
||||
"no_devices_found": "\u5728\u7db2\u8def\u4e0a\u627e\u4e0d\u5230 Google Cast \u88dd\u7f6e\u3002",
|
||||
"single_instance_allowed": "\u50c5\u9700\u8a2d\u5b9a\u4e00\u6b21 Google Cast \u5373\u53ef\u3002"
|
||||
},
|
||||
"step": {
|
||||
|
@ -10,7 +10,6 @@ import functools as ft
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.loader import bind_hass
|
||||
from homeassistant.helpers.temperature import display_temp as show_temp
|
||||
from homeassistant.util.temperature import convert as convert_temperature
|
||||
from homeassistant.helpers.entity_component import EntityComponent
|
||||
@ -20,7 +19,7 @@ import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF,
|
||||
STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE,
|
||||
PRECISION_TENTHS, )
|
||||
PRECISION_TENTHS)
|
||||
|
||||
DEFAULT_MIN_TEMP = 7
|
||||
DEFAULT_MAX_TEMP = 35
|
||||
@ -142,107 +141,6 @@ SET_SWING_MODE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_away_mode(hass, away_mode, entity_id=None):
|
||||
"""Turn all or specified climate devices away mode on."""
|
||||
data = {
|
||||
ATTR_AWAY_MODE: away_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_AWAY_MODE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_hold_mode(hass, hold_mode, entity_id=None):
|
||||
"""Set new hold mode."""
|
||||
data = {
|
||||
ATTR_HOLD_MODE: hold_mode
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_HOLD_MODE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_aux_heat(hass, aux_heat, entity_id=None):
|
||||
"""Turn all or specified climate devices auxiliary heater on."""
|
||||
data = {
|
||||
ATTR_AUX_HEAT: aux_heat
|
||||
}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_AUX_HEAT, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_temperature(hass, temperature=None, entity_id=None,
|
||||
target_temp_high=None, target_temp_low=None,
|
||||
operation_mode=None):
|
||||
"""Set new target temperature."""
|
||||
kwargs = {
|
||||
key: value for key, value in [
|
||||
(ATTR_TEMPERATURE, temperature),
|
||||
(ATTR_TARGET_TEMP_HIGH, target_temp_high),
|
||||
(ATTR_TARGET_TEMP_LOW, target_temp_low),
|
||||
(ATTR_ENTITY_ID, entity_id),
|
||||
(ATTR_OPERATION_MODE, operation_mode)
|
||||
] if value is not None
|
||||
}
|
||||
_LOGGER.debug("set_temperature start data=%s", kwargs)
|
||||
hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_humidity(hass, humidity, entity_id=None):
|
||||
"""Set new target humidity."""
|
||||
data = {ATTR_HUMIDITY: humidity}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_HUMIDITY, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_fan_mode(hass, fan, entity_id=None):
|
||||
"""Set all or specified climate devices fan mode on."""
|
||||
data = {ATTR_FAN_MODE: fan}
|
||||
|
||||
if entity_id:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_FAN_MODE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_operation_mode(hass, operation_mode, entity_id=None):
|
||||
"""Set new target operation mode."""
|
||||
data = {ATTR_OPERATION_MODE: operation_mode}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_OPERATION_MODE, data)
|
||||
|
||||
|
||||
@bind_hass
|
||||
def set_swing_mode(hass, swing_mode, entity_id=None):
|
||||
"""Set new target swing mode."""
|
||||
data = {ATTR_SWING_MODE: swing_mode}
|
||||
|
||||
if entity_id is not None:
|
||||
data[ATTR_ENTITY_ID] = entity_id
|
||||
|
||||
hass.services.call(DOMAIN, SERVICE_SET_SWING_MODE, data)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up climate devices."""
|
||||
component = hass.data[DOMAIN] = \
|
||||
|
371
homeassistant/components/climate/evohome.py
Normal file
371
homeassistant/components/climate/evohome.py
Normal 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 + "]"
|
||||
)
|
@ -10,13 +10,15 @@ import requests
|
||||
|
||||
from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN
|
||||
from homeassistant.components.fritzbox import (
|
||||
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED)
|
||||
ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_HOLIDAY_MODE,
|
||||
ATTR_STATE_LOCKED, ATTR_STATE_SUMMER_MODE,
|
||||
ATTR_STATE_WINDOW_OPEN)
|
||||
from homeassistant.components.climate import (
|
||||
ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL,
|
||||
STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_TARGET_TEMPERATURE)
|
||||
from homeassistant.const import (
|
||||
ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
|
||||
ATTR_BATTERY_LEVEL, ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS)
|
||||
DEPENDENCIES = ['fritzbox']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@ -151,10 +153,21 @@ class FritzboxThermostat(ClimateDevice):
|
||||
def device_state_attributes(self):
|
||||
"""Return the device specific state attributes."""
|
||||
attrs = {
|
||||
ATTR_STATE_BATTERY_LOW: self._device.battery_low,
|
||||
ATTR_STATE_DEVICE_LOCKED: self._device.device_lock,
|
||||
ATTR_STATE_LOCKED: self._device.lock,
|
||||
ATTR_STATE_BATTERY_LOW: self._device.battery_low,
|
||||
}
|
||||
|
||||
# the following attributes are available since fritzos 7
|
||||
if self._device.battery_level is not None:
|
||||
attrs[ATTR_BATTERY_LEVEL] = self._device.battery_level
|
||||
if self._device.holiday_active is not None:
|
||||
attrs[ATTR_STATE_HOLIDAY_MODE] = self._device.holiday_active
|
||||
if self._device.summer_active is not None:
|
||||
attrs[ATTR_STATE_SUMMER_MODE] = self._device.summer_active
|
||||
if ATTR_STATE_WINDOW_OPEN is not None:
|
||||
attrs[ATTR_STATE_WINDOW_OPEN] = self._device.window_open
|
||||
|
||||
return attrs
|
||||
|
||||
def update(self):
|
||||
|
@ -67,8 +67,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
})
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_setup_platform(hass, config, async_add_entities,
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Set up the generic thermostat platform."""
|
||||
name = config.get(CONF_NAME)
|
||||
@ -147,12 +146,10 @@ class GenericThermostat(ClimateDevice):
|
||||
if sensor_state and sensor_state.state != STATE_UNKNOWN:
|
||||
self._async_update_temp(sensor_state)
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_added_to_hass(self):
|
||||
async def async_added_to_hass(self):
|
||||
"""Run when entity about to be added."""
|
||||
# Check If we have an old state
|
||||
old_state = yield from async_get_last_state(self.hass,
|
||||
self.entity_id)
|
||||
old_state = await async_get_last_state(self.hass, self.entity_id)
|
||||
if old_state is not None:
|
||||
# If we have no initial temperature, restore
|
||||
if self._target_temp is None:
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user