mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
commit
e049b35413
31
.coveragerc
31
.coveragerc
@ -122,12 +122,17 @@ omit =
|
||||
homeassistant/components/*/ecovacs.py
|
||||
|
||||
homeassistant/components/esphome/__init__.py
|
||||
homeassistant/components/*/esphome.py
|
||||
homeassistant/components/esphome/binary_sensor.py
|
||||
homeassistant/components/esphome/cover.py
|
||||
homeassistant/components/esphome/fan.py
|
||||
homeassistant/components/esphome/light.py
|
||||
homeassistant/components/esphome/sensor.py
|
||||
homeassistant/components/esphome/switch.py
|
||||
|
||||
homeassistant/components/eufy.py
|
||||
homeassistant/components/*/eufy.py
|
||||
|
||||
homeassistant/components/fibaro.py
|
||||
homeassistant/components/fibaro/__init__.py
|
||||
homeassistant/components/*/fibaro.py
|
||||
|
||||
homeassistant/components/gc100.py
|
||||
@ -234,7 +239,7 @@ omit =
|
||||
homeassistant/components/lutron_caseta.py
|
||||
homeassistant/components/*/lutron_caseta.py
|
||||
|
||||
homeassistant/components/*/mailgun.py
|
||||
homeassistant/components/mailgun/notify.py
|
||||
|
||||
homeassistant/components/matrix.py
|
||||
homeassistant/components/*/matrix.py
|
||||
@ -276,7 +281,8 @@ omit =
|
||||
homeassistant/components/*/opentherm_gw.py
|
||||
|
||||
homeassistant/components/openuv/__init__.py
|
||||
homeassistant/components/*/openuv.py
|
||||
homeassistant/components/openuv/binary_sensor.py
|
||||
homeassistant/components/openuv/sensor.py
|
||||
|
||||
homeassistant/components/plum_lightpad.py
|
||||
homeassistant/components/*/plum_lightpad.py
|
||||
@ -298,7 +304,9 @@ omit =
|
||||
homeassistant/components/*/raincloud.py
|
||||
|
||||
homeassistant/components/rainmachine/__init__.py
|
||||
homeassistant/components/*/rainmachine.py
|
||||
homeassistant/components/rainmachine/binary_sensor.py
|
||||
homeassistant/components/rainmachine/sensor.py
|
||||
homeassistant/components/rainmachine/switch.py
|
||||
|
||||
homeassistant/components/raspihats.py
|
||||
homeassistant/components/*/raspihats.py
|
||||
@ -308,6 +316,9 @@ omit =
|
||||
homeassistant/components/rfxtrx.py
|
||||
homeassistant/components/*/rfxtrx.py
|
||||
|
||||
homeassistant/components/roku.py
|
||||
homeassistant/components/*/roku.py
|
||||
|
||||
homeassistant/components/rpi_gpio.py
|
||||
homeassistant/components/*/rpi_gpio.py
|
||||
|
||||
@ -327,7 +338,7 @@ omit =
|
||||
homeassistant/components/*/sense.py
|
||||
|
||||
homeassistant/components/simplisafe/__init__.py
|
||||
homeassistant/components/*/simplisafe.py
|
||||
homeassistant/components/simplisafe/alarm_control_panel.py
|
||||
|
||||
homeassistant/components/sisyphus.py
|
||||
homeassistant/components/*/sisyphus.py
|
||||
@ -424,8 +435,14 @@ omit =
|
||||
homeassistant/components/*/zabbix.py
|
||||
|
||||
homeassistant/components/zha/__init__.py
|
||||
homeassistant/components/zha/binary_sensor.py
|
||||
homeassistant/components/zha/const.py
|
||||
homeassistant/components/zha/event.py
|
||||
homeassistant/components/zha/fan.py
|
||||
homeassistant/components/zha/light.py
|
||||
homeassistant/components/zha/sensor.py
|
||||
homeassistant/components/zha/switch.py
|
||||
homeassistant/components/zha/api.py
|
||||
homeassistant/components/zha/entities/*
|
||||
homeassistant/components/zha/helpers.py
|
||||
homeassistant/components/*/zha.py
|
||||
@ -519,7 +536,6 @@ omit =
|
||||
homeassistant/components/device_tracker/fritz.py
|
||||
homeassistant/components/device_tracker/google_maps.py
|
||||
homeassistant/components/device_tracker/googlehome.py
|
||||
homeassistant/components/device_tracker/gpslogger.py
|
||||
homeassistant/components/device_tracker/hitron_coda.py
|
||||
homeassistant/components/device_tracker/huawei_router.py
|
||||
homeassistant/components/device_tracker/icloud.py
|
||||
@ -637,7 +653,6 @@ omit =
|
||||
homeassistant/components/media_player/pioneer.py
|
||||
homeassistant/components/media_player/pjlink.py
|
||||
homeassistant/components/media_player/plex.py
|
||||
homeassistant/components/media_player/roku.py
|
||||
homeassistant/components/media_player/russound_rio.py
|
||||
homeassistant/components/media_player/russound_rnet.py
|
||||
homeassistant/components/media_player/snapcast.py
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -78,6 +78,7 @@ venv
|
||||
.venv
|
||||
Pipfile*
|
||||
share/*
|
||||
Scripts/
|
||||
|
||||
# vimmy stuff
|
||||
*.swp
|
||||
|
@ -1,2 +0,0 @@
|
||||
[settings]
|
||||
multi_line_output=4
|
@ -185,7 +185,6 @@ homeassistant/components/edp_redy.py @abmantis
|
||||
homeassistant/components/eight_sleep.py @mezz64
|
||||
homeassistant/components/*/eight_sleep.py @mezz64
|
||||
homeassistant/components/esphome/*.py @OttoWinter
|
||||
homeassistant/components/*/esphome.py @OttoWinter
|
||||
|
||||
# H
|
||||
homeassistant/components/hive.py @Rendili @KJonline
|
||||
@ -219,7 +218,6 @@ homeassistant/components/*/ness_alarm.py @nickw444
|
||||
|
||||
# O
|
||||
homeassistant/components/openuv/* @bachya
|
||||
homeassistant/components/*/openuv.py @bachya
|
||||
|
||||
# P
|
||||
homeassistant/components/point/* @fredrike
|
||||
@ -231,13 +229,11 @@ homeassistant/components/*/qwikswitch.py @kellerza
|
||||
|
||||
# R
|
||||
homeassistant/components/rainmachine/* @bachya
|
||||
homeassistant/components/*/rainmachine.py @bachya
|
||||
homeassistant/components/*/random.py @fabaff
|
||||
homeassistant/components/*/rfxtrx.py @danielhiversen
|
||||
|
||||
# S
|
||||
homeassistant/components/simplisafe/* @bachya
|
||||
homeassistant/components/*/simplisafe.py @bachya
|
||||
|
||||
# T
|
||||
homeassistant/components/tahoma.py @philklei
|
||||
|
@ -16,7 +16,6 @@ LABEL maintainer="Paulus Schoutsen <Paulus@PaulusSchoutsen.nl>"
|
||||
|
||||
VOLUME /config
|
||||
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Copy build scripts
|
||||
|
@ -1,6 +1,7 @@
|
||||
"""Home Assistant auth provider."""
|
||||
import base64
|
||||
from collections import OrderedDict
|
||||
import logging
|
||||
from typing import Any, Dict, List, Optional, cast
|
||||
|
||||
import bcrypt
|
||||
@ -51,6 +52,15 @@ class Data:
|
||||
self._store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY,
|
||||
private=True)
|
||||
self._data = None # type: Optional[Dict[str, Any]]
|
||||
self.is_legacy = False
|
||||
|
||||
@callback
|
||||
def normalize_username(self, username: str) -> str:
|
||||
"""Normalize a username based on the mode."""
|
||||
if self.is_legacy:
|
||||
return username
|
||||
|
||||
return username.strip()
|
||||
|
||||
async def async_load(self) -> None:
|
||||
"""Load stored data."""
|
||||
@ -61,6 +71,20 @@ class Data:
|
||||
'users': []
|
||||
}
|
||||
|
||||
for user in data['users']:
|
||||
username = user['username']
|
||||
|
||||
# check if we have unstripped usernames
|
||||
if username != username.strip():
|
||||
self.is_legacy = True
|
||||
|
||||
logging.getLogger(__name__).warning(
|
||||
"Home Assistant auth provider is running in legacy mode "
|
||||
"because we detected usernames that start or end in a "
|
||||
"space. Please change the username.")
|
||||
|
||||
break
|
||||
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
@ -73,6 +97,7 @@ class Data:
|
||||
|
||||
Raises InvalidAuth if auth invalid.
|
||||
"""
|
||||
username = self.normalize_username(username)
|
||||
dummy = b'$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO'
|
||||
found = None
|
||||
|
||||
@ -105,7 +130,10 @@ class Data:
|
||||
|
||||
def add_auth(self, username: str, password: str) -> None:
|
||||
"""Add a new authenticated user/pass."""
|
||||
if any(user['username'] == username for user in self.users):
|
||||
username = self.normalize_username(username)
|
||||
|
||||
if any(self.normalize_username(user['username']) == username
|
||||
for user in self.users):
|
||||
raise InvalidUser
|
||||
|
||||
self.users.append({
|
||||
@ -116,9 +144,11 @@ class Data:
|
||||
@callback
|
||||
def async_remove_auth(self, username: str) -> None:
|
||||
"""Remove authentication."""
|
||||
username = self.normalize_username(username)
|
||||
|
||||
index = None
|
||||
for i, user in enumerate(self.users):
|
||||
if user['username'] == username:
|
||||
if self.normalize_username(user['username']) == username:
|
||||
index = i
|
||||
break
|
||||
|
||||
@ -132,8 +162,10 @@ class Data:
|
||||
|
||||
Raises InvalidUser if user cannot be found.
|
||||
"""
|
||||
username = self.normalize_username(username)
|
||||
|
||||
for user in self.users:
|
||||
if user['username'] == username:
|
||||
if self.normalize_username(user['username']) == username:
|
||||
user['password'] = self.hash_password(
|
||||
new_password, True).decode()
|
||||
break
|
||||
@ -178,10 +210,15 @@ class HassAuthProvider(AuthProvider):
|
||||
async def async_get_or_create_credentials(
|
||||
self, flow_result: Dict[str, str]) -> Credentials:
|
||||
"""Get credentials based on the flow result."""
|
||||
username = flow_result['username']
|
||||
if self.data is None:
|
||||
await self.async_initialize()
|
||||
assert self.data is not None
|
||||
|
||||
norm_username = self.data.normalize_username
|
||||
username = norm_username(flow_result['username'])
|
||||
|
||||
for credential in await self.async_credentials():
|
||||
if credential.data['username'] == username:
|
||||
if norm_username(credential.data['username']) == username:
|
||||
return credential
|
||||
|
||||
# Create new credentials.
|
||||
|
@ -18,7 +18,7 @@ from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers import discovery
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
REQUIREMENTS = ['abodepy==0.14.0']
|
||||
REQUIREMENTS = ['abodepy==0.15.0']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -46,8 +46,8 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
|
||||
SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({
|
||||
vol.Required(CONF_ADS_TYPE):
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]),
|
||||
vol.Required(CONF_ADS_VALUE): cv.match_all,
|
||||
vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE, ADSTYPE_BOOL]),
|
||||
vol.Required(CONF_ADS_VALUE): vol.Coerce(int),
|
||||
vol.Required(CONF_ADS_VAR): cv.string,
|
||||
})
|
||||
|
||||
|
@ -21,6 +21,8 @@ from homeassistant.helpers.entity_component import EntityComponent
|
||||
DOMAIN = 'alarm_control_panel'
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
ATTR_CHANGED_BY = 'changed_by'
|
||||
FORMAT_TEXT = 'text'
|
||||
FORMAT_NUMBER = 'number'
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
|
@ -99,7 +99,7 @@ class AlarmDecoderAlarmPanel(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -81,8 +81,8 @@ class AlarmDotCom(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -17,7 +17,7 @@ from homeassistant.components.arlo import (
|
||||
DATA_ARLO, CONF_ATTRIBUTION, SIGNAL_UPDATE_ARLO)
|
||||
from homeassistant.const import (
|
||||
ATTR_ATTRIBUTION, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_DISARMED)
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_NIGHT)
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -25,6 +25,7 @@ ARMED = 'armed'
|
||||
|
||||
CONF_HOME_MODE_NAME = 'home_mode_name'
|
||||
CONF_AWAY_MODE_NAME = 'away_mode_name'
|
||||
CONF_NIGHT_MODE_NAME = 'night_mode_name'
|
||||
|
||||
DEPENDENCIES = ['arlo']
|
||||
|
||||
@ -35,6 +36,7 @@ ICON = 'mdi:security'
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_HOME_MODE_NAME, default=ARMED): cv.string,
|
||||
vol.Optional(CONF_AWAY_MODE_NAME, default=ARMED): cv.string,
|
||||
vol.Optional(CONF_NIGHT_MODE_NAME, default=ARMED): cv.string,
|
||||
})
|
||||
|
||||
|
||||
@ -47,21 +49,23 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
|
||||
home_mode_name = config.get(CONF_HOME_MODE_NAME)
|
||||
away_mode_name = config.get(CONF_AWAY_MODE_NAME)
|
||||
night_mode_name = config.get(CONF_NIGHT_MODE_NAME)
|
||||
base_stations = []
|
||||
for base_station in arlo.base_stations:
|
||||
base_stations.append(ArloBaseStation(base_station, home_mode_name,
|
||||
away_mode_name))
|
||||
away_mode_name, night_mode_name))
|
||||
add_entities(base_stations, True)
|
||||
|
||||
|
||||
class ArloBaseStation(AlarmControlPanel):
|
||||
"""Representation of an Arlo Alarm Control Panel."""
|
||||
|
||||
def __init__(self, data, home_mode_name, away_mode_name):
|
||||
def __init__(self, data, home_mode_name, away_mode_name, night_mode_name):
|
||||
"""Initialize the alarm control panel."""
|
||||
self._base_station = data
|
||||
self._home_mode_name = home_mode_name
|
||||
self._away_mode_name = away_mode_name
|
||||
self._night_mode_name = night_mode_name
|
||||
self._state = None
|
||||
|
||||
@property
|
||||
@ -105,6 +109,10 @@ class ArloBaseStation(AlarmControlPanel):
|
||||
"""Send arm home command. Uses custom mode."""
|
||||
self._base_station.mode = self._home_mode_name
|
||||
|
||||
async def async_alarm_arm_night(self, code=None):
|
||||
"""Send arm night command. Uses custom mode."""
|
||||
self._base_station.mode = self._night_mode_name
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the base station."""
|
||||
@ -128,4 +136,6 @@ class ArloBaseStation(AlarmControlPanel):
|
||||
return STATE_ALARM_ARMED_HOME
|
||||
if mode == self._away_mode_name:
|
||||
return STATE_ALARM_ARMED_AWAY
|
||||
if mode == self._night_mode_name:
|
||||
return STATE_ALARM_ARMED_NIGHT
|
||||
return mode
|
||||
|
@ -80,7 +80,7 @@ class Concord232Alarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the characters if code is defined."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -116,7 +116,7 @@ class ElkArea(ElkEntity, alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the alarm code format."""
|
||||
return '^[0-9]{4}([0-9]{2})?$'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -104,7 +104,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, alarm.AlarmControlPanel):
|
||||
"""Regex for code format or None if no code is required."""
|
||||
if self._code:
|
||||
return None
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -0,0 +1,117 @@
|
||||
"""
|
||||
Support for Homekit Alarm Control Panel.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/alarm_control_panel.homekit_controller/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.homekit_controller import (HomeKitEntity,
|
||||
KNOWN_ACCESSORIES)
|
||||
from homeassistant.components.alarm_control_panel import AlarmControlPanel
|
||||
from homeassistant.const import (
|
||||
STATE_ALARM_DISARMED, STATE_ALARM_ARMED_AWAY, STATE_ALARM_ARMED_HOME,
|
||||
STATE_ALARM_ARMED_NIGHT, STATE_ALARM_TRIGGERED)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
|
||||
DEPENDENCIES = ['homekit_controller']
|
||||
|
||||
ICON = 'mdi:security'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CURRENT_STATE_MAP = {
|
||||
0: STATE_ALARM_ARMED_HOME,
|
||||
1: STATE_ALARM_ARMED_AWAY,
|
||||
2: STATE_ALARM_ARMED_NIGHT,
|
||||
3: STATE_ALARM_DISARMED,
|
||||
4: STATE_ALARM_TRIGGERED
|
||||
}
|
||||
|
||||
TARGET_STATE_MAP = {
|
||||
STATE_ALARM_ARMED_HOME: 0,
|
||||
STATE_ALARM_ARMED_AWAY: 1,
|
||||
STATE_ALARM_ARMED_NIGHT: 2,
|
||||
STATE_ALARM_DISARMED: 3,
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up Homekit Alarm Control Panel support."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']]
|
||||
add_entities([HomeKitAlarmControlPanel(accessory, discovery_info)],
|
||||
True)
|
||||
|
||||
|
||||
class HomeKitAlarmControlPanel(HomeKitEntity, AlarmControlPanel):
|
||||
"""Representation of a Homekit Alarm Control Panel."""
|
||||
|
||||
def __init__(self, *args):
|
||||
"""Initialise the Alarm Control Panel."""
|
||||
super().__init__(*args)
|
||||
self._state = None
|
||||
self._battery_level = None
|
||||
|
||||
def update_characteristics(self, characteristics):
|
||||
"""Synchronise the Alarm Control Panel state with Home Assistant."""
|
||||
# pylint: disable=import-error
|
||||
from homekit.model.characteristics import CharacteristicsTypes
|
||||
|
||||
for characteristic in characteristics:
|
||||
ctype = characteristic['type']
|
||||
ctype = CharacteristicsTypes.get_short(ctype)
|
||||
if ctype == "security-system-state.current":
|
||||
self._chars['security-system-state.current'] = \
|
||||
characteristic['iid']
|
||||
self._state = CURRENT_STATE_MAP[characteristic['value']]
|
||||
elif ctype == "security-system-state.target":
|
||||
self._chars['security-system-state.target'] = \
|
||||
characteristic['iid']
|
||||
elif ctype == "battery-level":
|
||||
self._chars['battery-level'] = characteristic['iid']
|
||||
self._battery_level = characteristic['value']
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return icon."""
|
||||
return ICON
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the device."""
|
||||
return self._state
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
self.set_alarm_state(STATE_ALARM_DISARMED, code)
|
||||
|
||||
def alarm_arm_away(self, code=None):
|
||||
"""Send arm command."""
|
||||
self.set_alarm_state(STATE_ALARM_ARMED_AWAY, code)
|
||||
|
||||
def alarm_arm_home(self, code=None):
|
||||
"""Send stay command."""
|
||||
self.set_alarm_state(STATE_ALARM_ARMED_HOME, code)
|
||||
|
||||
def alarm_arm_night(self, code=None):
|
||||
"""Send night command."""
|
||||
self.set_alarm_state(STATE_ALARM_ARMED_NIGHT, code)
|
||||
|
||||
def set_alarm_state(self, state, code=None):
|
||||
"""Send state command."""
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['security-system-state.target'],
|
||||
'value': TARGET_STATE_MAP[state]}]
|
||||
self.put_characteristics(characteristics)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
if self._battery_level is None:
|
||||
return None
|
||||
|
||||
return {
|
||||
ATTR_BATTERY_LEVEL: self._battery_level,
|
||||
}
|
@ -82,8 +82,8 @@ class IAlarmPanel(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -129,8 +129,8 @@ class IFTTTAlarmPanel(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
@ -207,8 +207,8 @@ class ManualAlarm(alarm.AlarmControlPanel, RestoreEntity):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
@ -241,8 +241,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
|
||||
if self._code is None:
|
||||
return None
|
||||
if isinstance(self._code, str) and re.search('^\\d+$', self._code):
|
||||
return 'Number'
|
||||
return 'Any'
|
||||
return alarm.FORMAT_NUMBER
|
||||
return alarm.FORMAT_TEXT
|
||||
|
||||
def alarm_disarm(self, code=None):
|
||||
"""Send disarm command."""
|
||||
|
@ -59,7 +59,7 @@ class NessAlarmPanel(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the regex for code format or None if no code is required."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -70,7 +70,7 @@ class NX584Alarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -64,7 +64,7 @@ class SatelIntegraAlarmPanel(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return the regex for code format or None if no code is required."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
|
@ -61,7 +61,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
|
||||
@property
|
||||
def code_format(self):
|
||||
"""Return one or more digits/characters."""
|
||||
return 'Number'
|
||||
return alarm.FORMAT_NUMBER
|
||||
|
||||
@property
|
||||
def changed_by(self):
|
||||
|
@ -46,9 +46,7 @@ ALERT_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NOTIFIERS): cv.ensure_list})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: ALERT_SCHEMA,
|
||||
}),
|
||||
DOMAIN: cv.schema_with_slug_keys(ALERT_SCHEMA),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
|
@ -72,6 +72,6 @@ async def async_setup(hass, config):
|
||||
pass
|
||||
else:
|
||||
smart_home_config = smart_home_config or SMART_HOME_SCHEMA({})
|
||||
smart_home.async_setup(hass, smart_home_config)
|
||||
await smart_home.async_setup(hass, smart_home_config)
|
||||
|
||||
return True
|
||||
|
@ -27,8 +27,9 @@ from homeassistant.const import (
|
||||
CONF_NAME, SERVICE_LOCK, SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE,
|
||||
SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
|
||||
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON, STATE_UNLOCKED,
|
||||
TEMP_CELSIUS, TEMP_FAHRENHEIT, MATCH_ALL)
|
||||
SERVICE_UNLOCK, SERVICE_VOLUME_SET, STATE_LOCKED, STATE_ON,
|
||||
STATE_UNAVAILABLE, STATE_UNLOCKED, TEMP_CELSIUS, TEMP_FAHRENHEIT,
|
||||
MATCH_ALL)
|
||||
import homeassistant.core as ha
|
||||
import homeassistant.util.color as color_util
|
||||
from homeassistant.util.decorator import Registry
|
||||
@ -393,6 +394,37 @@ class _AlexaInterface:
|
||||
}
|
||||
|
||||
|
||||
class _AlexaEndpointHealth(_AlexaInterface):
|
||||
"""Implements Alexa.EndpointHealth.
|
||||
|
||||
https://developer.amazon.com/docs/smarthome/state-reporting-for-a-smart-home-skill.html#report-state-when-alexa-requests-it
|
||||
"""
|
||||
|
||||
def __init__(self, hass, entity):
|
||||
super().__init__(entity)
|
||||
self.hass = hass
|
||||
|
||||
def name(self):
|
||||
return 'Alexa.EndpointHealth'
|
||||
|
||||
def properties_supported(self):
|
||||
return [{'name': 'connectivity'}]
|
||||
|
||||
def properties_proactively_reported(self):
|
||||
return False
|
||||
|
||||
def properties_retrievable(self):
|
||||
return True
|
||||
|
||||
def get_property(self, name):
|
||||
if name != 'connectivity':
|
||||
raise _UnsupportedProperty(name)
|
||||
|
||||
if self.entity.state == STATE_UNAVAILABLE:
|
||||
return {'value': 'UNREACHABLE'}
|
||||
return {'value': 'OK'}
|
||||
|
||||
|
||||
class _AlexaPowerController(_AlexaInterface):
|
||||
"""Implements Alexa.PowerController.
|
||||
|
||||
@ -769,7 +801,8 @@ class _GenericCapabilities(_AlexaEntity):
|
||||
return [_DisplayCategory.OTHER]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaPowerController(self.entity)]
|
||||
return [_AlexaPowerController(self.entity),
|
||||
_AlexaEndpointHealth(self.hass, self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(switch.DOMAIN)
|
||||
@ -778,7 +811,8 @@ class _SwitchCapabilities(_AlexaEntity):
|
||||
return [_DisplayCategory.SWITCH]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaPowerController(self.entity)]
|
||||
return [_AlexaPowerController(self.entity),
|
||||
_AlexaEndpointHealth(self.hass, self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(climate.DOMAIN)
|
||||
@ -792,6 +826,7 @@ class _ClimateCapabilities(_AlexaEntity):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
yield _AlexaThermostatController(self.hass, self.entity)
|
||||
yield _AlexaTemperatureSensor(self.hass, self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(cover.DOMAIN)
|
||||
@ -804,6 +839,7 @@ class _CoverCapabilities(_AlexaEntity):
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & cover.SUPPORT_SET_POSITION:
|
||||
yield _AlexaPercentageController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(light.DOMAIN)
|
||||
@ -821,6 +857,7 @@ class _LightCapabilities(_AlexaEntity):
|
||||
yield _AlexaColorController(self.entity)
|
||||
if supported & light.SUPPORT_COLOR_TEMP:
|
||||
yield _AlexaColorTemperatureController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(fan.DOMAIN)
|
||||
@ -833,6 +870,7 @@ class _FanCapabilities(_AlexaEntity):
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & fan.SUPPORT_SET_SPEED:
|
||||
yield _AlexaPercentageController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(lock.DOMAIN)
|
||||
@ -841,7 +879,8 @@ class _LockCapabilities(_AlexaEntity):
|
||||
return [_DisplayCategory.SMARTLOCK]
|
||||
|
||||
def interfaces(self):
|
||||
return [_AlexaLockController(self.entity)]
|
||||
return [_AlexaLockController(self.entity),
|
||||
_AlexaEndpointHealth(self.hass, self.entity)]
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
|
||||
@ -851,6 +890,7 @@ class _MediaPlayerCapabilities(_AlexaEntity):
|
||||
|
||||
def interfaces(self):
|
||||
yield _AlexaPowerController(self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
|
||||
if supported & media_player.SUPPORT_VOLUME_SET:
|
||||
@ -913,6 +953,7 @@ class _SensorCapabilities(_AlexaEntity):
|
||||
TEMP_CELSIUS,
|
||||
):
|
||||
yield _AlexaTemperatureSensor(self.hass, self.entity)
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
|
||||
@ENTITY_ADAPTERS.register(binary_sensor.DOMAIN)
|
||||
@ -934,6 +975,8 @@ class _BinarySensorCapabilities(_AlexaEntity):
|
||||
elif sensor_type is self.TYPE_MOTION:
|
||||
yield _AlexaMotionSensor(self.hass, self.entity)
|
||||
|
||||
yield _AlexaEndpointHealth(self.hass, self.entity)
|
||||
|
||||
def get_type(self):
|
||||
"""Return the type of binary sensor."""
|
||||
attrs = self.entity.attributes
|
||||
@ -993,8 +1036,7 @@ class Config:
|
||||
self.entity_config = entity_config or {}
|
||||
|
||||
|
||||
@ha.callback
|
||||
def async_setup(hass, config):
|
||||
async def async_setup(hass, config):
|
||||
"""Activate Smart Home functionality of Alexa component.
|
||||
|
||||
This is optional, triggered by having a `smart_home:` sub-section in the
|
||||
@ -1020,8 +1062,7 @@ def async_setup(hass, config):
|
||||
hass.http.register_view(SmartHomeView(smart_home_config))
|
||||
|
||||
if AUTH_KEY in hass.data:
|
||||
hass.loop.create_task(
|
||||
async_enable_proactive_mode(hass, smart_home_config))
|
||||
await async_enable_proactive_mode(hass, smart_home_config)
|
||||
|
||||
|
||||
async def async_enable_proactive_mode(hass, smart_home_config):
|
||||
@ -1337,8 +1378,7 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
return
|
||||
|
||||
headers = {
|
||||
"Authorization": "Bearer {}".format(token),
|
||||
"Content-Type": "application/json;charset=UTF-8"
|
||||
"Authorization": "Bearer {}".format(token)
|
||||
}
|
||||
|
||||
endpoint = alexa_entity.entity_id()
|
||||
@ -1359,14 +1399,14 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
payload=payload)
|
||||
message.set_endpoint_full(token, endpoint)
|
||||
|
||||
message_str = json.dumps(message.serialize())
|
||||
message_serialized = message.serialize()
|
||||
|
||||
try:
|
||||
session = aiohttp_client.async_get_clientsession(hass)
|
||||
with async_timeout.timeout(DEFAULT_TIMEOUT, loop=hass.loop):
|
||||
response = await session.post(config.endpoint,
|
||||
headers=headers,
|
||||
data=message_str,
|
||||
json=message_serialized,
|
||||
allow_redirects=True)
|
||||
|
||||
except (asyncio.TimeoutError, aiohttp.ClientError):
|
||||
@ -1375,7 +1415,7 @@ async def async_send_changereport_message(hass, config, alexa_entity):
|
||||
|
||||
response_text = await response.text()
|
||||
|
||||
_LOGGER.debug("Sent: %s", message_str)
|
||||
_LOGGER.debug("Sent: %s", json.dumps(message_serialized))
|
||||
_LOGGER.debug("Received (%s): %s", response.status, response_text)
|
||||
|
||||
if response.status != 202:
|
||||
|
@ -26,6 +26,8 @@ CONF_SSH_KEY = 'ssh_key'
|
||||
CONF_REQUIRE_IP = 'require_ip'
|
||||
DEFAULT_SSH_PORT = 22
|
||||
SECRET_GROUP = 'Password or SSH Key'
|
||||
CONF_SENSORS = 'sensors'
|
||||
SENSOR_TYPES = ['upload_speed', 'download_speed', 'download', 'upload']
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
@ -37,7 +39,9 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean,
|
||||
vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string,
|
||||
vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile,
|
||||
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile
|
||||
vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile,
|
||||
vol.Optional(CONF_SENSORS): vol.All(
|
||||
cv.ensure_list, [vol.In(SENSOR_TYPES)]),
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
@ -62,7 +66,8 @@ async def async_setup(hass, config):
|
||||
hass.data[DATA_ASUSWRT] = api
|
||||
|
||||
hass.async_create_task(async_load_platform(
|
||||
hass, 'sensor', DOMAIN, {}, config))
|
||||
hass, 'sensor', DOMAIN, config[DOMAIN].get(CONF_SENSORS), config))
|
||||
hass.async_create_task(async_load_platform(
|
||||
hass, 'device_tracker', DOMAIN, {}, config))
|
||||
|
||||
return True
|
||||
|
@ -9,11 +9,11 @@
|
||||
},
|
||||
"step": {
|
||||
"init": {
|
||||
"description": "Bitte w\u00e4hle einen der Benachrichtigungsdienste:",
|
||||
"description": "Bitte w\u00e4hlen Sie einen der Benachrichtigungsdienste:",
|
||||
"title": "Einmal Passwort f\u00fcr Notify einrichten"
|
||||
},
|
||||
"setup": {
|
||||
"description": "Ein Einmal-Passwort wurde per ** notify gesendet. {notify_service} **. Bitte gebe es unten ein:",
|
||||
"description": "Ein Einmal-Passwort wurde per **notify.{notify_service}** gesendet. Bitte geben Sie es unten ein:",
|
||||
"title": "\u00dcberpr\u00fcfe das Setup"
|
||||
}
|
||||
},
|
||||
|
7
homeassistant/components/auth/.translations/et.json
Normal file
7
homeassistant/components/auth/.translations/et.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"mfa_setup": {
|
||||
"totp": {
|
||||
"title": ""
|
||||
}
|
||||
}
|
||||
}
|
@ -375,8 +375,6 @@ def _async_get_action(hass, config, name):
|
||||
async def action(entity_id, variables, context):
|
||||
"""Execute an action."""
|
||||
_LOGGER.info('Executing %s', name)
|
||||
hass.components.logbook.async_log_entry(
|
||||
name, 'has been triggered', DOMAIN, entity_id)
|
||||
|
||||
try:
|
||||
await script_obj.async_run(variables, context)
|
||||
|
@ -1,9 +1,9 @@
|
||||
"""
|
||||
Offer geo location automation rules.
|
||||
Offer geolocation automation rules.
|
||||
|
||||
For more details about this automation trigger, please refer to the
|
||||
documentation at
|
||||
https://home-assistant.io/docs/automation/trigger/#geo-location-trigger
|
||||
https://home-assistant.io/docs/automation/trigger/#geolocation-trigger
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
|
@ -13,30 +13,18 @@ from homeassistant.const import CONF_AT, CONF_PLATFORM
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_time_change
|
||||
|
||||
CONF_HOURS = 'hours'
|
||||
CONF_MINUTES = 'minutes'
|
||||
CONF_SECONDS = 'seconds'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
TRIGGER_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'time',
|
||||
CONF_AT: cv.time,
|
||||
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT))
|
||||
vol.Required(CONF_AT): cv.time,
|
||||
})
|
||||
|
||||
|
||||
async def async_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
if CONF_AT in config:
|
||||
at_time = config.get(CONF_AT)
|
||||
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
|
||||
else:
|
||||
hours = config.get(CONF_HOURS)
|
||||
minutes = config.get(CONF_MINUTES)
|
||||
seconds = config.get(CONF_SECONDS)
|
||||
at_time = config.get(CONF_AT)
|
||||
hours, minutes, seconds = at_time.hour, at_time.minute, at_time.second
|
||||
|
||||
@callback
|
||||
def time_automation_listener(now):
|
||||
|
53
homeassistant/components/automation/time_pattern.py
Normal file
53
homeassistant/components/automation/time_pattern.py
Normal file
@ -0,0 +1,53 @@
|
||||
"""
|
||||
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 logging
|
||||
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.const import CONF_PLATFORM
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.event import async_track_time_change
|
||||
|
||||
CONF_HOURS = 'hours'
|
||||
CONF_MINUTES = 'minutes'
|
||||
CONF_SECONDS = 'seconds'
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
TRIGGER_SCHEMA = vol.All(vol.Schema({
|
||||
vol.Required(CONF_PLATFORM): 'time_pattern',
|
||||
CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)),
|
||||
}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS))
|
||||
|
||||
|
||||
async def async_trigger(hass, config, action, automation_info):
|
||||
"""Listen for state changes based on configuration."""
|
||||
hours = config.get(CONF_HOURS)
|
||||
minutes = config.get(CONF_MINUTES)
|
||||
seconds = config.get(CONF_SECONDS)
|
||||
|
||||
# If larger units are specified, default the smaller units to zero
|
||||
if minutes is None and hours is not None:
|
||||
minutes = 0
|
||||
if seconds is None and minutes is not None:
|
||||
seconds = 0
|
||||
|
||||
@callback
|
||||
def time_automation_listener(now):
|
||||
"""Listen for time changes and calls action."""
|
||||
hass.async_run_job(action, {
|
||||
'trigger': {
|
||||
'platform': 'time_pattern',
|
||||
'now': now,
|
||||
},
|
||||
})
|
||||
|
||||
return async_track_time_change(hass, time_automation_listener,
|
||||
hour=hours, minute=minutes, second=seconds)
|
@ -50,9 +50,7 @@ DEVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: DEVICE_SCHEMA,
|
||||
}),
|
||||
DOMAIN: cv.schema_with_slug_keys(DEVICE_SCHEMA),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
SERVICE_VAPIX_CALL = 'vapix_call'
|
||||
|
@ -1,147 +0,0 @@
|
||||
"""
|
||||
Support for deCONZ binary sensor.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.deconz/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.deconz.const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DECONZ_REACHABLE,
|
||||
DOMAIN as DECONZ_DOMAIN)
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Old way of setting up deCONZ binary sensors."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the deCONZ binary sensor."""
|
||||
gateway = hass.data[DECONZ_DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_sensor(sensors):
|
||||
"""Add binary sensor from deCONZ."""
|
||||
from pydeconz.sensor import DECONZ_BINARY_SENSOR
|
||||
entities = []
|
||||
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
for sensor in sensors:
|
||||
if sensor.type in DECONZ_BINARY_SENSOR and \
|
||||
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
|
||||
entities.append(DeconzBinarySensor(sensor, gateway))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
|
||||
|
||||
async_add_sensor(gateway.api.sensors.values())
|
||||
|
||||
|
||||
class DeconzBinarySensor(BinarySensorDevice):
|
||||
"""Representation of a binary sensor."""
|
||||
|
||||
def __init__(self, sensor, gateway):
|
||||
"""Set up sensor and add update callback to get data from websocket."""
|
||||
self._sensor = sensor
|
||||
self.gateway = gateway
|
||||
self.unsub_dispatcher = None
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe sensors events."""
|
||||
self._sensor.register_async_callback(self.async_update_callback)
|
||||
self.gateway.deconz_ids[self.entity_id] = self._sensor.deconz_id
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, DECONZ_REACHABLE, self.async_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect sensor object when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
self._sensor.remove_callback(self.async_update_callback)
|
||||
self._sensor = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the sensor's state.
|
||||
|
||||
If reason is that state is updated,
|
||||
or reachable has changed or battery has changed.
|
||||
"""
|
||||
if reason['state'] or \
|
||||
'reachable' in reason['attr'] or \
|
||||
'battery' in reason['attr'] or \
|
||||
'on' in reason['attr']:
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if sensor is on."""
|
||||
return self._sensor.is_tripped
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the sensor."""
|
||||
return self._sensor.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this sensor."""
|
||||
return self._sensor.uniqueid
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return self._sensor.sensor_class
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._sensor.sensor_icon
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if sensor is available."""
|
||||
return self.gateway.available and self._sensor.reachable
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the sensor."""
|
||||
from pydeconz.sensor import PRESENCE
|
||||
attr = {}
|
||||
if self._sensor.battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self._sensor.battery
|
||||
if self._sensor.on is not None:
|
||||
attr[ATTR_ON] = self._sensor.on
|
||||
if self._sensor.type in PRESENCE and self._sensor.dark is not None:
|
||||
attr[ATTR_DARK] = self._sensor.dark
|
||||
return attr
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._sensor.uniqueid is None or
|
||||
self._sensor.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._sensor.uniqueid.split('-', 1)[0]
|
||||
bridgeid = self.gateway.api.config.bridgeid
|
||||
return {
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._sensor.manufacturer,
|
||||
'model': self._sensor.modelid,
|
||||
'name': self._sensor.name,
|
||||
'sw_version': self._sensor.swversion,
|
||||
'via_hub': (DECONZ_DOMAIN, bridgeid),
|
||||
}
|
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDevice, ENTITY_ID_FORMAT)
|
||||
from homeassistant.components.fibaro import (
|
||||
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
|
||||
FIBARO_DEVICES, FibaroDevice)
|
||||
from homeassistant.const import (CONF_DEVICE_CLASS, CONF_ICON)
|
||||
|
||||
DEPENDENCIES = ['fibaro']
|
||||
@ -33,17 +33,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
return
|
||||
|
||||
add_entities(
|
||||
[FibaroBinarySensor(device, hass.data[FIBARO_CONTROLLER])
|
||||
[FibaroBinarySensor(device)
|
||||
for device in hass.data[FIBARO_DEVICES]['binary_sensor']], True)
|
||||
|
||||
|
||||
class FibaroBinarySensor(FibaroDevice, BinarySensorDevice):
|
||||
"""Representation of a Fibaro Binary Sensor."""
|
||||
|
||||
def __init__(self, fibaro_device, controller):
|
||||
def __init__(self, fibaro_device):
|
||||
"""Initialize the binary_sensor."""
|
||||
self._state = None
|
||||
super().__init__(fibaro_device, controller)
|
||||
super().__init__(fibaro_device)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||
stype = None
|
||||
devconf = fibaro_device.device_config
|
||||
|
@ -5,7 +5,7 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.hive/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
from homeassistant.components.hive import DATA_HIVE, DOMAIN
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
|
||||
@ -35,9 +35,24 @@ class HiveBinarySensorEntity(BinarySensorDevice):
|
||||
self.attributes = {}
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
|
||||
self._unique_id = '{}-{}'.format(self.node_id, self.device_type)
|
||||
self.session.entities.append(self)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID of entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {
|
||||
'identifiers': {
|
||||
(DOMAIN, self.unique_id)
|
||||
},
|
||||
'name': self.name
|
||||
}
|
||||
|
||||
def handle_update(self, updatesource):
|
||||
"""Handle the new update request."""
|
||||
if '{}.{}'.format(self.device_type, self.node_id) not in updatesource:
|
||||
|
@ -41,7 +41,7 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
|
||||
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
@ -51,7 +51,7 @@ SENSOR_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
|
||||
vol.Required(CONF_SENSORS): cv.schema_with_slug_keys(SENSOR_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.const import CONF_NAME, WEEKDAYS
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
REQUIREMENTS = ['holidays==0.9.8']
|
||||
REQUIREMENTS = ['holidays==0.9.9']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -26,6 +26,7 @@ ALL_COUNTRIES = [
|
||||
'Canada', 'CA', 'Colombia', 'CO', 'Croatia', 'HR', 'Czech', 'CZ',
|
||||
'Denmark', 'DK', 'England', 'EuropeanCentralBank', 'ECB', 'TAR',
|
||||
'Finland', 'FI', 'France', 'FRA', 'Germany', 'DE', 'Hungary', 'HU',
|
||||
'Honduras', 'HUD',
|
||||
'India', 'IND', 'Ireland', 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP',
|
||||
'Mexico', 'MX', 'Netherlands', 'NL', 'NewZealand', 'NZ',
|
||||
'Northern Ireland', 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT',
|
||||
|
@ -107,7 +107,7 @@ class XiaomiBinarySensor(XiaomiDevice, BinarySensorDevice):
|
||||
|
||||
def update(self):
|
||||
"""Update the sensor state."""
|
||||
_LOGGER.debug('Updating xiaomi sensor by polling')
|
||||
_LOGGER.debug('Updating xiaomi sensor (%s) by polling', self._sid)
|
||||
self._get_from_hub(self._sid)
|
||||
|
||||
|
||||
@ -178,7 +178,28 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
def parse_data(self, data, raw_data):
|
||||
"""Parse data sent by gateway."""
|
||||
"""Parse data sent by gateway.
|
||||
|
||||
Polling (proto v1, firmware version 1.4.1_159.0143)
|
||||
|
||||
>> { "cmd":"read","sid":"158..."}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'read_ack', 'data': '{"voltage":3005}'}
|
||||
|
||||
Multicast messages (proto v1, firmware version 1.4.1_159.0143)
|
||||
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"status":"motion"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"no_motion":"120"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"no_motion":"180"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'report', 'data': '{"no_motion":"300"}'}
|
||||
<< {'model': 'motion', 'sid': '158...', 'short_id': 26331,
|
||||
'cmd': 'heartbeat', 'data': '{"voltage":3005}'}
|
||||
|
||||
"""
|
||||
if raw_data['cmd'] == 'heartbeat':
|
||||
_LOGGER.debug(
|
||||
'Skipping heartbeat of the motion sensor. '
|
||||
@ -187,8 +208,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
'11631#issuecomment-357507744).')
|
||||
return
|
||||
|
||||
self._should_poll = False
|
||||
if NO_MOTION in data: # handle push from the hub
|
||||
if NO_MOTION in data:
|
||||
self._no_motion_since = data[NO_MOTION]
|
||||
self._state = False
|
||||
return True
|
||||
@ -203,26 +223,20 @@ class XiaomiMotionSensor(XiaomiBinarySensor):
|
||||
self._unsub_set_no_motion()
|
||||
self._unsub_set_no_motion = async_call_later(
|
||||
self._hass,
|
||||
180,
|
||||
120,
|
||||
self._async_set_no_motion
|
||||
)
|
||||
else:
|
||||
self._should_poll = True
|
||||
if self.entity_id is not None:
|
||||
self._hass.bus.fire('xiaomi_aqara.motion', {
|
||||
'entity_id': self.entity_id
|
||||
})
|
||||
|
||||
if self.entity_id is not None:
|
||||
self._hass.bus.fire('xiaomi_aqara.motion', {
|
||||
'entity_id': self.entity_id
|
||||
})
|
||||
|
||||
self._no_motion_since = 0
|
||||
if self._state:
|
||||
return False
|
||||
self._state = True
|
||||
return True
|
||||
if value == NO_MOTION:
|
||||
if not self._state:
|
||||
return False
|
||||
self._state = False
|
||||
return True
|
||||
|
||||
|
||||
class XiaomiDoorSensor(XiaomiBinarySensor):
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import (
|
||||
CONF_BINARY_SENSORS, CONF_SENSORS, CONF_FILENAME,
|
||||
CONF_MONITORED_CONDITIONS, TEMP_FAHRENHEIT)
|
||||
|
||||
REQUIREMENTS = ['blinkpy==0.11.0']
|
||||
REQUIREMENTS = ['blinkpy==0.11.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -7,6 +7,7 @@ https://www.home-assistant.io/components/camera.proxy/
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from datetime import timedelta
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.camera import PLATFORM_SCHEMA, Camera
|
||||
@ -18,7 +19,7 @@ from homeassistant.util.async_ import run_coroutine_threadsafe
|
||||
import homeassistant.util.dt as dt_util
|
||||
from homeassistant.components.camera import async_get_still_stream
|
||||
|
||||
REQUIREMENTS = ['pillow==5.3.0']
|
||||
REQUIREMENTS = ['pillow==5.4.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -206,7 +207,7 @@ class ProxyCamera(Camera):
|
||||
self._cache_images = bool(
|
||||
config.get(CONF_IMAGE_REFRESH_RATE)
|
||||
or config.get(CONF_CACHE_IMAGES))
|
||||
self._last_image_time = 0
|
||||
self._last_image_time = dt_util.utc_from_timestamp(0)
|
||||
self._last_image = None
|
||||
self._headers = (
|
||||
{HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password}
|
||||
@ -223,7 +224,8 @@ class ProxyCamera(Camera):
|
||||
now = dt_util.utcnow()
|
||||
|
||||
if (self._image_refresh_rate and
|
||||
now < self._last_image_time + self._image_refresh_rate):
|
||||
now < self._last_image_time +
|
||||
timedelta(seconds=self._image_refresh_rate)):
|
||||
return self._last_image
|
||||
|
||||
self._last_image_time = now
|
||||
|
@ -107,7 +107,7 @@ class XiaomiCamera(Camera):
|
||||
_LOGGER.warning("There don't appear to be any folders")
|
||||
return False
|
||||
|
||||
first_dir = dirs[-1]
|
||||
first_dir = latest_dir = dirs[-1]
|
||||
try:
|
||||
ftp.cwd(first_dir)
|
||||
except error_perm as exc:
|
||||
|
@ -19,17 +19,18 @@ DEPENDENCIES = ['zoneminder']
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up the ZoneMinder cameras."""
|
||||
filter_urllib3_logging()
|
||||
zm_client = hass.data[ZONEMINDER_DOMAIN]
|
||||
|
||||
monitors = zm_client.get_monitors()
|
||||
if not monitors:
|
||||
_LOGGER.warning("Could not fetch monitors from ZoneMinder")
|
||||
return
|
||||
|
||||
cameras = []
|
||||
for monitor in monitors:
|
||||
_LOGGER.info("Initializing camera %s", monitor.id)
|
||||
cameras.append(ZoneMinderCamera(monitor, zm_client.verify_ssl))
|
||||
for zm_client in hass.data[ZONEMINDER_DOMAIN].values():
|
||||
monitors = zm_client.get_monitors()
|
||||
if not monitors:
|
||||
_LOGGER.warning(
|
||||
"Could not fetch monitors from ZoneMinder host: %s"
|
||||
)
|
||||
return
|
||||
|
||||
for monitor in monitors:
|
||||
_LOGGER.info("Initializing camera %s", monitor.id)
|
||||
cameras.append(ZoneMinderCamera(monitor, zm_client.verify_ssl))
|
||||
add_entities(cameras)
|
||||
|
||||
|
||||
|
10
homeassistant/components/cast/.translations/et.json
Normal file
10
homeassistant/components/cast/.translations/et.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"confirm": {
|
||||
"title": ""
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
@ -8,7 +8,7 @@ from homeassistant.components.climate import (
|
||||
ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON,
|
||||
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE)
|
||||
from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS
|
||||
from homeassistant.components.hive import DATA_HIVE
|
||||
from homeassistant.components.hive import DATA_HIVE, DOMAIN
|
||||
|
||||
DEPENDENCIES = ['hive']
|
||||
HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT,
|
||||
@ -44,6 +44,7 @@ class HiveClimateEntity(ClimateDevice):
|
||||
self.attributes = {}
|
||||
self.data_updatesource = '{}.{}'.format(self.device_type,
|
||||
self.node_id)
|
||||
self._unique_id = '{}-{}'.format(self.node_id, self.device_type)
|
||||
|
||||
if self.device_type == "Heating":
|
||||
self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF]
|
||||
@ -52,6 +53,21 @@ class HiveClimateEntity(ClimateDevice):
|
||||
|
||||
self.session.entities.append(self)
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return unique ID of entity."""
|
||||
return self._unique_id
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return device information."""
|
||||
return {
|
||||
'identifiers': {
|
||||
(DOMAIN, self.unique_id)
|
||||
},
|
||||
'name': self.name
|
||||
}
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Return the list of supported features."""
|
||||
|
@ -19,7 +19,8 @@ from homeassistant.components.climate import (
|
||||
ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA,
|
||||
SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE,
|
||||
SUPPORT_FAN_MODE, SUPPORT_SWING_MODE,
|
||||
SUPPORT_ON_OFF)
|
||||
SUPPORT_ON_OFF, STATE_HEAT, STATE_COOL, STATE_FAN_ONLY, STATE_DRY,
|
||||
STATE_AUTO)
|
||||
from homeassistant.exceptions import PlatformNotReady
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||
@ -57,6 +58,16 @@ FIELD_TO_FLAG = {
|
||||
'on': SUPPORT_ON_OFF,
|
||||
}
|
||||
|
||||
SENSIBO_TO_HA = {
|
||||
"cool": STATE_COOL,
|
||||
"heat": STATE_HEAT,
|
||||
"fan": STATE_FAN_ONLY,
|
||||
"auto": STATE_AUTO,
|
||||
"dry": STATE_DRY
|
||||
}
|
||||
|
||||
HA_TO_SENSIBO = {value: key for key, value in SENSIBO_TO_HA.items()}
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
@ -129,9 +140,10 @@ class SensiboClimate(ClimateDevice):
|
||||
self._ac_states = data['acState']
|
||||
self._status = data['connectionStatus']['isAlive']
|
||||
capabilities = data['remoteCapabilities']
|
||||
self._operations = sorted(capabilities['modes'].keys())
|
||||
self._current_capabilities = capabilities[
|
||||
'modes'][self.current_operation]
|
||||
self._operations = [SENSIBO_TO_HA[mode] for mode
|
||||
in capabilities['modes']]
|
||||
self._current_capabilities = \
|
||||
capabilities['modes'][self._ac_states['mode']]
|
||||
temperature_unit_key = data.get('temperatureUnit') or \
|
||||
self._ac_states.get('temperatureUnit')
|
||||
if temperature_unit_key:
|
||||
@ -186,7 +198,7 @@ class SensiboClimate(ClimateDevice):
|
||||
@property
|
||||
def current_operation(self):
|
||||
"""Return current operation ie. heat, cool, idle."""
|
||||
return self._ac_states['mode']
|
||||
return SENSIBO_TO_HA.get(self._ac_states['mode'])
|
||||
|
||||
@property
|
||||
def current_humidity(self):
|
||||
@ -293,7 +305,8 @@ class SensiboClimate(ClimateDevice):
|
||||
"""Set new target operation mode."""
|
||||
with async_timeout.timeout(TIMEOUT):
|
||||
await self._client.async_set_ac_state_property(
|
||||
self._id, 'mode', operation_mode, self._ac_states)
|
||||
self._id, 'mode', HA_TO_SENSIBO[operation_mode],
|
||||
self._ac_states)
|
||||
|
||||
async def async_set_swing_mode(self, swing_mode):
|
||||
"""Set new target swing operation."""
|
||||
|
@ -2,6 +2,7 @@
|
||||
import asyncio
|
||||
import logging
|
||||
import pprint
|
||||
import random
|
||||
import uuid
|
||||
|
||||
from aiohttp import hdrs, client_exceptions, WSMsgType
|
||||
@ -107,9 +108,11 @@ class CloudIoT:
|
||||
self.tries += 1
|
||||
|
||||
try:
|
||||
# Sleep 2^tries seconds between retries
|
||||
self.retry_task = hass.async_create_task(asyncio.sleep(
|
||||
2**min(9, self.tries), loop=hass.loop))
|
||||
# Sleep 2^tries + 0…tries*3 seconds between retries
|
||||
self.retry_task = hass.async_create_task(
|
||||
asyncio.sleep(2**min(9, self.tries) +
|
||||
random.randint(0, self.tries * 3),
|
||||
loop=hass.loop))
|
||||
yield from self.retry_task
|
||||
self.retry_task = None
|
||||
except asyncio.CancelledError:
|
||||
@ -313,15 +316,20 @@ def async_handle_google_actions(hass, cloud, payload):
|
||||
|
||||
|
||||
@HANDLERS.register('cloud')
|
||||
@asyncio.coroutine
|
||||
def async_handle_cloud(hass, cloud, payload):
|
||||
async def async_handle_cloud(hass, cloud, payload):
|
||||
"""Handle an incoming IoT message for cloud component."""
|
||||
action = payload['action']
|
||||
|
||||
if action == 'logout':
|
||||
yield from cloud.logout()
|
||||
# Log out of Home Assistant Cloud
|
||||
await cloud.logout()
|
||||
_LOGGER.error("You have been logged out from Home Assistant cloud: %s",
|
||||
payload['reason'])
|
||||
elif action == 'refresh_auth':
|
||||
# Refresh the auth token between now and payload['seconds']
|
||||
hass.helpers.event.async_call_later(
|
||||
random.randint(0, payload['seconds']),
|
||||
lambda now: auth_api.check_token(cloud))
|
||||
else:
|
||||
_LOGGER.warning("Received unknown cloud action: %s", action)
|
||||
|
||||
|
@ -37,8 +37,8 @@ SERVICE_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
cv.slug: vol.Any({
|
||||
DOMAIN: cv.schema_with_slug_keys(
|
||||
vol.Any({
|
||||
vol.Optional(CONF_ICON): cv.icon,
|
||||
vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL):
|
||||
cv.positive_int,
|
||||
@ -46,7 +46,7 @@ CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Optional(CONF_RESTORE, default=True): cv.boolean,
|
||||
vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int,
|
||||
}, None)
|
||||
})
|
||||
)
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
|
@ -27,7 +27,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
@ -9,7 +9,7 @@ import logging
|
||||
from homeassistant.components.cover import (
|
||||
CoverDevice, ENTITY_ID_FORMAT, ATTR_POSITION, ATTR_TILT_POSITION)
|
||||
from homeassistant.components.fibaro import (
|
||||
FIBARO_CONTROLLER, FIBARO_DEVICES, FibaroDevice)
|
||||
FIBARO_DEVICES, FibaroDevice)
|
||||
|
||||
DEPENDENCIES = ['fibaro']
|
||||
|
||||
@ -22,16 +22,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
return
|
||||
|
||||
add_entities(
|
||||
[FibaroCover(device, hass.data[FIBARO_CONTROLLER]) for
|
||||
[FibaroCover(device) for
|
||||
device in hass.data[FIBARO_DEVICES]['cover']], True)
|
||||
|
||||
|
||||
class FibaroCover(FibaroDevice, CoverDevice):
|
||||
"""Representation a Fibaro Cover."""
|
||||
|
||||
def __init__(self, fibaro_device, controller):
|
||||
def __init__(self, fibaro_device):
|
||||
"""Initialize the Vera device."""
|
||||
super().__init__(fibaro_device, controller)
|
||||
super().__init__(fibaro_device)
|
||||
self.entity_id = ENTITY_ID_FORMAT.format(self.ha_id)
|
||||
|
||||
@staticmethod
|
||||
|
@ -47,7 +47,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
305
homeassistant/components/cover/homekit_controller.py
Normal file
305
homeassistant/components/cover/homekit_controller.py
Normal file
@ -0,0 +1,305 @@
|
||||
"""
|
||||
Support for Homekit Cover.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.homekit_controller/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from homeassistant.components.homekit_controller import (HomeKitEntity,
|
||||
KNOWN_ACCESSORIES)
|
||||
from homeassistant.components.cover import (
|
||||
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION,
|
||||
SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, SUPPORT_SET_TILT_POSITION,
|
||||
ATTR_POSITION, ATTR_TILT_POSITION)
|
||||
from homeassistant.const import (
|
||||
STATE_CLOSED, STATE_CLOSING, STATE_OPEN, STATE_OPENING)
|
||||
|
||||
STATE_STOPPED = 'stopped'
|
||||
|
||||
DEPENDENCIES = ['homekit_controller']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
CURRENT_GARAGE_STATE_MAP = {
|
||||
0: STATE_OPEN,
|
||||
1: STATE_CLOSED,
|
||||
2: STATE_OPENING,
|
||||
3: STATE_CLOSING,
|
||||
4: STATE_STOPPED
|
||||
}
|
||||
|
||||
TARGET_GARAGE_STATE_MAP = {
|
||||
STATE_OPEN: 0,
|
||||
STATE_CLOSED: 1,
|
||||
STATE_STOPPED: 2
|
||||
}
|
||||
|
||||
CURRENT_WINDOW_STATE_MAP = {
|
||||
0: STATE_OPENING,
|
||||
1: STATE_CLOSING,
|
||||
2: STATE_STOPPED
|
||||
}
|
||||
|
||||
|
||||
def setup_platform(hass, config, add_entities, discovery_info=None):
|
||||
"""Set up HomeKit Cover support."""
|
||||
if discovery_info is None:
|
||||
return
|
||||
accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']]
|
||||
|
||||
if discovery_info['device-type'] == 'garage-door-opener':
|
||||
add_entities([HomeKitGarageDoorCover(accessory, discovery_info)],
|
||||
True)
|
||||
else:
|
||||
add_entities([HomeKitWindowCover(accessory, discovery_info)],
|
||||
True)
|
||||
|
||||
|
||||
class HomeKitGarageDoorCover(HomeKitEntity, CoverDevice):
|
||||
"""Representation of a HomeKit Garage Door."""
|
||||
|
||||
def __init__(self, accessory, discovery_info):
|
||||
"""Initialise the Cover."""
|
||||
super().__init__(accessory, discovery_info)
|
||||
self._name = None
|
||||
self._state = None
|
||||
self._obstruction_detected = None
|
||||
self.lock_state = None
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Define this cover as a garage door."""
|
||||
return 'garage'
|
||||
|
||||
def update_characteristics(self, characteristics):
|
||||
"""Synchronise the Cover state with Home Assistant."""
|
||||
# pylint: disable=import-error
|
||||
from homekit.model.characteristics import CharacteristicsTypes
|
||||
|
||||
for characteristic in characteristics:
|
||||
ctype = characteristic['type']
|
||||
ctype = CharacteristicsTypes.get_short(ctype)
|
||||
if ctype == "door-state.current":
|
||||
self._chars['door-state.current'] = \
|
||||
characteristic['iid']
|
||||
self._state = CURRENT_GARAGE_STATE_MAP[characteristic['value']]
|
||||
elif ctype == "door-state.target":
|
||||
self._chars['door-state.target'] = \
|
||||
characteristic['iid']
|
||||
elif ctype == "obstruction-detected":
|
||||
self._chars['obstruction-detected'] = characteristic['iid']
|
||||
self._obstruction_detected = characteristic['value']
|
||||
elif ctype == "name":
|
||||
self._chars['name'] = characteristic['iid']
|
||||
self._name = characteristic['value']
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the cover."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self._state is not None
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return SUPPORT_OPEN | SUPPORT_CLOSE
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return true if cover is closed, else False."""
|
||||
return self._state == STATE_CLOSED
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""Return if the cover is closing or not."""
|
||||
return self._state == STATE_CLOSING
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
"""Return if the cover is opening or not."""
|
||||
return self._state == STATE_OPENING
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
"""Send open command."""
|
||||
self.set_door_state(STATE_OPEN)
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
"""Send close command."""
|
||||
self.set_door_state(STATE_CLOSED)
|
||||
|
||||
def set_door_state(self, state):
|
||||
"""Send state command."""
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['door-state.target'],
|
||||
'value': TARGET_GARAGE_STATE_MAP[state]}]
|
||||
self.put_characteristics(characteristics)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
if self._obstruction_detected is None:
|
||||
return None
|
||||
|
||||
return {
|
||||
'obstruction-detected': self._obstruction_detected,
|
||||
}
|
||||
|
||||
|
||||
class HomeKitWindowCover(HomeKitEntity, CoverDevice):
|
||||
"""Representation of a HomeKit Window or Window Covering."""
|
||||
|
||||
def __init__(self, accessory, discovery_info):
|
||||
"""Initialise the Cover."""
|
||||
super().__init__(accessory, discovery_info)
|
||||
self._name = None
|
||||
self._state = None
|
||||
self._position = None
|
||||
self._tilt_position = None
|
||||
self._hold = None
|
||||
self._obstruction_detected = None
|
||||
self.lock_state = None
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if entity is available."""
|
||||
return self._state is not None
|
||||
|
||||
def update_characteristics(self, characteristics):
|
||||
"""Synchronise the Cover state with Home Assistant."""
|
||||
# pylint: disable=import-error
|
||||
from homekit.model.characteristics import CharacteristicsTypes
|
||||
|
||||
for characteristic in characteristics:
|
||||
ctype = characteristic['type']
|
||||
ctype = CharacteristicsTypes.get_short(ctype)
|
||||
if ctype == "position.state":
|
||||
self._chars['position.state'] = \
|
||||
characteristic['iid']
|
||||
if 'value' in characteristic:
|
||||
self._state = \
|
||||
CURRENT_WINDOW_STATE_MAP[characteristic['value']]
|
||||
elif ctype == "position.current":
|
||||
self._chars['position.current'] = \
|
||||
characteristic['iid']
|
||||
self._position = characteristic['value']
|
||||
elif ctype == "position.target":
|
||||
self._chars['position.target'] = \
|
||||
characteristic['iid']
|
||||
elif ctype == "position.hold":
|
||||
self._chars['position.hold'] = characteristic['iid']
|
||||
if 'value' in characteristic:
|
||||
self._hold = characteristic['value']
|
||||
elif ctype == "vertical-tilt.current":
|
||||
self._chars['vertical-tilt.current'] = characteristic['iid']
|
||||
if characteristic['value'] is not None:
|
||||
self._tilt_position = characteristic['value']
|
||||
elif ctype == "horizontal-tilt.current":
|
||||
self._chars['horizontal-tilt.current'] = characteristic['iid']
|
||||
if characteristic['value'] is not None:
|
||||
self._tilt_position = characteristic['value']
|
||||
elif ctype == "vertical-tilt.target":
|
||||
self._chars['vertical-tilt.target'] = \
|
||||
characteristic['iid']
|
||||
elif ctype == "horizontal-tilt.target":
|
||||
self._chars['vertical-tilt.target'] = \
|
||||
characteristic['iid']
|
||||
elif ctype == "obstruction-detected":
|
||||
self._chars['obstruction-detected'] = characteristic['iid']
|
||||
self._obstruction_detected = characteristic['value']
|
||||
elif ctype == "name":
|
||||
self._chars['name'] = characteristic['iid']
|
||||
if 'value' in characteristic:
|
||||
self._name = characteristic['value']
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the cover."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
supported_features = (
|
||||
SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION)
|
||||
|
||||
if self._tilt_position is not None:
|
||||
supported_features |= (
|
||||
SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT |
|
||||
SUPPORT_SET_TILT_POSITION)
|
||||
|
||||
return supported_features
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of cover."""
|
||||
return self._position
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return true if cover is closed, else False."""
|
||||
return self._position == 0
|
||||
|
||||
@property
|
||||
def is_closing(self):
|
||||
"""Return if the cover is closing or not."""
|
||||
return self._state == STATE_CLOSING
|
||||
|
||||
@property
|
||||
def is_opening(self):
|
||||
"""Return if the cover is opening or not."""
|
||||
return self._state == STATE_OPENING
|
||||
|
||||
def open_cover(self, **kwargs):
|
||||
"""Send open command."""
|
||||
self.set_cover_position(position=100)
|
||||
|
||||
def close_cover(self, **kwargs):
|
||||
"""Send close command."""
|
||||
self.set_cover_position(position=0)
|
||||
|
||||
def set_cover_position(self, **kwargs):
|
||||
"""Send position command."""
|
||||
position = kwargs[ATTR_POSITION]
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['position.target'],
|
||||
'value': position}]
|
||||
self.put_characteristics(characteristics)
|
||||
|
||||
@property
|
||||
def current_cover_tilt_position(self):
|
||||
"""Return current position of cover tilt."""
|
||||
return self._tilt_position
|
||||
|
||||
def set_cover_tilt_position(self, **kwargs):
|
||||
"""Move the cover tilt to a specific position."""
|
||||
tilt_position = kwargs[ATTR_TILT_POSITION]
|
||||
if 'vertical-tilt.target' in self._chars:
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid': self._chars['vertical-tilt.target'],
|
||||
'value': tilt_position}]
|
||||
self.put_characteristics(characteristics)
|
||||
elif 'horizontal-tilt.target' in self._chars:
|
||||
characteristics = [{'aid': self._aid,
|
||||
'iid':
|
||||
self._chars['horizontal-tilt.target'],
|
||||
'value': tilt_position}]
|
||||
self.put_characteristics(characteristics)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the optional state attributes."""
|
||||
state_attributes = {}
|
||||
if self._obstruction_detected is not None:
|
||||
state_attributes['obstruction-detected'] = \
|
||||
self._obstruction_detected
|
||||
|
||||
if self._hold is not None:
|
||||
state_attributes['hold-position'] = \
|
||||
self._hold
|
||||
|
||||
return state_attributes
|
@ -46,7 +46,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
@ -18,7 +18,8 @@ _LOGGER = logging.getLogger(__name__)
|
||||
DEPENDENCIES = ['scsgate']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}),
|
||||
vol.Required(CONF_DEVICES):
|
||||
cv.schema_with_slug_keys(scsgate.SCSGATE_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
@ -67,7 +67,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
|
||||
|
@ -26,7 +26,7 @@ COVER_SCHEMA = vol.Schema({
|
||||
})
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
|
||||
vol.Required(CONF_COVERS): cv.schema_with_slug_keys(COVER_SCHEMA),
|
||||
})
|
||||
|
||||
DEPENDENCIES = ['velbus']
|
||||
|
19
homeassistant/components/daikin/.translations/de.json
Normal file
19
homeassistant/components/daikin/.translations/de.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Ger\u00e4t ist bereits konfiguriert",
|
||||
"device_fail": "Unerwarteter Fehler beim Erstellen des Ger\u00e4ts.",
|
||||
"device_timeout": "Zeit\u00fcberschreitung beim Verbinden mit dem Ger\u00e4t."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Geben Sie die IP-Adresse Ihrer Daikin AC ein.",
|
||||
"title": "Daikin AC konfigurieren"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
11
homeassistant/components/daikin/.translations/hu.json
Normal file
11
homeassistant/components/daikin/.translations/hu.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Kiszolg\u00e1l\u00f3"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
19
homeassistant/components/daikin/.translations/nl.json
Normal file
19
homeassistant/components/daikin/.translations/nl.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Apparaat is al geconfigureerd",
|
||||
"device_fail": "Onverwachte fout bij het aanmaken van een apparaat.",
|
||||
"device_timeout": "Time-out voor verbinding met het apparaat."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Voer het IP-adres van uw Daikin AC in.",
|
||||
"title": "Daikin AC instellen"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
19
homeassistant/components/daikin/.translations/no.json
Normal file
19
homeassistant/components/daikin/.translations/no.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Enheten er allerede konfigurert",
|
||||
"device_fail": "Uventet feil under oppretting av enheten.",
|
||||
"device_timeout": "Tidsavbrudd for tilkobling til enheten."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Vert"
|
||||
},
|
||||
"description": "Angi IP-adressen til din Daikin AC.",
|
||||
"title": "Konfigurer Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
19
homeassistant/components/daikin/.translations/pl.json
Normal file
19
homeassistant/components/daikin/.translations/pl.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "Urz\u0105dzenie jest ju\u017c skonfigurowane",
|
||||
"device_fail": "Nieoczekiwany b\u0142\u0105d tworzenia urz\u0105dzenia.",
|
||||
"device_timeout": "Limit czasu pod\u0142\u0105czenia do urz\u0105dzenia."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Host"
|
||||
},
|
||||
"description": "Wprowad\u017a adres IP Daikin AC.",
|
||||
"title": "Konfiguracja Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
16
homeassistant/components/daikin/.translations/pt-BR.json
Normal file
16
homeassistant/components/daikin/.translations/pt-BR.json
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "O dispositivo j\u00e1 est\u00e1 configurado",
|
||||
"device_fail": "Erro inesperado ao criar dispositivo.",
|
||||
"device_timeout": "Excedido tempo limite conectando ao dispositivo"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Digite o endere\u00e7o IP do seu AC Daikin.",
|
||||
"title": "Configurar o AC Daikin"
|
||||
}
|
||||
},
|
||||
"title": "AC Daikin"
|
||||
}
|
||||
}
|
19
homeassistant/components/daikin/.translations/pt.json
Normal file
19
homeassistant/components/daikin/.translations/pt.json
Normal file
@ -0,0 +1,19 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"already_configured": "O dispositivo j\u00e1 est\u00e1 configurado",
|
||||
"device_fail": "Erro inesperado ao criar dispositivo.",
|
||||
"device_timeout": "Tempo excedido a tentar ligar ao dispositivo."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host": "Servidor"
|
||||
},
|
||||
"description": "Introduza o endere\u00e7o IP do seu Daikin AC.",
|
||||
"title": "Configurar o Daikin AC"
|
||||
}
|
||||
},
|
||||
"title": "Daikin AC"
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Host",
|
||||
"port": "Port (Standartwert : '80')"
|
||||
"port": "Port"
|
||||
},
|
||||
"title": "Definiere das deCONZ-Gateway"
|
||||
},
|
||||
|
13
homeassistant/components/deconz/.translations/et.json
Normal file
13
homeassistant/components/deconz/.translations/et.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "",
|
||||
"port": ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": "deCONZ Zigbee l\u00fc\u00fcs"
|
||||
}
|
||||
}
|
@ -12,7 +12,7 @@
|
||||
"init": {
|
||||
"data": {
|
||||
"host": "Gostitelj",
|
||||
"port": "Vrata (privzeta vrednost: '80')"
|
||||
"port": "Vrata"
|
||||
},
|
||||
"title": "Dolo\u010dite deCONZ prehod"
|
||||
},
|
||||
@ -28,6 +28,6 @@
|
||||
"title": "Dodatne mo\u017enosti konfiguracije za deCONZ"
|
||||
}
|
||||
},
|
||||
"title": "deCONZ"
|
||||
"title": "deCONZ Zigbee prehod"
|
||||
}
|
||||
}
|
89
homeassistant/components/deconz/binary_sensor.py
Normal file
89
homeassistant/components/deconz/binary_sensor.py
Normal file
@ -0,0 +1,89 @@
|
||||
"""
|
||||
Support for deCONZ binary sensor.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/binary_sensor.deconz/
|
||||
"""
|
||||
from homeassistant.components.binary_sensor import BinarySensorDevice
|
||||
from homeassistant.const import ATTR_BATTERY_LEVEL
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Old way of setting up deCONZ binary sensors."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the deCONZ binary sensor."""
|
||||
gateway = hass.data[DECONZ_DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_sensor(sensors):
|
||||
"""Add binary sensor from deCONZ."""
|
||||
from pydeconz.sensor import DECONZ_BINARY_SENSOR
|
||||
entities = []
|
||||
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
for sensor in sensors:
|
||||
if sensor.type in DECONZ_BINARY_SENSOR and \
|
||||
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
|
||||
entities.append(DeconzBinarySensor(sensor, gateway))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
|
||||
|
||||
async_add_sensor(gateway.api.sensors.values())
|
||||
|
||||
|
||||
class DeconzBinarySensor(DeconzDevice, BinarySensorDevice):
|
||||
"""Representation of a deCONZ binary sensor."""
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the sensor's state.
|
||||
|
||||
If reason is that state is updated,
|
||||
or reachable has changed or battery has changed.
|
||||
"""
|
||||
if reason['state'] or \
|
||||
'reachable' in reason['attr'] or \
|
||||
'battery' in reason['attr'] or \
|
||||
'on' in reason['attr']:
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if sensor is on."""
|
||||
return self._device.is_tripped
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return self._device.sensor_class
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._device.sensor_icon
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the sensor."""
|
||||
from pydeconz.sensor import PRESENCE
|
||||
attr = {}
|
||||
if self._device.battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self._device.battery
|
||||
if self._device.on is not None:
|
||||
attr[ATTR_ON] = self._device.on
|
||||
if self._device.type in PRESENCE and self._device.dark is not None:
|
||||
attr[ATTR_DARK] = self._device.dark
|
||||
return attr
|
@ -4,16 +4,15 @@ Support for deCONZ covers.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/cover.deconz/
|
||||
"""
|
||||
from homeassistant.components.deconz.const import (
|
||||
COVER_TYPES, DAMPERS, DECONZ_REACHABLE, DOMAIN as DECONZ_DOMAIN,
|
||||
WINDOW_COVERS)
|
||||
from homeassistant.components.cover import (
|
||||
ATTR_POSITION, CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN, SUPPORT_STOP,
|
||||
SUPPORT_SET_POSITION)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import COVER_TYPES, DAMPERS, DOMAIN as DECONZ_DOMAIN, WINDOW_COVERS
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
ZIGBEE_SPEC = ['lumi.curtain']
|
||||
@ -50,67 +49,36 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_cover(gateway.api.lights.values())
|
||||
|
||||
|
||||
class DeconzCover(CoverDevice):
|
||||
class DeconzCover(DeconzDevice, CoverDevice):
|
||||
"""Representation of a deCONZ cover."""
|
||||
|
||||
def __init__(self, cover, gateway):
|
||||
def __init__(self, device, gateway):
|
||||
"""Set up cover and add update callback to get data from websocket."""
|
||||
self._cover = cover
|
||||
self.gateway = gateway
|
||||
self.unsub_dispatcher = None
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._features = SUPPORT_OPEN
|
||||
self._features |= SUPPORT_CLOSE
|
||||
self._features |= SUPPORT_STOP
|
||||
self._features |= SUPPORT_SET_POSITION
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to covers events."""
|
||||
self._cover.register_async_callback(self.async_update_callback)
|
||||
self.gateway.deconz_ids[self.entity_id] = self._cover.deconz_id
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, DECONZ_REACHABLE, self.async_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect cover object when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
self._cover.remove_callback(self.async_update_callback)
|
||||
self._cover = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the cover's state."""
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of the cover."""
|
||||
if self.is_closed:
|
||||
return 0
|
||||
return int(self._cover.brightness / 255 * 100)
|
||||
return int(self._device.brightness / 255 * 100)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
return not self._cover.state
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the cover."""
|
||||
return self._cover.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this cover."""
|
||||
return self._cover.uniqueid
|
||||
return not self._device.state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the cover."""
|
||||
if self._cover.type in DAMPERS:
|
||||
if self._device.type in DAMPERS:
|
||||
return 'damper'
|
||||
if self._cover.type in WINDOW_COVERS:
|
||||
if self._device.type in WINDOW_COVERS:
|
||||
return 'window'
|
||||
|
||||
@property
|
||||
@ -118,16 +86,6 @@ class DeconzCover(CoverDevice):
|
||||
"""Flag supported features."""
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if light is available."""
|
||||
return self.gateway.available and self._cover.reachable
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
position = kwargs[ATTR_POSITION]
|
||||
@ -135,7 +93,7 @@ class DeconzCover(CoverDevice):
|
||||
if position > 0:
|
||||
data['on'] = True
|
||||
data['bri'] = int(position / 100 * 255)
|
||||
await self._cover.async_set_state(data)
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
async def async_open_cover(self, **kwargs):
|
||||
"""Open cover."""
|
||||
@ -150,25 +108,7 @@ class DeconzCover(CoverDevice):
|
||||
async def async_stop_cover(self, **kwargs):
|
||||
"""Stop cover."""
|
||||
data = {'bri_inc': 0}
|
||||
await self._cover.async_set_state(data)
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._cover.uniqueid is None or
|
||||
self._cover.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._cover.uniqueid.split('-', 1)[0]
|
||||
bridgeid = self.gateway.api.config.bridgeid
|
||||
return {
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._cover.manufacturer,
|
||||
'model': self._cover.modelid,
|
||||
'name': self._cover.name,
|
||||
'sw_version': self._cover.swversion,
|
||||
'via_hub': (DECONZ_DOMAIN, bridgeid),
|
||||
}
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
|
||||
class DeconzCoverZigbeeSpec(DeconzCover):
|
||||
@ -177,12 +117,12 @@ class DeconzCoverZigbeeSpec(DeconzCover):
|
||||
@property
|
||||
def current_cover_position(self):
|
||||
"""Return the current position of the cover."""
|
||||
return 100 - int(self._cover.brightness / 255 * 100)
|
||||
return 100 - int(self._device.brightness / 255 * 100)
|
||||
|
||||
@property
|
||||
def is_closed(self):
|
||||
"""Return if the cover is closed."""
|
||||
return self._cover.state
|
||||
return self._device.state
|
||||
|
||||
async def async_set_cover_position(self, **kwargs):
|
||||
"""Move the cover to a specific position."""
|
||||
@ -191,4 +131,4 @@ class DeconzCoverZigbeeSpec(DeconzCover):
|
||||
if position < 100:
|
||||
data['on'] = True
|
||||
data['bri'] = 255 - int(position / 100 * 255)
|
||||
await self._cover.async_set_state(data)
|
||||
await self._device.async_set_state(data)
|
74
homeassistant/components/deconz/deconz_device.py
Normal file
74
homeassistant/components/deconz/deconz_device.py
Normal file
@ -0,0 +1,74 @@
|
||||
"""Base class for deCONZ devices."""
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.entity import Entity
|
||||
|
||||
from .const import DECONZ_REACHABLE, DOMAIN as DECONZ_DOMAIN
|
||||
|
||||
|
||||
class DeconzDevice(Entity):
|
||||
"""Representation of a deCONZ device."""
|
||||
|
||||
def __init__(self, device, gateway):
|
||||
"""Set up device and add update callback to get data from websocket."""
|
||||
self._device = device
|
||||
self.gateway = gateway
|
||||
self.unsub_dispatcher = None
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to device events."""
|
||||
self._device.register_async_callback(self.async_update_callback)
|
||||
self.gateway.deconz_ids[self.entity_id] = self._device.deconz_id
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, DECONZ_REACHABLE, self.async_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect device object when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
self._device.remove_callback(self.async_update_callback)
|
||||
self._device = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the device's state."""
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the device."""
|
||||
return self._device.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this device."""
|
||||
return self._device.uniqueid
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if device is available."""
|
||||
return self.gateway.available and self._device.reachable
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._device.uniqueid is None or
|
||||
self._device.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._device.uniqueid.split('-', 1)[0]
|
||||
bridgeid = self.gateway.api.config.bridgeid
|
||||
return {
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._device.manufacturer,
|
||||
'model': self._device.modelid,
|
||||
'name': self._device.name,
|
||||
'sw_version': self._device.swversion,
|
||||
'via_hub': (DECONZ_DOMAIN, bridgeid),
|
||||
}
|
@ -4,19 +4,20 @@ Support for deCONZ light.
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/light.deconz/
|
||||
"""
|
||||
from homeassistant.components.deconz.const import (
|
||||
CONF_ALLOW_DECONZ_GROUPS, DECONZ_REACHABLE, DOMAIN as DECONZ_DOMAIN,
|
||||
COVER_TYPES, SWITCH_TYPES)
|
||||
from homeassistant.components.light import (
|
||||
ATTR_BRIGHTNESS, ATTR_COLOR_TEMP, ATTR_EFFECT, ATTR_FLASH, ATTR_HS_COLOR,
|
||||
ATTR_TRANSITION, EFFECT_COLORLOOP, FLASH_LONG, FLASH_SHORT,
|
||||
SUPPORT_BRIGHTNESS, SUPPORT_COLOR, SUPPORT_COLOR_TEMP, SUPPORT_EFFECT,
|
||||
SUPPORT_FLASH, SUPPORT_TRANSITION, Light)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
import homeassistant.util.color as color_util
|
||||
|
||||
from .const import (
|
||||
CONF_ALLOW_DECONZ_GROUPS, DOMAIN as DECONZ_DOMAIN, COVER_TYPES,
|
||||
SWITCH_TYPES)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
@ -59,51 +60,30 @@ async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
async_add_group(gateway.api.groups.values())
|
||||
|
||||
|
||||
class DeconzLight(Light):
|
||||
class DeconzLight(DeconzDevice, Light):
|
||||
"""Representation of a deCONZ light."""
|
||||
|
||||
def __init__(self, light, gateway):
|
||||
def __init__(self, device, gateway):
|
||||
"""Set up light and add update callback to get data from websocket."""
|
||||
self._light = light
|
||||
self.gateway = gateway
|
||||
self.unsub_dispatcher = None
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._features = SUPPORT_BRIGHTNESS
|
||||
self._features |= SUPPORT_FLASH
|
||||
self._features |= SUPPORT_TRANSITION
|
||||
|
||||
if self._light.ct is not None:
|
||||
if self._device.ct is not None:
|
||||
self._features |= SUPPORT_COLOR_TEMP
|
||||
|
||||
if self._light.xy is not None:
|
||||
if self._device.xy is not None:
|
||||
self._features |= SUPPORT_COLOR
|
||||
|
||||
if self._light.effect is not None:
|
||||
if self._device.effect is not None:
|
||||
self._features |= SUPPORT_EFFECT
|
||||
|
||||
async def async_added_to_hass(self):
|
||||
"""Subscribe to lights events."""
|
||||
self._light.register_async_callback(self.async_update_callback)
|
||||
self.gateway.deconz_ids[self.entity_id] = self._light.deconz_id
|
||||
self.unsub_dispatcher = async_dispatcher_connect(
|
||||
self.hass, DECONZ_REACHABLE, self.async_update_callback)
|
||||
|
||||
async def async_will_remove_from_hass(self) -> None:
|
||||
"""Disconnect light object when removed."""
|
||||
if self.unsub_dispatcher is not None:
|
||||
self.unsub_dispatcher()
|
||||
self._light.remove_callback(self.async_update_callback)
|
||||
self._light = None
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the light's state."""
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def brightness(self):
|
||||
"""Return the brightness of this light between 0..255."""
|
||||
return self._light.brightness
|
||||
return self._device.brightness
|
||||
|
||||
@property
|
||||
def effect_list(self):
|
||||
@ -113,48 +93,28 @@ class DeconzLight(Light):
|
||||
@property
|
||||
def color_temp(self):
|
||||
"""Return the CT color value."""
|
||||
if self._light.colormode != 'ct':
|
||||
if self._device.colormode != 'ct':
|
||||
return None
|
||||
|
||||
return self._light.ct
|
||||
return self._device.ct
|
||||
|
||||
@property
|
||||
def hs_color(self):
|
||||
"""Return the hs color value."""
|
||||
if self._light.colormode in ('xy', 'hs') and self._light.xy:
|
||||
return color_util.color_xy_to_hs(*self._light.xy)
|
||||
if self._device.colormode in ('xy', 'hs') and self._device.xy:
|
||||
return color_util.color_xy_to_hs(*self._device.xy)
|
||||
return None
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if light is on."""
|
||||
return self._light.state
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the light."""
|
||||
return self._light.name
|
||||
|
||||
@property
|
||||
def unique_id(self):
|
||||
"""Return a unique identifier for this light."""
|
||||
return self._light.uniqueid
|
||||
return self._device.state
|
||||
|
||||
@property
|
||||
def supported_features(self):
|
||||
"""Flag supported features."""
|
||||
return self._features
|
||||
|
||||
@property
|
||||
def available(self):
|
||||
"""Return True if light is available."""
|
||||
return self.gateway.available and self._light.reachable
|
||||
|
||||
@property
|
||||
def should_poll(self):
|
||||
"""No polling needed."""
|
||||
return False
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on light."""
|
||||
data = {'on': True}
|
||||
@ -185,7 +145,7 @@ class DeconzLight(Light):
|
||||
else:
|
||||
data['effect'] = 'none'
|
||||
|
||||
await self._light.async_set_state(data)
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off light."""
|
||||
@ -203,31 +163,13 @@ class DeconzLight(Light):
|
||||
data['alert'] = 'lselect'
|
||||
del data['on']
|
||||
|
||||
await self._light.async_set_state(data)
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the device state attributes."""
|
||||
attributes = {}
|
||||
attributes['is_deconz_group'] = self._light.type == 'LightGroup'
|
||||
if self._light.type == 'LightGroup':
|
||||
attributes['all_on'] = self._light.all_on
|
||||
attributes['is_deconz_group'] = self._device.type == 'LightGroup'
|
||||
if self._device.type == 'LightGroup':
|
||||
attributes['all_on'] = self._device.all_on
|
||||
return attributes
|
||||
|
||||
@property
|
||||
def device_info(self):
|
||||
"""Return a device description for device registry."""
|
||||
if (self._light.uniqueid is None or
|
||||
self._light.uniqueid.count(':') != 7):
|
||||
return None
|
||||
serial = self._light.uniqueid.split('-', 1)[0]
|
||||
bridgeid = self.gateway.api.config.bridgeid
|
||||
return {
|
||||
'connections': {(CONNECTION_ZIGBEE, serial)},
|
||||
'identifiers': {(DECONZ_DOMAIN, serial)},
|
||||
'manufacturer': self._light.manufacturer,
|
||||
'model': self._light.modelid,
|
||||
'name': self._light.name,
|
||||
'sw_version': self._light.swversion,
|
||||
'via_hub': (DECONZ_DOMAIN, bridgeid),
|
||||
}
|
153
homeassistant/components/deconz/sensor.py
Normal file
153
homeassistant/components/deconz/sensor.py
Normal file
@ -0,0 +1,153 @@
|
||||
"""
|
||||
Support for deCONZ sensor.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/sensor.deconz/
|
||||
"""
|
||||
from homeassistant.const import (
|
||||
ATTR_BATTERY_LEVEL, ATTR_VOLTAGE, DEVICE_CLASS_BATTERY)
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.util import slugify
|
||||
|
||||
from .const import (
|
||||
ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DECONZ_DOMAIN)
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
ATTR_CURRENT = 'current'
|
||||
ATTR_DAYLIGHT = 'daylight'
|
||||
ATTR_EVENT_ID = 'event_id'
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Old way of setting up deCONZ sensors."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up the deCONZ sensors."""
|
||||
gateway = hass.data[DECONZ_DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_sensor(sensors):
|
||||
"""Add sensors from deCONZ."""
|
||||
from pydeconz.sensor import DECONZ_SENSOR, SWITCH as DECONZ_REMOTE
|
||||
entities = []
|
||||
allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True)
|
||||
for sensor in sensors:
|
||||
if sensor.type in DECONZ_SENSOR and \
|
||||
not (not allow_clip_sensor and sensor.type.startswith('CLIP')):
|
||||
if sensor.type in DECONZ_REMOTE:
|
||||
if sensor.battery:
|
||||
entities.append(DeconzBattery(sensor, gateway))
|
||||
else:
|
||||
entities.append(DeconzSensor(sensor, gateway))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor))
|
||||
|
||||
async_add_sensor(gateway.api.sensors.values())
|
||||
|
||||
|
||||
class DeconzSensor(DeconzDevice):
|
||||
"""Representation of a deCONZ sensor."""
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the sensor's state.
|
||||
|
||||
If reason is that state is updated,
|
||||
or reachable has changed or battery has changed.
|
||||
"""
|
||||
if reason['state'] or \
|
||||
'reachable' in reason['attr'] or \
|
||||
'battery' in reason['attr'] or \
|
||||
'on' in reason['attr']:
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the sensor."""
|
||||
return self._device.state
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return self._device.sensor_class
|
||||
|
||||
@property
|
||||
def icon(self):
|
||||
"""Return the icon to use in the frontend."""
|
||||
return self._device.sensor_icon
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this sensor."""
|
||||
return self._device.sensor_unit
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the sensor."""
|
||||
from pydeconz.sensor import LIGHTLEVEL
|
||||
attr = {}
|
||||
if self._device.battery:
|
||||
attr[ATTR_BATTERY_LEVEL] = self._device.battery
|
||||
if self._device.on is not None:
|
||||
attr[ATTR_ON] = self._device.on
|
||||
if self._device.type in LIGHTLEVEL and self._device.dark is not None:
|
||||
attr[ATTR_DARK] = self._device.dark
|
||||
if self.unit_of_measurement == 'Watts':
|
||||
attr[ATTR_CURRENT] = self._device.current
|
||||
attr[ATTR_VOLTAGE] = self._device.voltage
|
||||
if self._device.sensor_class == 'daylight':
|
||||
attr[ATTR_DAYLIGHT] = self._device.daylight
|
||||
return attr
|
||||
|
||||
|
||||
class DeconzBattery(DeconzDevice):
|
||||
"""Battery class for when a device is only represented as an event."""
|
||||
|
||||
def __init__(self, device, gateway):
|
||||
"""Register dispatcher callback for update of battery state."""
|
||||
super().__init__(device, gateway)
|
||||
|
||||
self._name = '{} {}'.format(self._device.name, 'Battery Level')
|
||||
self._unit_of_measurement = "%"
|
||||
|
||||
@callback
|
||||
def async_update_callback(self, reason):
|
||||
"""Update the battery's state, if needed."""
|
||||
if 'reachable' in reason['attr'] or 'battery' in reason['attr']:
|
||||
self.async_schedule_update_ha_state()
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the battery."""
|
||||
return self._device.battery
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"""Return the name of the battery."""
|
||||
return self._name
|
||||
|
||||
@property
|
||||
def device_class(self):
|
||||
"""Return the class of the sensor."""
|
||||
return DEVICE_CLASS_BATTERY
|
||||
|
||||
@property
|
||||
def unit_of_measurement(self):
|
||||
"""Return the unit of measurement of this entity."""
|
||||
return self._unit_of_measurement
|
||||
|
||||
@property
|
||||
def device_state_attributes(self):
|
||||
"""Return the state attributes of the battery."""
|
||||
attr = {
|
||||
ATTR_EVENT_ID: slugify(self._device.name),
|
||||
}
|
||||
return attr
|
83
homeassistant/components/deconz/switch.py
Normal file
83
homeassistant/components/deconz/switch.py
Normal file
@ -0,0 +1,83 @@
|
||||
"""
|
||||
Support for deCONZ switches.
|
||||
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/switch.deconz/
|
||||
"""
|
||||
from homeassistant.components.switch import SwitchDevice
|
||||
from homeassistant.core import callback
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
from .const import DOMAIN as DECONZ_DOMAIN, POWER_PLUGS, SIRENS
|
||||
from .deconz_device import DeconzDevice
|
||||
|
||||
|
||||
DEPENDENCIES = ['deconz']
|
||||
|
||||
|
||||
async def async_setup_platform(hass, config, async_add_entities,
|
||||
discovery_info=None):
|
||||
"""Old way of setting up deCONZ switches."""
|
||||
pass
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry, async_add_entities):
|
||||
"""Set up switches for deCONZ component.
|
||||
|
||||
Switches are based same device class as lights in deCONZ.
|
||||
"""
|
||||
gateway = hass.data[DECONZ_DOMAIN]
|
||||
|
||||
@callback
|
||||
def async_add_switch(lights):
|
||||
"""Add switch from deCONZ."""
|
||||
entities = []
|
||||
for light in lights:
|
||||
if light.type in POWER_PLUGS:
|
||||
entities.append(DeconzPowerPlug(light, gateway))
|
||||
elif light.type in SIRENS:
|
||||
entities.append(DeconzSiren(light, gateway))
|
||||
async_add_entities(entities, True)
|
||||
|
||||
gateway.listeners.append(
|
||||
async_dispatcher_connect(hass, 'deconz_new_light', async_add_switch))
|
||||
|
||||
async_add_switch(gateway.api.lights.values())
|
||||
|
||||
|
||||
class DeconzPowerPlug(DeconzDevice, SwitchDevice):
|
||||
"""Representation of a deCONZ power plug."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
return self._device.state
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on switch."""
|
||||
data = {'on': True}
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off switch."""
|
||||
data = {'on': False}
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
|
||||
class DeconzSiren(DeconzDevice, SwitchDevice):
|
||||
"""Representation of a deCONZ siren."""
|
||||
|
||||
@property
|
||||
def is_on(self):
|
||||
"""Return true if switch is on."""
|
||||
return self._device.alert == 'lselect'
|
||||
|
||||
async def async_turn_on(self, **kwargs):
|
||||
"""Turn on switch."""
|
||||
data = {'alert': 'lselect'}
|
||||
await self._device.async_set_state(data)
|
||||
|
||||
async def async_turn_off(self, **kwargs):
|
||||
"""Turn off switch."""
|
||||
data = {'alert': 'none'}
|
||||
await self._device.async_set_state(data)
|
@ -15,7 +15,7 @@ import homeassistant.util.dt as dt_util
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
REQUIREMENTS = ['pygatt==3.2.0']
|
||||
REQUIREMENTS = ['pygatt[GATTTOOL]==3.2.0']
|
||||
|
||||
BLE_PREFIX = 'BLE_'
|
||||
MIN_SEEN_NEW = 5
|
||||
|
@ -19,7 +19,7 @@ from homeassistant.helpers.event import track_time_interval
|
||||
from homeassistant.helpers.typing import ConfigType
|
||||
from homeassistant.util import slugify, dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['locationsharinglib==3.0.9']
|
||||
REQUIREMENTS = ['locationsharinglib==3.0.11']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -89,5 +89,7 @@ class GoogleHomeDeviceScanner(DeviceScanner):
|
||||
devices[uuid]['btle_mac_address'] = device['mac_address']
|
||||
devices[uuid]['ghname'] = ghname
|
||||
devices[uuid]['source_type'] = 'bluetooth'
|
||||
if device['name']:
|
||||
devices[uuid]['btle_name'] = device['name']
|
||||
await self.scanner.clear_scan_result()
|
||||
self.last_results = devices
|
||||
|
@ -5,104 +5,28 @@ For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.gpslogger/
|
||||
"""
|
||||
import logging
|
||||
from hmac import compare_digest
|
||||
|
||||
from aiohttp.web import Request, HTTPUnauthorized
|
||||
import voluptuous as vol
|
||||
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.const import (
|
||||
CONF_PASSWORD, HTTP_UNPROCESSABLE_ENTITY
|
||||
)
|
||||
from homeassistant.components.http import (
|
||||
CONF_API_PASSWORD, HomeAssistantView
|
||||
)
|
||||
# pylint: disable=unused-import
|
||||
from homeassistant.components.device_tracker import ( # NOQA
|
||||
DOMAIN, PLATFORM_SCHEMA
|
||||
)
|
||||
from homeassistant.components.gpslogger import TRACKER_UPDATE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
from homeassistant.helpers.typing import HomeAssistantType, ConfigType
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
|
||||
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
|
||||
vol.Optional(CONF_PASSWORD): cv.string,
|
||||
})
|
||||
DEPENDENCIES = ['gpslogger']
|
||||
|
||||
|
||||
async def async_setup_scanner(hass: HomeAssistantType, config: ConfigType,
|
||||
async_see, discovery_info=None):
|
||||
"""Set up an endpoint for the GPSLogger application."""
|
||||
hass.http.register_view(GPSLoggerView(async_see, config))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
class GPSLoggerView(HomeAssistantView):
|
||||
"""View to handle GPSLogger requests."""
|
||||
|
||||
url = '/api/gpslogger'
|
||||
name = 'api:gpslogger'
|
||||
|
||||
def __init__(self, async_see, config):
|
||||
"""Initialize GPSLogger url endpoints."""
|
||||
self.async_see = async_see
|
||||
self._password = config.get(CONF_PASSWORD)
|
||||
# this component does not require external authentication if
|
||||
# password is set
|
||||
self.requires_auth = self._password is None
|
||||
|
||||
async def get(self, request: Request):
|
||||
"""Handle for GPSLogger message received as GET."""
|
||||
hass = request.app['hass']
|
||||
data = request.query
|
||||
|
||||
if self._password is not None:
|
||||
authenticated = CONF_API_PASSWORD in data and compare_digest(
|
||||
self._password,
|
||||
data[CONF_API_PASSWORD]
|
||||
)
|
||||
if not authenticated:
|
||||
raise HTTPUnauthorized()
|
||||
|
||||
if 'latitude' not in data or 'longitude' not in data:
|
||||
return ('Latitude and longitude not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if 'device' not in data:
|
||||
_LOGGER.error("Device id not specified")
|
||||
return ('Device id not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
device = data['device'].replace('-', '')
|
||||
gps_location = (data['latitude'], data['longitude'])
|
||||
accuracy = 200
|
||||
battery = -1
|
||||
|
||||
if 'accuracy' in data:
|
||||
accuracy = int(float(data['accuracy']))
|
||||
if 'battery' in data:
|
||||
battery = float(data['battery'])
|
||||
|
||||
attrs = {}
|
||||
if 'speed' in data:
|
||||
attrs['speed'] = float(data['speed'])
|
||||
if 'direction' in data:
|
||||
attrs['direction'] = float(data['direction'])
|
||||
if 'altitude' in data:
|
||||
attrs['altitude'] = float(data['altitude'])
|
||||
if 'provider' in data:
|
||||
attrs['provider'] = data['provider']
|
||||
if 'activity' in data:
|
||||
attrs['activity'] = data['activity']
|
||||
|
||||
hass.async_create_task(self.async_see(
|
||||
"""Set up an endpoint for the GPSLogger device tracker."""
|
||||
async def _set_location(device, gps_location, battery, accuracy, attrs):
|
||||
"""Fire HA event to set location."""
|
||||
await async_see(
|
||||
dev_id=device,
|
||||
gps=gps_location, battery=battery,
|
||||
gps=gps_location,
|
||||
battery=battery,
|
||||
gps_accuracy=accuracy,
|
||||
attributes=attrs
|
||||
))
|
||||
)
|
||||
|
||||
return 'Setting location for {}'.format(device)
|
||||
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
|
||||
return True
|
||||
|
@ -19,7 +19,7 @@ from homeassistant.const import (
|
||||
INTERFACES = 2
|
||||
DEFAULT_TIMEOUT = 10
|
||||
|
||||
REQUIREMENTS = ['beautifulsoup4==4.6.3']
|
||||
REQUIREMENTS = ['beautifulsoup4==4.7.1']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -81,13 +81,14 @@ class LinksysAPDeviceScanner(DeviceScanner):
|
||||
request = self._make_request(interface)
|
||||
self.last_results.extend(
|
||||
[x.find_all('td')[1].text
|
||||
for x in BS(request.content, "html.parser")
|
||||
for x in BS(request.content, 'html.parser')
|
||||
.find_all(class_='section-row')]
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def _make_request(self, unit=0):
|
||||
"""Create a request to get the data."""
|
||||
# No, the '&&' is not a typo - this is expected by the web interface.
|
||||
login = base64.b64encode(bytes(self.username, 'utf8')).decode('ascii')
|
||||
pwd = base64.b64encode(bytes(self.password, 'utf8')).decode('ascii')
|
||||
|
@ -4,106 +4,25 @@ Support for the Locative platform.
|
||||
For more details about this platform, please refer to the documentation at
|
||||
https://home-assistant.io/components/device_tracker.locative/
|
||||
"""
|
||||
from functools import partial
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
ATTR_LATITUDE, ATTR_LONGITUDE, STATE_NOT_HOME, HTTP_UNPROCESSABLE_ENTITY)
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
# pylint: disable=unused-import
|
||||
from homeassistant.components.device_tracker import ( # NOQA
|
||||
DOMAIN, PLATFORM_SCHEMA)
|
||||
from homeassistant.components.locative import TRACKER_UPDATE
|
||||
from homeassistant.helpers.dispatcher import async_dispatcher_connect
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DEPENDENCIES = ['http']
|
||||
URL = '/api/locative'
|
||||
DEPENDENCIES = ['locative']
|
||||
|
||||
|
||||
def setup_scanner(hass, config, see, discovery_info=None):
|
||||
"""Set up an endpoint for the Locative application."""
|
||||
hass.http.register_view(LocativeView(see))
|
||||
async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
"""Set up an endpoint for the Locative device tracker."""
|
||||
async def _set_location(device, gps_location, location_name):
|
||||
"""Fire HA event to set location."""
|
||||
await async_see(
|
||||
dev_id=device,
|
||||
gps=gps_location,
|
||||
location_name=location_name
|
||||
)
|
||||
|
||||
async_dispatcher_connect(hass, TRACKER_UPDATE, _set_location)
|
||||
return True
|
||||
|
||||
|
||||
class LocativeView(HomeAssistantView):
|
||||
"""View to handle Locative requests."""
|
||||
|
||||
url = URL
|
||||
name = 'api:locative'
|
||||
|
||||
def __init__(self, see):
|
||||
"""Initialize Locative URL endpoints."""
|
||||
self.see = see
|
||||
|
||||
async def get(self, request):
|
||||
"""Locative message received as GET."""
|
||||
res = await self._handle(request.app['hass'], request.query)
|
||||
return res
|
||||
|
||||
async def post(self, request):
|
||||
"""Locative message received."""
|
||||
data = await request.post()
|
||||
res = await self._handle(request.app['hass'], data)
|
||||
return res
|
||||
|
||||
async def _handle(self, hass, data):
|
||||
"""Handle locative request."""
|
||||
if 'latitude' not in data or 'longitude' not in data:
|
||||
return ('Latitude and longitude not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if 'device' not in data:
|
||||
_LOGGER.error('Device id not specified.')
|
||||
return ('Device id not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if 'trigger' not in data:
|
||||
_LOGGER.error('Trigger is not specified.')
|
||||
return ('Trigger is not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
if 'id' not in data and data['trigger'] != 'test':
|
||||
_LOGGER.error('Location id not specified.')
|
||||
return ('Location id not specified.',
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
||||
device = data['device'].replace('-', '')
|
||||
location_name = data.get('id', data['trigger']).lower()
|
||||
direction = data['trigger']
|
||||
gps_location = (data[ATTR_LATITUDE], data[ATTR_LONGITUDE])
|
||||
|
||||
if direction == 'enter':
|
||||
await hass.async_add_job(
|
||||
partial(self.see, dev_id=device, location_name=location_name,
|
||||
gps=gps_location))
|
||||
return 'Setting location to {}'.format(location_name)
|
||||
|
||||
if direction == 'exit':
|
||||
current_state = hass.states.get(
|
||||
'{}.{}'.format(DOMAIN, device))
|
||||
|
||||
if current_state is None or current_state.state == location_name:
|
||||
location_name = STATE_NOT_HOME
|
||||
await hass.async_add_job(
|
||||
partial(self.see, dev_id=device,
|
||||
location_name=location_name, gps=gps_location))
|
||||
return 'Setting location to not home'
|
||||
|
||||
# Ignore the message if it is telling us to exit a zone that we
|
||||
# aren't currently in. This occurs when a zone is entered
|
||||
# before the previous zone was exited. The enter message will
|
||||
# be sent first, then the exit message will be sent second.
|
||||
return 'Ignoring exit from {} (already in {})'.format(
|
||||
location_name, current_state)
|
||||
|
||||
if direction == 'test':
|
||||
# In the app, a test message can be sent. Just return something to
|
||||
# the user to let them know that it works.
|
||||
return 'Received test message.'
|
||||
|
||||
_LOGGER.error('Received unidentified message from Locative: %s',
|
||||
direction)
|
||||
return ('Received unidentified message: {}'.format(direction),
|
||||
HTTP_UNPROCESSABLE_ENTITY)
|
||||
|
@ -14,7 +14,7 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
"""Set up the MySensors device scanner."""
|
||||
new_devices = mysensors.setup_mysensors_platform(
|
||||
hass, DOMAIN, discovery_info, MySensorsDeviceScanner,
|
||||
device_args=(async_see, ))
|
||||
device_args=(hass, async_see))
|
||||
if not new_devices:
|
||||
return False
|
||||
|
||||
@ -37,12 +37,13 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None):
|
||||
class MySensorsDeviceScanner(mysensors.device.MySensorsDevice):
|
||||
"""Represent a MySensors scanner."""
|
||||
|
||||
def __init__(self, async_see, *args):
|
||||
def __init__(self, hass, async_see, *args):
|
||||
"""Set up instance."""
|
||||
super().__init__(*args)
|
||||
self.async_see = async_see
|
||||
self.hass = hass
|
||||
|
||||
async def async_update_callback(self):
|
||||
async def _async_update_callback(self):
|
||||
"""Update the device."""
|
||||
await self.async_update()
|
||||
node = self.gateway.sensors[self.node_id]
|
||||
|
@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
|
||||
DOMAIN, PLATFORM_SCHEMA, DeviceScanner)
|
||||
from homeassistant.const import CONF_HOST
|
||||
|
||||
REQUIREMENTS = ['pysnmp==4.4.6']
|
||||
REQUIREMENTS = ['pysnmp==4.4.8']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
@ -15,7 +15,7 @@ from homeassistant.const import CONF_HOST, CONF_USERNAME, CONF_PASSWORD
|
||||
from homeassistant.const import CONF_VERIFY_SSL, CONF_MONITORED_CONDITIONS
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['pyunifi==2.13']
|
||||
REQUIREMENTS = ['pyunifi==2.16']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
CONF_PORT = 'port'
|
||||
|
@ -131,6 +131,6 @@ def _response_to_json(response):
|
||||
active_clients[client.get("mac")] = client
|
||||
|
||||
return active_clients
|
||||
except ValueError:
|
||||
except (ValueError, TypeError):
|
||||
_LOGGER.error("Failed to decode response from AP.")
|
||||
return {}
|
||||
|
@ -10,7 +10,7 @@
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Est\u00e0s segur que vols configurar Dialogflow?",
|
||||
"title": "Configuraci\u00f3 del Webhook de Dialogflow"
|
||||
"title": "Configuraci\u00f3 del Webhook Dialogflow"
|
||||
}
|
||||
},
|
||||
"title": "Dialogflow"
|
||||
|
@ -1,7 +1,15 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"not_internet_accessible": "Ihre Home Assistant-Instanz muss \u00fcber das Internet erreichbar sein, um Dialogflow-Nachrichten empfangen zu k\u00f6nnen.",
|
||||
"one_instance_allowed": "Nur eine einzige Instanz ist notwendig."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Um Ereignisse an den Home Assistant zu senden, m\u00fcssen Sie [Webhook-Integration von Dialogflow]({dialogflow_url}) einrichten. \n\nF\u00fcllen Sie die folgenden Informationen aus: \n\n - URL: ` {webhook_url} ` \n - Methode: POST \n - Inhaltstyp: application / json \n\nWeitere Informationen finden Sie in der [Dokumentation]({docs_url})."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "M\u00f6chten Sie Dialogflow wirklich einrichten?",
|
||||
"title": "Dialogflow Webhook einrichten"
|
||||
}
|
||||
},
|
||||
|
@ -4,6 +4,9 @@
|
||||
"not_internet_accessible": "Su instancia de Home Assistant debe ser accesible desde Internet para recibir mensajes de Dialogflow.",
|
||||
"one_instance_allowed": "Solo una instancia es necesaria."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Para enviar eventos a Home Assistant, necesitas configurar [Integracion de flujos de dialogo de webhook]({dialogflow_url}).\n\nRellena la siguiente informaci\u00f3n:\n\n- URL: `{webhook_url}`\n- Method: POST\n- Content Type: application/json\n\nVer [Documentaci\u00f3n]({docs_url}) para mas detalles."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "\u00bfEst\u00e1 seguro de que desea configurar Dialogflow?"
|
||||
|
@ -4,6 +4,9 @@
|
||||
"not_internet_accessible": "Uw Home Assistant instantie moet toegankelijk zijn vanaf het internet om Dialogflow-berichten te ontvangen.",
|
||||
"one_instance_allowed": "Slechts \u00e9\u00e9n instantie is nodig."
|
||||
},
|
||||
"create_entry": {
|
||||
"default": "Om evenementen naar de Home Assistant te verzenden, moet u [webhookintegratie van Dialogflow]({dialogflow_url}) instellen. \n\n Vul de volgende info in: \n\n - URL: `{webhook_url}` \n - Method: POST \n - Content Type: application/json \n\nZie [de documentatie]({docs_url}) voor verdere informatie."
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"description": "Weet u zeker dat u Dialogflow wilt instellen?",
|
||||
|
@ -47,6 +47,7 @@ SERVICE_OCTOPRINT = 'octoprint'
|
||||
SERVICE_FREEBOX = 'freebox'
|
||||
SERVICE_IGD = 'igd'
|
||||
SERVICE_DLNA_DMR = 'dlna_dmr'
|
||||
SERVICE_ROKU = 'roku'
|
||||
|
||||
CONFIG_ENTRY_HANDLERS = {
|
||||
SERVICE_DAIKIN: 'daikin',
|
||||
@ -67,6 +68,7 @@ SERVICE_HANDLERS = {
|
||||
SERVICE_HASSIO: ('hassio', None),
|
||||
SERVICE_AXIS: ('axis', None),
|
||||
SERVICE_APPLE_TV: ('apple_tv', None),
|
||||
SERVICE_ROKU: ('roku', None),
|
||||
SERVICE_WINK: ('wink', None),
|
||||
SERVICE_XIAOMI_GW: ('xiaomi_aqara', None),
|
||||
SERVICE_SABNZBD: ('sabnzbd', None),
|
||||
@ -76,7 +78,6 @@ SERVICE_HANDLERS = {
|
||||
SERVICE_FREEBOX: ('freebox', None),
|
||||
'panasonic_viera': ('media_player', 'panasonic_viera'),
|
||||
'plex_mediaserver': ('media_player', 'plex'),
|
||||
'roku': ('media_player', 'roku'),
|
||||
'yamaha': ('media_player', 'yamaha'),
|
||||
'logitech_mediaserver': ('media_player', 'squeezebox'),
|
||||
'directv': ('media_player', 'directv'),
|
||||
|
@ -6,15 +6,16 @@ https://home-assistant.io/components/doorbird/
|
||||
"""
|
||||
import logging
|
||||
|
||||
from urllib.error import HTTPError
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant.components.http import HomeAssistantView
|
||||
from homeassistant.const import CONF_HOST, CONF_USERNAME, \
|
||||
CONF_PASSWORD, CONF_NAME, CONF_DEVICES, CONF_MONITORED_CONDITIONS
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util import slugify, dt as dt_util
|
||||
|
||||
REQUIREMENTS = ['doorbirdpy==2.0.4']
|
||||
REQUIREMENTS = ['doorbirdpy==2.0.6']
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@ -25,6 +26,7 @@ API_URL = '/api/{}'.format(DOMAIN)
|
||||
CONF_CUSTOM_URL = 'hass_url_override'
|
||||
CONF_DOORBELL_EVENTS = 'doorbell_events'
|
||||
CONF_DOORBELL_NUMS = 'doorbell_numbers'
|
||||
CONF_RELAY_NUMS = 'relay_numbers'
|
||||
CONF_MOTION_EVENTS = 'motion_events'
|
||||
CONF_TOKEN = 'token'
|
||||
|
||||
@ -37,6 +39,10 @@ SENSOR_TYPES = {
|
||||
'name': 'Motion',
|
||||
'device_class': 'motion',
|
||||
},
|
||||
'relay': {
|
||||
'name': 'Relay',
|
||||
'device_class': 'relay',
|
||||
}
|
||||
}
|
||||
|
||||
RESET_DEVICE_FAVORITES = 'doorbird_reset_favorites'
|
||||
@ -47,6 +53,8 @@ DEVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_PASSWORD): cv.string,
|
||||
vol.Optional(CONF_DOORBELL_NUMS, default=[1]): vol.All(
|
||||
cv.ensure_list, [cv.positive_int]),
|
||||
vol.Optional(CONF_RELAY_NUMS, default=[1]): vol.All(
|
||||
cv.ensure_list, [cv.positive_int]),
|
||||
vol.Optional(CONF_CUSTOM_URL): cv.string,
|
||||
vol.Optional(CONF_NAME): cv.string,
|
||||
vol.Optional(CONF_MONITORED_CONDITIONS, default=[]):
|
||||
@ -80,6 +88,7 @@ def setup(hass, config):
|
||||
username = doorstation_config.get(CONF_USERNAME)
|
||||
password = doorstation_config.get(CONF_PASSWORD)
|
||||
doorbell_nums = doorstation_config.get(CONF_DOORBELL_NUMS)
|
||||
relay_nums = doorstation_config.get(CONF_RELAY_NUMS)
|
||||
custom_url = doorstation_config.get(CONF_CUSTOM_URL)
|
||||
events = doorstation_config.get(CONF_MONITORED_CONDITIONS)
|
||||
name = (doorstation_config.get(CONF_NAME)
|
||||
@ -90,7 +99,7 @@ def setup(hass, config):
|
||||
|
||||
if status[0]:
|
||||
doorstation = ConfiguredDoorBird(device, name, events, custom_url,
|
||||
doorbell_nums, token)
|
||||
doorbell_nums, relay_nums, token)
|
||||
doorstations.append(doorstation)
|
||||
_LOGGER.info('Connected to DoorBird "%s" as %s@%s',
|
||||
doorstation.name, username, device_ip)
|
||||
@ -105,7 +114,18 @@ def setup(hass, config):
|
||||
|
||||
# Subscribe to doorbell or motion events
|
||||
if events:
|
||||
doorstation.update_schedule(hass)
|
||||
try:
|
||||
doorstation.update_schedule(hass)
|
||||
except HTTPError:
|
||||
hass.components.persistent_notification.create(
|
||||
'Doorbird configuration failed. Please verify that API '
|
||||
'Operator permission is enabled for the Doorbird user. '
|
||||
'A restart will be required once permissions have been '
|
||||
'verified.',
|
||||
title='Doorbird Configuration Failure',
|
||||
notification_id='doorbird_schedule_error')
|
||||
|
||||
return False
|
||||
|
||||
hass.data[DOMAIN] = doorstations
|
||||
|
||||
@ -148,13 +168,15 @@ def handle_event(event):
|
||||
class ConfiguredDoorBird():
|
||||
"""Attach additional information to pass along with configured device."""
|
||||
|
||||
def __init__(self, device, name, events, custom_url, doorbell_nums, token):
|
||||
def __init__(self, device, name, events, custom_url, doorbell_nums,
|
||||
relay_nums, token):
|
||||
"""Initialize configured device."""
|
||||
self._name = name
|
||||
self._device = device
|
||||
self._custom_url = custom_url
|
||||
self._monitored_events = events
|
||||
self._doorbell_nums = doorbell_nums
|
||||
self._relay_nums = relay_nums
|
||||
self._token = token
|
||||
|
||||
@property
|
||||
@ -218,9 +240,9 @@ class ConfiguredDoorBird():
|
||||
|
||||
# Register HA URL as webhook if not already, then get the ID
|
||||
if not self.webhook_is_registered(hass_url):
|
||||
self.device.change_favorite('http',
|
||||
'Home Assistant on {} ({} events)'
|
||||
.format(hass_url, event), hass_url)
|
||||
self.device.change_favorite('http', 'Home Assistant ({} events)'
|
||||
.format(event), hass_url)
|
||||
|
||||
fav_id = self.get_webhook_id(hass_url)
|
||||
|
||||
if not fav_id:
|
||||
@ -239,6 +261,11 @@ class ConfiguredDoorBird():
|
||||
entry = self.device.get_schedule_entry(event, str(doorbell))
|
||||
entry.output.append(output)
|
||||
self.device.change_schedule(entry)
|
||||
elif event == 'relay':
|
||||
# Repeat edit for each monitored doorbell number
|
||||
for relay in self._relay_nums:
|
||||
entry = self.device.get_schedule_entry(event, str(relay))
|
||||
entry.output.append(output)
|
||||
else:
|
||||
entry = self.device.get_schedule_entry(event)
|
||||
entry.output.append(output)
|
||||
@ -303,6 +330,16 @@ class ConfiguredDoorBird():
|
||||
|
||||
return None
|
||||
|
||||
def get_event_data(self):
|
||||
"""Get data to pass along with HA event."""
|
||||
return {
|
||||
'timestamp': dt_util.utcnow().isoformat(),
|
||||
'live_video_url': self._device.live_video_url,
|
||||
'live_image_url': self._device.live_image_url,
|
||||
'rtsp_live_video_url': self._device.rtsp_live_video_url,
|
||||
'html5_viewer_url': self._device.html5_viewer_url
|
||||
}
|
||||
|
||||
|
||||
class DoorBirdRequestView(HomeAssistantView):
|
||||
"""Provide a page for the device to call."""
|
||||
@ -330,7 +367,14 @@ class DoorBirdRequestView(HomeAssistantView):
|
||||
if request_token == '' or not authenticated:
|
||||
return web.Response(status=401, text='Unauthorized')
|
||||
|
||||
hass.bus.async_fire('{}_{}'.format(DOMAIN, sensor))
|
||||
doorstation = get_doorstation_by_slug(hass, sensor)
|
||||
|
||||
if doorstation:
|
||||
event_data = doorstation.get_event_data()
|
||||
else:
|
||||
event_data = {}
|
||||
|
||||
hass.bus.async_fire('{}_{}'.format(DOMAIN, sensor), event_data)
|
||||
|
||||
return web.Response(status=200, text='OK')
|
||||
|
||||
|
21
homeassistant/components/emulated_roku/.translations/ca.json
Normal file
21
homeassistant/components/emulated_roku/.translations/ca.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"name_exists": "El nom ja existeix"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"advertise_ip": "IP d'advert\u00e8ncies",
|
||||
"advertise_port": "Port d'advert\u00e8ncies",
|
||||
"host_ip": "IP de l'amfitri\u00f3",
|
||||
"listen_port": "Port d'escolta",
|
||||
"name": "Nom",
|
||||
"upnp_bind_multicast": "Enlla\u00e7ar multicast (true/false)"
|
||||
},
|
||||
"title": "Configuraci\u00f3 del servidor"
|
||||
}
|
||||
},
|
||||
"title": "EmulatedRoku"
|
||||
}
|
||||
}
|
21
homeassistant/components/emulated_roku/.translations/en.json
Normal file
21
homeassistant/components/emulated_roku/.translations/en.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"name_exists": "Name already exists"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"advertise_ip": "Advertise IP",
|
||||
"advertise_port": "Advertise port",
|
||||
"host_ip": "Host IP",
|
||||
"listen_port": "Listen port",
|
||||
"name": "Name",
|
||||
"upnp_bind_multicast": "Bind multicast (True/False)"
|
||||
},
|
||||
"title": "Define server configuration"
|
||||
}
|
||||
},
|
||||
"title": "EmulatedRoku"
|
||||
}
|
||||
}
|
13
homeassistant/components/emulated_roku/.translations/et.json
Normal file
13
homeassistant/components/emulated_roku/.translations/et.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"config": {
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host_ip": "",
|
||||
"name": "Nimi"
|
||||
}
|
||||
}
|
||||
},
|
||||
"title": ""
|
||||
}
|
||||
}
|
21
homeassistant/components/emulated_roku/.translations/ko.json
Normal file
21
homeassistant/components/emulated_roku/.translations/ko.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"name_exists": "\uc774\ub984\uc774 \uc774\ubbf8 \uc874\uc7ac\ud569\ub2c8\ub2e4"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"advertise_ip": "\uad11\uace0 IP",
|
||||
"advertise_port": "\uad11\uace0 \ud3ec\ud2b8",
|
||||
"host_ip": "\ud638\uc2a4\ud2b8 IP",
|
||||
"listen_port": "\uc218\uc2e0 \ud3ec\ud2b8",
|
||||
"name": "\uc774\ub984",
|
||||
"upnp_bind_multicast": "\uba40\ud2f0 \uce90\uc2a4\ud2b8 \ubc14\uc778\ub4dc (\ucc38/\uac70\uc9d3)"
|
||||
},
|
||||
"title": "\uc11c\ubc84 \uad6c\uc131 \uc815\uc758"
|
||||
}
|
||||
},
|
||||
"title": "EmulatedRoku"
|
||||
}
|
||||
}
|
21
homeassistant/components/emulated_roku/.translations/no.json
Normal file
21
homeassistant/components/emulated_roku/.translations/no.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"name_exists": "Navnet eksisterer allerede"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"advertise_ip": "Annonser IP",
|
||||
"advertise_port": "Annonser port",
|
||||
"host_ip": "Vert IP",
|
||||
"listen_port": "Lytte port",
|
||||
"name": "Navn",
|
||||
"upnp_bind_multicast": "Bind multicast (True/False)"
|
||||
},
|
||||
"title": "Definer serverkonfigurasjon"
|
||||
}
|
||||
},
|
||||
"title": "EmulatedRoku"
|
||||
}
|
||||
}
|
18
homeassistant/components/emulated_roku/.translations/ru.json
Normal file
18
homeassistant/components/emulated_roku/.translations/ru.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"config": {
|
||||
"abort": {
|
||||
"name_exists": "\u0418\u043c\u044f \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442"
|
||||
},
|
||||
"step": {
|
||||
"user": {
|
||||
"data": {
|
||||
"host_ip": "\u0425\u043e\u0441\u0442",
|
||||
"listen_port": "\u041f\u043e\u0440\u0442",
|
||||
"name": "\u041d\u0430\u0437\u0432\u0430\u043d\u0438\u0435"
|
||||
},
|
||||
"title": "EmulatedRoku"
|
||||
}
|
||||
},
|
||||
"title": "EmulatedRoku"
|
||||
}
|
||||
}
|
84
homeassistant/components/emulated_roku/__init__.py
Normal file
84
homeassistant/components/emulated_roku/__init__.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""
|
||||
Support for Roku API emulation.
|
||||
|
||||
For more details about this component, please refer to the documentation at
|
||||
https://home-assistant.io/components/emulated_roku/
|
||||
"""
|
||||
import voluptuous as vol
|
||||
|
||||
from homeassistant import config_entries, util
|
||||
from homeassistant.const import CONF_NAME
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
|
||||
from .binding import EmulatedRoku
|
||||
from .config_flow import configured_servers
|
||||
from .const import (
|
||||
CONF_ADVERTISE_IP, CONF_ADVERTISE_PORT, CONF_HOST_IP, CONF_LISTEN_PORT,
|
||||
CONF_SERVERS, CONF_UPNP_BIND_MULTICAST, DOMAIN)
|
||||
|
||||
REQUIREMENTS = ['emulated_roku==0.1.7']
|
||||
|
||||
SERVER_CONFIG_SCHEMA = vol.Schema({
|
||||
vol.Required(CONF_NAME): cv.string,
|
||||
vol.Required(CONF_LISTEN_PORT): cv.port,
|
||||
vol.Optional(CONF_HOST_IP): cv.string,
|
||||
vol.Optional(CONF_ADVERTISE_IP): cv.string,
|
||||
vol.Optional(CONF_ADVERTISE_PORT): cv.port,
|
||||
vol.Optional(CONF_UPNP_BIND_MULTICAST): cv.boolean
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
DOMAIN: vol.Schema({
|
||||
vol.Required(CONF_SERVERS):
|
||||
vol.All(cv.ensure_list, [SERVER_CONFIG_SCHEMA]),
|
||||
}),
|
||||
}, extra=vol.ALLOW_EXTRA)
|
||||
|
||||
|
||||
async def async_setup(hass, config):
|
||||
"""Set up the emulated roku component."""
|
||||
conf = config.get(DOMAIN)
|
||||
|
||||
if conf is None:
|
||||
return True
|
||||
|
||||
existing_servers = configured_servers(hass)
|
||||
|
||||
for entry in conf[CONF_SERVERS]:
|
||||
if entry[CONF_NAME] not in existing_servers:
|
||||
hass.async_create_task(hass.config_entries.flow.async_init(
|
||||
DOMAIN,
|
||||
context={'source': config_entries.SOURCE_IMPORT},
|
||||
data=entry
|
||||
))
|
||||
|
||||
return True
|
||||
|
||||
|
||||
async def async_setup_entry(hass, config_entry):
|
||||
"""Set up an emulated roku server from a config entry."""
|
||||
config = config_entry.data
|
||||
|
||||
if DOMAIN not in hass.data:
|
||||
hass.data[DOMAIN] = {}
|
||||
|
||||
name = config[CONF_NAME]
|
||||
listen_port = config[CONF_LISTEN_PORT]
|
||||
host_ip = config.get(CONF_HOST_IP) or util.get_local_ip()
|
||||
advertise_ip = config.get(CONF_ADVERTISE_IP)
|
||||
advertise_port = config.get(CONF_ADVERTISE_PORT)
|
||||
upnp_bind_multicast = config.get(CONF_UPNP_BIND_MULTICAST)
|
||||
|
||||
server = EmulatedRoku(hass, name, host_ip, listen_port,
|
||||
advertise_ip, advertise_port, upnp_bind_multicast)
|
||||
|
||||
hass.data[DOMAIN][name] = server
|
||||
|
||||
return await server.setup()
|
||||
|
||||
|
||||
async def async_unload_entry(hass, entry):
|
||||
"""Unload a config entry."""
|
||||
name = entry.data[CONF_NAME]
|
||||
server = hass.data[DOMAIN].pop(name)
|
||||
return await server.unload()
|
147
homeassistant/components/emulated_roku/binding.py
Normal file
147
homeassistant/components/emulated_roku/binding.py
Normal file
@ -0,0 +1,147 @@
|
||||
"""Bridge between emulated_roku and Home Assistant."""
|
||||
import logging
|
||||
|
||||
from homeassistant.const import (
|
||||
EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP)
|
||||
from homeassistant.core import CoreState, EventOrigin
|
||||
|
||||
LOGGER = logging.getLogger('homeassistant.components.emulated_roku')
|
||||
|
||||
EVENT_ROKU_COMMAND = 'roku_command'
|
||||
|
||||
ATTR_COMMAND_TYPE = 'type'
|
||||
ATTR_SOURCE_NAME = 'source_name'
|
||||
ATTR_KEY = 'key'
|
||||
ATTR_APP_ID = 'app_id'
|
||||
|
||||
ROKU_COMMAND_KEYDOWN = 'keydown'
|
||||
ROKU_COMMAND_KEYUP = 'keyup'
|
||||
ROKU_COMMAND_KEYPRESS = 'keypress'
|
||||
ROKU_COMMAND_LAUNCH = 'launch'
|
||||
|
||||
|
||||
class EmulatedRoku:
|
||||
"""Manages an emulated_roku server."""
|
||||
|
||||
def __init__(self, hass, name, host_ip, listen_port,
|
||||
advertise_ip, advertise_port, upnp_bind_multicast):
|
||||
"""Initialize the properties."""
|
||||
self.hass = hass
|
||||
|
||||
self.roku_usn = name
|
||||
self.host_ip = host_ip
|
||||
self.listen_port = listen_port
|
||||
|
||||
self.advertise_port = advertise_port
|
||||
self.advertise_ip = advertise_ip
|
||||
|
||||
self.bind_multicast = upnp_bind_multicast
|
||||
|
||||
self._api_server = None
|
||||
|
||||
self._unsub_start_listener = None
|
||||
self._unsub_stop_listener = None
|
||||
|
||||
async def setup(self):
|
||||
"""Start the emulated_roku server."""
|
||||
from emulated_roku import EmulatedRokuServer, \
|
||||
EmulatedRokuCommandHandler
|
||||
|
||||
class EventCommandHandler(EmulatedRokuCommandHandler):
|
||||
"""emulated_roku command handler to turn commands into events."""
|
||||
|
||||
def __init__(self, hass):
|
||||
self.hass = hass
|
||||
|
||||
def on_keydown(self, roku_usn, key):
|
||||
"""Handle keydown event."""
|
||||
self.hass.bus.async_fire(EVENT_ROKU_COMMAND, {
|
||||
ATTR_SOURCE_NAME: roku_usn,
|
||||
ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYDOWN,
|
||||
ATTR_KEY: key
|
||||
}, EventOrigin.local)
|
||||
|
||||
def on_keyup(self, roku_usn, key):
|
||||
"""Handle keyup event."""
|
||||
self.hass.bus.async_fire(EVENT_ROKU_COMMAND, {
|
||||
ATTR_SOURCE_NAME: roku_usn,
|
||||
ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYUP,
|
||||
ATTR_KEY: key
|
||||
}, EventOrigin.local)
|
||||
|
||||
def on_keypress(self, roku_usn, key):
|
||||
"""Handle keypress event."""
|
||||
self.hass.bus.async_fire(EVENT_ROKU_COMMAND, {
|
||||
ATTR_SOURCE_NAME: roku_usn,
|
||||
ATTR_COMMAND_TYPE: ROKU_COMMAND_KEYPRESS,
|
||||
ATTR_KEY: key
|
||||
}, EventOrigin.local)
|
||||
|
||||
def launch(self, roku_usn, app_id):
|
||||
"""Handle launch event."""
|
||||
self.hass.bus.async_fire(EVENT_ROKU_COMMAND, {
|
||||
ATTR_SOURCE_NAME: roku_usn,
|
||||
ATTR_COMMAND_TYPE: ROKU_COMMAND_LAUNCH,
|
||||
ATTR_APP_ID: app_id
|
||||
}, EventOrigin.local)
|
||||
|
||||
LOGGER.debug("Intializing emulated_roku %s on %s:%s",
|
||||
self.roku_usn, self.host_ip, self.listen_port)
|
||||
|
||||
handler = EventCommandHandler(self.hass)
|
||||
|
||||
self._api_server = EmulatedRokuServer(
|
||||
self.hass.loop, handler,
|
||||
self.roku_usn, self.host_ip, self.listen_port,
|
||||
advertise_ip=self.advertise_ip,
|
||||
advertise_port=self.advertise_port,
|
||||
bind_multicast=self.bind_multicast
|
||||
)
|
||||
|
||||
async def emulated_roku_stop(event):
|
||||
"""Wrap the call to emulated_roku.close."""
|
||||
LOGGER.debug("Stopping emulated_roku %s", self.roku_usn)
|
||||
self._unsub_stop_listener = None
|
||||
await self._api_server.close()
|
||||
|
||||
async def emulated_roku_start(event):
|
||||
"""Wrap the call to emulated_roku.start."""
|
||||
try:
|
||||
LOGGER.debug("Starting emulated_roku %s", self.roku_usn)
|
||||
self._unsub_start_listener = None
|
||||
await self._api_server.start()
|
||||
except OSError:
|
||||
LOGGER.exception("Failed to start Emulated Roku %s on %s:%s",
|
||||
self.roku_usn, self.host_ip, self.listen_port)
|
||||
# clean up inconsistent state on errors
|
||||
await emulated_roku_stop(None)
|
||||
else:
|
||||
self._unsub_stop_listener = self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_STOP,
|
||||
emulated_roku_stop)
|
||||
|
||||
# start immediately if already running
|
||||
if self.hass.state == CoreState.running:
|
||||
await emulated_roku_start(None)
|
||||
else:
|
||||
self._unsub_start_listener = self.hass.bus.async_listen_once(
|
||||
EVENT_HOMEASSISTANT_START,
|
||||
emulated_roku_start)
|
||||
|
||||
return True
|
||||
|
||||
async def unload(self):
|
||||
"""Unload the emulated_roku server."""
|
||||
LOGGER.debug("Unloading emulated_roku %s", self.roku_usn)
|
||||
|
||||
if self._unsub_start_listener:
|
||||
self._unsub_start_listener()
|
||||
self._unsub_start_listener = None
|
||||
|
||||
if self._unsub_stop_listener:
|
||||
self._unsub_stop_listener()
|
||||
self._unsub_stop_listener = None
|
||||
|
||||
await self._api_server.close()
|
||||
|
||||
return True
|
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