Merge pull request #12609 from home-assistant/release-0-64

0.64.0
This commit is contained in:
Paulus Schoutsen 2018-02-25 12:25:02 -08:00 committed by GitHub
commit 5dcf92fa2a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
468 changed files with 11448 additions and 5727 deletions

View File

@ -29,6 +29,9 @@ omit =
homeassistant/components/arduino.py homeassistant/components/arduino.py
homeassistant/components/*/arduino.py homeassistant/components/*/arduino.py
homeassistant/components/bmw_connected_drive.py
homeassistant/components/*/bmw_connected_drive.py
homeassistant/components/android_ip_webcam.py homeassistant/components/android_ip_webcam.py
homeassistant/components/*/android_ip_webcam.py homeassistant/components/*/android_ip_webcam.py
@ -38,6 +41,9 @@ omit =
homeassistant/components/asterisk_mbox.py homeassistant/components/asterisk_mbox.py
homeassistant/components/*/asterisk_mbox.py homeassistant/components/*/asterisk_mbox.py
homeassistant/components/august.py
homeassistant/components/*/august.py
homeassistant/components/axis.py homeassistant/components/axis.py
homeassistant/components/*/axis.py homeassistant/components/*/axis.py
@ -205,6 +211,9 @@ omit =
homeassistant/components/skybell.py homeassistant/components/skybell.py
homeassistant/components/*/skybell.py homeassistant/components/*/skybell.py
homeassistant/components/smappee.py
homeassistant/components/*/smappee.py
homeassistant/components/tado.py homeassistant/components/tado.py
homeassistant/components/*/tado.py homeassistant/components/*/tado.py
@ -462,6 +471,7 @@ omit =
homeassistant/components/media_player/vizio.py homeassistant/components/media_player/vizio.py
homeassistant/components/media_player/vlc.py homeassistant/components/media_player/vlc.py
homeassistant/components/media_player/volumio.py homeassistant/components/media_player/volumio.py
homeassistant/components/media_player/xiaomi_tv.py
homeassistant/components/media_player/yamaha.py homeassistant/components/media_player/yamaha.py
homeassistant/components/media_player/yamaha_musiccast.py homeassistant/components/media_player/yamaha_musiccast.py
homeassistant/components/media_player/ziggo_mediabox_xl.py homeassistant/components/media_player/ziggo_mediabox_xl.py
@ -551,8 +561,10 @@ omit =
homeassistant/components/sensor/etherscan.py homeassistant/components/sensor/etherscan.py
homeassistant/components/sensor/fastdotcom.py homeassistant/components/sensor/fastdotcom.py
homeassistant/components/sensor/fedex.py homeassistant/components/sensor/fedex.py
homeassistant/components/sensor/filesize.py
homeassistant/components/sensor/fitbit.py homeassistant/components/sensor/fitbit.py
homeassistant/components/sensor/fixer.py homeassistant/components/sensor/fixer.py
homeassistant/components/sensor/folder.py
homeassistant/components/sensor/fritzbox_callmonitor.py homeassistant/components/sensor/fritzbox_callmonitor.py
homeassistant/components/sensor/fritzbox_netmonitor.py homeassistant/components/sensor/fritzbox_netmonitor.py
homeassistant/components/sensor/gearbest.py homeassistant/components/sensor/gearbest.py
@ -617,6 +629,7 @@ omit =
homeassistant/components/sensor/sochain.py homeassistant/components/sensor/sochain.py
homeassistant/components/sensor/sonarr.py homeassistant/components/sensor/sonarr.py
homeassistant/components/sensor/speedtest.py homeassistant/components/sensor/speedtest.py
homeassistant/components/sensor/spotcrime.py
homeassistant/components/sensor/steam_online.py homeassistant/components/sensor/steam_online.py
homeassistant/components/sensor/supervisord.py homeassistant/components/sensor/supervisord.py
homeassistant/components/sensor/swiss_hydrological_data.py homeassistant/components/sensor/swiss_hydrological_data.py

11
.gitattributes vendored
View File

@ -1,3 +1,10 @@
# Ensure Docker script files uses LF to support Docker for Windows. # Ensure Docker script files uses LF to support Docker for Windows.
setup_docker_prereqs eol=lf # Ensure "git config --global core.autocrlf input" before you clone
/virtualization/Docker/scripts/* eol=lf * text eol=lf
*.py whitespace=error
*.ico binary
*.jpg binary
*.png binary
*.zip binary
*.mp3 binary

7
CODEOWNERS Normal file → Executable file
View File

@ -43,6 +43,7 @@ homeassistant/components/hassio.py @home-assistant/hassio
# Individual components # Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/camera/yi.py @bachya homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50 homeassistant/components/climate/ephember.py @ttroy50
homeassistant/components/climate/eq3btsmart.py @rytilahti homeassistant/components/climate/eq3btsmart.py @rytilahti
@ -54,7 +55,10 @@ homeassistant/components/history_graph.py @andrey-git
homeassistant/components/light/tplink.py @rytilahti homeassistant/components/light/tplink.py @rytilahti
homeassistant/components/light/yeelight.py @rytilahti homeassistant/components/light/yeelight.py @rytilahti
homeassistant/components/media_player/kodi.py @armills homeassistant/components/media_player/kodi.py @armills
homeassistant/components/media_player/mediaroom.py @dgomes
homeassistant/components/media_player/monoprice.py @etsinko homeassistant/components/media_player/monoprice.py @etsinko
homeassistant/components/media_player/sonos.py @amelchio
homeassistant/components/media_player/xiaomi_tv.py @fattdev
homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth homeassistant/components/media_player/yamaha_musiccast.py @jalmeroth
homeassistant/components/plant.py @ChristianKuehnel homeassistant/components/plant.py @ChristianKuehnel
homeassistant/components/sensor/airvisual.py @bachya homeassistant/components/sensor/airvisual.py @bachya
@ -63,6 +67,7 @@ homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/pollen.py @bachya homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/sytadin.py @gautric homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/sql.py @dgomes
homeassistant/components/sensor/tibber.py @danielhiversen homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git homeassistant/components/sensor/waqi.py @andrey-git
homeassistant/components/switch/rainmachine.py @bachya homeassistant/components/switch/rainmachine.py @bachya
@ -70,9 +75,11 @@ homeassistant/components/switch/tplink.py @rytilahti
homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi homeassistant/components/xiaomi_aqara.py @danielhiversen @syssi
homeassistant/components/*/axis.py @kane610 homeassistant/components/*/axis.py @kane610
homeassistant/components/*/bmw_connected_drive.py @ChristianKuehnel
homeassistant/components/*/broadlink.py @danielhiversen homeassistant/components/*/broadlink.py @danielhiversen
homeassistant/components/hive.py @Rendili @KJonline homeassistant/components/hive.py @Rendili @KJonline
homeassistant/components/*/hive.py @Rendili @KJonline homeassistant/components/*/hive.py @Rendili @KJonline
homeassistant/components/homekit/* @cdce8p
homeassistant/components/*/deconz.py @kane610 homeassistant/components/*/deconz.py @kane610
homeassistant/components/*/rfxtrx.py @danielhiversen homeassistant/components/*/rfxtrx.py @danielhiversen
homeassistant/components/velux.py @Julius2342 homeassistant/components/velux.py @Julius2342

View File

@ -22,10 +22,23 @@ import os
import inspect import inspect
from homeassistant.const import __version__, __short_version__ from homeassistant.const import __version__, __short_version__
from setup import (
PROJECT_NAME, PROJECT_LONG_DESCRIPTION, PROJECT_COPYRIGHT, PROJECT_AUTHOR, PROJECT_NAME = 'Home Assistant'
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY, GITHUB_PATH, PROJECT_PACKAGE_NAME = 'homeassistant'
GITHUB_URL) PROJECT_AUTHOR = 'The Home Assistant Authors'
PROJECT_COPYRIGHT = ' 2013-2018, {}'.format(PROJECT_AUTHOR)
PROJECT_LONG_DESCRIPTION = ('Home Assistant is an open-source '
'home automation platform running on Python 3. '
'Track and control all devices at home and '
'automate control. '
'Installation in less than a minute.')
PROJECT_GITHUB_USERNAME = 'home-assistant'
PROJECT_GITHUB_REPOSITORY = 'home-assistant'
GITHUB_PATH = '{}/{}'.format(
PROJECT_GITHUB_USERNAME, PROJECT_GITHUB_REPOSITORY)
GITHUB_URL = 'https://github.com/{}'.format(GITHUB_PATH)
sys.path.insert(0, os.path.abspath('_ext')) sys.path.insert(0, os.path.abspath('_ext'))
sys.path.insert(0, os.path.abspath('../homeassistant')) sys.path.insert(0, os.path.abspath('../homeassistant'))

View File

@ -12,7 +12,8 @@ from typing import Any, Optional, Dict
import voluptuous as vol import voluptuous as vol
from homeassistant import ( from homeassistant import (
core, config as conf_util, loader, components as core_components) core, config as conf_util, config_entries, loader,
components as core_components)
from homeassistant.components import persistent_notification from homeassistant.components import persistent_notification
from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE
from homeassistant.setup import async_setup_component from homeassistant.setup import async_setup_component
@ -35,13 +36,13 @@ FIRST_INIT_COMPONENT = set((
def from_config_dict(config: Dict[str, Any], def from_config_dict(config: Dict[str, Any],
hass: Optional[core.HomeAssistant]=None, hass: Optional[core.HomeAssistant] = None,
config_dir: Optional[str]=None, config_dir: Optional[str] = None,
enable_log: bool=True, enable_log: bool = True,
verbose: bool=False, verbose: bool = False,
skip_pip: bool=False, skip_pip: bool = False,
log_rotate_days: Any=None, log_rotate_days: Any = None,
log_file: Any=None) \ log_file: Any = None) \
-> Optional[core.HomeAssistant]: -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary. """Try to configure Home Assistant from a configuration dictionary.
@ -68,12 +69,12 @@ def from_config_dict(config: Dict[str, Any],
@asyncio.coroutine @asyncio.coroutine
def async_from_config_dict(config: Dict[str, Any], def async_from_config_dict(config: Dict[str, Any],
hass: core.HomeAssistant, hass: core.HomeAssistant,
config_dir: Optional[str]=None, config_dir: Optional[str] = None,
enable_log: bool=True, enable_log: bool = True,
verbose: bool=False, verbose: bool = False,
skip_pip: bool=False, skip_pip: bool = False,
log_rotate_days: Any=None, log_rotate_days: Any = None,
log_file: Any=None) \ log_file: Any = None) \
-> Optional[core.HomeAssistant]: -> Optional[core.HomeAssistant]:
"""Try to configure Home Assistant from a configuration dictionary. """Try to configure Home Assistant from a configuration dictionary.
@ -123,9 +124,13 @@ def async_from_config_dict(config: Dict[str, Any],
new_config[key] = value or {} new_config[key] = value or {}
config = new_config config = new_config
hass.config_entries = config_entries.ConfigEntries(hass, config)
yield from hass.config_entries.async_load()
# Filter out the repeating and common config section [homeassistant] # Filter out the repeating and common config section [homeassistant]
components = set(key.split(' ')[0] for key in config.keys() components = set(key.split(' ')[0] for key in config.keys()
if key != core.DOMAIN) if key != core.DOMAIN)
components.update(hass.config_entries.async_domains())
# setup components # setup components
# pylint: disable=not-an-iterable # pylint: disable=not-an-iterable
@ -163,11 +168,11 @@ def async_from_config_dict(config: Dict[str, Any],
def from_config_file(config_path: str, def from_config_file(config_path: str,
hass: Optional[core.HomeAssistant]=None, hass: Optional[core.HomeAssistant] = None,
verbose: bool=False, verbose: bool = False,
skip_pip: bool=True, skip_pip: bool = True,
log_rotate_days: Any=None, log_rotate_days: Any = None,
log_file: Any=None): log_file: Any = None):
"""Read the configuration file and try to start all the functionality. """Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter if given, Will add functionality to 'hass' parameter if given,
@ -188,10 +193,10 @@ def from_config_file(config_path: str,
@asyncio.coroutine @asyncio.coroutine
def async_from_config_file(config_path: str, def async_from_config_file(config_path: str,
hass: core.HomeAssistant, hass: core.HomeAssistant,
verbose: bool=False, verbose: bool = False,
skip_pip: bool=True, skip_pip: bool = True,
log_rotate_days: Any=None, log_rotate_days: Any = None,
log_file: Any=None): log_file: Any = None):
"""Read the configuration file and try to start all the functionality. """Read the configuration file and try to start all the functionality.
Will add functionality to 'hass' parameter. Will add functionality to 'hass' parameter.
@ -219,7 +224,7 @@ def async_from_config_file(config_path: str,
@core.callback @core.callback
def async_enable_logging(hass: core.HomeAssistant, verbose: bool=False, def async_enable_logging(hass: core.HomeAssistant, verbose: bool = False,
log_rotate_days=None, log_file=None) -> None: log_rotate_days=None, log_file=None) -> None:
"""Set up the logging. """Set up the logging.

View File

@ -15,6 +15,7 @@ import homeassistant.core as ha
import homeassistant.config as conf_util import homeassistant.config as conf_util
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers.service import extract_entity_ids
from homeassistant.helpers import intent
from homeassistant.const import ( from homeassistant.const import (
ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE,
SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART,
@ -154,6 +155,12 @@ def async_setup(hass, config):
ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service)
hass.services.async_register( hass.services.async_register(
ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service)
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned on {}"))
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned off {}"))
hass.helpers.intent.async_register(intent.ServiceIntentHandler(
intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}"))
@asyncio.coroutine @asyncio.coroutine
def async_handle_core_service(call): def async_handle_core_service(call):

View File

@ -7,6 +7,7 @@ https://home-assistant.io/components/abode/
import asyncio import asyncio
import logging import logging
from functools import partial from functools import partial
from requests.exceptions import HTTPError, ConnectTimeout
import voluptuous as vol import voluptuous as vol
@ -17,7 +18,6 @@ from homeassistant.const import (
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import discovery from homeassistant.helpers import discovery
from homeassistant.helpers.entity import Entity from homeassistant.helpers.entity import Entity
from requests.exceptions import HTTPError, ConnectTimeout
REQUIREMENTS = ['abodepy==0.12.2'] REQUIREMENTS = ['abodepy==0.12.2']

View File

@ -59,8 +59,7 @@ class CanaryAlarm(AlarmControlPanel):
return STATE_ALARM_ARMED_HOME return STATE_ALARM_ARMED_HOME
elif mode.name == LOCATION_MODE_NIGHT: elif mode.name == LOCATION_MODE_NIGHT:
return STATE_ALARM_ARMED_NIGHT return STATE_ALARM_ARMED_NIGHT
else: return None
return None
@property @property
def device_state_attributes(self): def device_state_attributes(self):

View File

@ -172,9 +172,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
trigger_time) < dt_util.utcnow(): trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger: if self._disarm_after_trigger:
return STATE_ALARM_DISARMED return STATE_ALARM_DISARMED
else: self._state = self._previous_state
self._state = self._previous_state return self._state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \ if self._state in SUPPORTED_PENDING_STATES and \
self._within_pending_time(self._state): self._within_pending_time(self._state):
@ -187,8 +186,7 @@ class ManualAlarm(alarm.AlarmControlPanel):
"""Get the current state.""" """Get the current state."""
if self.state == STATE_ALARM_PENDING: if self.state == STATE_ALARM_PENDING:
return self._previous_state return self._previous_state
else: return self._state
return self._state
def _pending_time(self, state): def _pending_time(self, state):
"""Get the pending time.""" """Get the pending time."""

View File

@ -208,9 +208,8 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
trigger_time) < dt_util.utcnow(): trigger_time) < dt_util.utcnow():
if self._disarm_after_trigger: if self._disarm_after_trigger:
return STATE_ALARM_DISARMED return STATE_ALARM_DISARMED
else: self._state = self._previous_state
self._state = self._previous_state return self._state
return self._state
if self._state in SUPPORTED_PENDING_STATES and \ if self._state in SUPPORTED_PENDING_STATES and \
self._within_pending_time(self._state): self._within_pending_time(self._state):
@ -223,8 +222,7 @@ class ManualMQTTAlarm(alarm.AlarmControlPanel):
"""Get the current state.""" """Get the current state."""
if self.state == STATE_ALARM_PENDING: if self.state == STATE_ALARM_PENDING:
return self._previous_state return self._previous_state
else: return self._state
return self._state
def _pending_time(self, state): def _pending_time(self, state):
"""Get the pending time.""" """Get the pending time."""

View File

@ -1,71 +1,71 @@
# Describes the format for available alarm control panel services # Describes the format for available alarm control panel services
alarm_disarm: alarm_disarm:
description: Send the alarm the command for disarm. description: Send the alarm the command for disarm.
fields: fields:
entity_id: entity_id:
description: Name of alarm control panel to disarm. description: Name of alarm control panel to disarm.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
code: code:
description: An optional code to disarm the alarm control panel with. description: An optional code to disarm the alarm control panel with.
example: 1234 example: 1234
alarm_arm_home: alarm_arm_home:
description: Send the alarm the command for arm home. description: Send the alarm the command for arm home.
fields: fields:
entity_id: entity_id:
description: Name of alarm control panel to arm home. description: Name of alarm control panel to arm home.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
code: code:
description: An optional code to arm home the alarm control panel with. description: An optional code to arm home the alarm control panel with.
example: 1234 example: 1234
alarm_arm_away: alarm_arm_away:
description: Send the alarm the command for arm away. description: Send the alarm the command for arm away.
fields: fields:
entity_id: entity_id:
description: Name of alarm control panel to arm away. description: Name of alarm control panel to arm away.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
code: code:
description: An optional code to arm away the alarm control panel with. description: An optional code to arm away the alarm control panel with.
example: 1234 example: 1234
alarm_arm_night: alarm_arm_night:
description: Send the alarm the command for arm night. description: Send the alarm the command for arm night.
fields: fields:
entity_id: entity_id:
description: Name of alarm control panel to arm night. description: Name of alarm control panel to arm night.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
code: code:
description: An optional code to arm night the alarm control panel with. description: An optional code to arm night the alarm control panel with.
example: 1234 example: 1234
alarm_trigger: alarm_trigger:
description: Send the alarm the command for trigger. description: Send the alarm the command for trigger.
fields: fields:
entity_id: entity_id:
description: Name of alarm control panel to trigger. description: Name of alarm control panel to trigger.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
code: code:
description: An optional code to trigger the alarm control panel with. description: An optional code to trigger the alarm control panel with.
example: 1234 example: 1234
envisalink_alarm_keypress: envisalink_alarm_keypress:
description: Send custom keypresses to the alarm. description: Send custom keypresses to the alarm.
fields: fields:
entity_id: entity_id:
description: Name of the alarm control panel to trigger. description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
keypress: keypress:
description: 'String to send to the alarm panel (1-6 characters).' description: 'String to send to the alarm panel (1-6 characters).'
example: '*71' example: '*71'
alarmdecoder_alarm_toggle_chime: alarmdecoder_alarm_toggle_chime:
description: Send the alarm the toggle chime command. description: Send the alarm the toggle chime command.
fields: fields:
entity_id: entity_id:
description: Name of the alarm control panel to trigger. description: Name of the alarm control panel to trigger.
example: 'alarm_control_panel.downstairs' example: 'alarm_control_panel.downstairs'
code: code:
description: A required code to toggle the alarm control panel chime with. description: A required code to toggle the alarm control panel chime with.
example: 1234 example: 1234

View File

@ -34,7 +34,7 @@ DEFAULT_SKIP_FIRST = False
ALERT_SCHEMA = vol.Schema({ ALERT_SCHEMA = vol.Schema({
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_DONE_MESSAGE, default=None): cv.string, vol.Optional(CONF_DONE_MESSAGE): cv.string,
vol.Required(CONF_ENTITY_ID): cv.entity_id, vol.Required(CONF_ENTITY_ID): cv.entity_id,
vol.Required(CONF_STATE, default=STATE_ON): cv.string, vol.Required(CONF_STATE, default=STATE_ON): cv.string,
vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]), vol.Required(CONF_REPEAT): vol.All(cv.ensure_list, [vol.Coerce(float)]),
@ -121,7 +121,7 @@ def async_setup(hass, config):
# Setup alerts # Setup alerts
for entity_id, alert in alerts.items(): for entity_id, alert in alerts.items():
entity = Alert(hass, entity_id, entity = Alert(hass, entity_id,
alert[CONF_NAME], alert[CONF_DONE_MESSAGE], alert[CONF_NAME], alert.get(CONF_DONE_MESSAGE),
alert[CONF_ENTITY_ID], alert[CONF_STATE], alert[CONF_ENTITY_ID], alert[CONF_STATE],
alert[CONF_REPEAT], alert[CONF_SKIP_FIRST], alert[CONF_REPEAT], alert[CONF_SKIP_FIRST],
alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK]) alert[CONF_NOTIFIERS], alert[CONF_CAN_ACK])

View File

@ -31,10 +31,7 @@ ALEXA_ENTITY_SCHEMA = vol.Schema({
}) })
SMART_HOME_SCHEMA = vol.Schema({ SMART_HOME_SCHEMA = vol.Schema({
vol.Optional( vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
CONF_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA} vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}
}) })

View File

@ -391,6 +391,7 @@ class _AlexaTemperatureSensor(_AlexaInterface):
@ENTITY_ADAPTERS.register(alert.DOMAIN) @ENTITY_ADAPTERS.register(alert.DOMAIN)
@ENTITY_ADAPTERS.register(automation.DOMAIN) @ENTITY_ADAPTERS.register(automation.DOMAIN)
@ENTITY_ADAPTERS.register(group.DOMAIN)
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN) @ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
class _GenericCapabilities(_AlexaEntity): class _GenericCapabilities(_AlexaEntity):
"""A generic, on/off device. """A generic, on/off device.
@ -521,16 +522,6 @@ class _ScriptCapabilities(_AlexaEntity):
supports_deactivation=can_cancel)] supports_deactivation=can_cancel)]
@ENTITY_ADAPTERS.register(group.DOMAIN)
class _GroupCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=True)]
@ENTITY_ADAPTERS.register(sensor.DOMAIN) @ENTITY_ADAPTERS.register(sensor.DOMAIN)
class _SensorCapabilities(_AlexaEntity): class _SensorCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
@ -773,6 +764,8 @@ def extract_entity(funct):
def async_api_turn_on(hass, config, request, entity): def async_api_turn_on(hass, config, request, entity):
"""Process a turn on request.""" """Process a turn on request."""
domain = entity.domain domain = entity.domain
if entity.domain == group.DOMAIN:
domain = ha.DOMAIN
service = SERVICE_TURN_ON service = SERVICE_TURN_ON
if entity.domain == cover.DOMAIN: if entity.domain == cover.DOMAIN:
@ -928,10 +921,7 @@ def async_api_increase_color_temp(hass, config, request, entity):
@asyncio.coroutine @asyncio.coroutine
def async_api_activate(hass, config, request, entity): def async_api_activate(hass, config, request, entity):
"""Process an activate request.""" """Process an activate request."""
if entity.domain == group.DOMAIN: domain = entity.domain
domain = ha.DOMAIN
else:
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_ON, { yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
@ -955,10 +945,7 @@ def async_api_activate(hass, config, request, entity):
@asyncio.coroutine @asyncio.coroutine
def async_api_deactivate(hass, config, request, entity): def async_api_deactivate(hass, config, request, entity):
"""Process a deactivate request.""" """Process a deactivate request."""
if entity.domain == group.DOMAIN: domain = entity.domain
domain = ha.DOMAIN
else:
domain = entity.domain
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, { yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
@ -1178,20 +1165,24 @@ def async_api_adjust_volume(hass, config, request, entity):
@asyncio.coroutine @asyncio.coroutine
def async_api_adjust_volume_step(hass, config, request, entity): def async_api_adjust_volume_step(hass, config, request, entity):
"""Process an adjust volume step request.""" """Process an adjust volume step request."""
volume_step = round(float(request[API_PAYLOAD]['volumeSteps'] / 100), 2) # media_player volume up/down service does not support specifying steps
# each component handles it differently e.g. via config.
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) # For now we use the volumeSteps returned to figure out if we
# should step up/down
volume = current_level + volume_step volume_step = request[API_PAYLOAD]['volumeSteps']
data = { data = {
ATTR_ENTITY_ID: entity.entity_id, ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
} }
yield from hass.services.async_call( if volume_step > 0:
entity.domain, media_player.SERVICE_VOLUME_SET, yield from hass.services.async_call(
data, blocking=False) entity.domain, media_player.SERVICE_VOLUME_UP,
data, blocking=False)
elif volume_step < 0:
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_DOWN,
data, blocking=False)
return api_message(request) return api_message(request)

View File

@ -79,7 +79,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL):
cv.time_period, cv.time_period,
vol.Optional(CONF_SENSORS, default=None): vol.Optional(CONF_SENSORS):
vol.All(cv.ensure_list, [vol.In(SENSORS)]), vol.All(cv.ensure_list, [vol.In(SENSORS)]),
})]) })])
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)

View File

@ -140,11 +140,11 @@ CONFIG_SCHEMA = vol.Schema({
cv.time_period, cv.time_period,
vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string,
vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string,
vol.Optional(CONF_SWITCHES, default=None): vol.Optional(CONF_SWITCHES):
vol.All(cv.ensure_list, [vol.In(SWITCHES)]), vol.All(cv.ensure_list, [vol.In(SWITCHES)]),
vol.Optional(CONF_SENSORS, default=None): vol.Optional(CONF_SENSORS):
vol.All(cv.ensure_list, [vol.In(SENSORS)]), vol.All(cv.ensure_list, [vol.In(SENSORS)]),
vol.Optional(CONF_MOTION_SENSOR, default=None): cv.boolean, vol.Optional(CONF_MOTION_SENSOR): cv.boolean,
})]) })])
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
@ -165,9 +165,9 @@ def async_setup(hass, config):
password = cam_config.get(CONF_PASSWORD) password = cam_config.get(CONF_PASSWORD)
name = cam_config[CONF_NAME] name = cam_config[CONF_NAME]
interval = cam_config[CONF_SCAN_INTERVAL] interval = cam_config[CONF_SCAN_INTERVAL]
switches = cam_config[CONF_SWITCHES] switches = cam_config.get(CONF_SWITCHES)
sensors = cam_config[CONF_SENSORS] sensors = cam_config.get(CONF_SENSORS)
motion = cam_config[CONF_MOTION_SENSOR] motion = cam_config.get(CONF_MOTION_SENSOR)
# Init ip webcam # Init ip webcam
cam = PyDroidIPCam( cam = PyDroidIPCam(

View File

@ -60,7 +60,7 @@ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(ensure_list, [vol.Schema({ DOMAIN: vol.All(ensure_list, [vol.Schema({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Required(CONF_LOGIN_ID): cv.string, vol.Required(CONF_LOGIN_ID): cv.string,
vol.Optional(CONF_CREDENTIALS, default=None): cv.string, vol.Optional(CONF_CREDENTIALS): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_START_OFF, default=False): cv.boolean, vol.Optional(CONF_START_OFF, default=False): cv.boolean,
})]) })])

View File

@ -0,0 +1,257 @@
"""
Support for August devices.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/august/
"""
import logging
from datetime import timedelta
import voluptuous as vol
from requests import RequestException
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT)
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
_CONFIGURING = {}
REQUIREMENTS = ['py-august==0.3.0']
DEFAULT_TIMEOUT = 10
ACTIVITY_FETCH_LIMIT = 10
ACTIVITY_INITIAL_FETCH_LIMIT = 20
CONF_LOGIN_METHOD = 'login_method'
CONF_INSTALL_ID = 'install_id'
NOTIFICATION_ID = 'august_notification'
NOTIFICATION_TITLE = "August Setup"
AUGUST_CONFIG_FILE = '.august.conf'
DATA_AUGUST = 'august'
DOMAIN = 'august'
DEFAULT_ENTITY_NAMESPACE = 'august'
MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5)
DEFAULT_SCAN_INTERVAL = timedelta(seconds=5)
LOGIN_METHODS = ['phone', 'email']
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS),
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_INSTALL_ID): cv.string,
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
})
}, extra=vol.ALLOW_EXTRA)
AUGUST_COMPONENTS = [
'camera', 'binary_sensor', 'lock'
]
def request_configuration(hass, config, api, authenticator):
"""Request configuration steps from the user."""
configurator = hass.components.configurator
def august_configuration_callback(data):
"""Run when the configuration callback is called."""
from august.authenticator import ValidationResult
result = authenticator.validate_verification_code(
data.get('verification_code'))
if result == ValidationResult.INVALID_VERIFICATION_CODE:
configurator.notify_errors(_CONFIGURING[DOMAIN],
"Invalid verification code")
elif result == ValidationResult.VALIDATED:
setup_august(hass, config, api, authenticator)
if DOMAIN not in _CONFIGURING:
authenticator.send_verification_code()
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
login_method = conf.get(CONF_LOGIN_METHOD)
_CONFIGURING[DOMAIN] = configurator.request_config(
NOTIFICATION_TITLE,
august_configuration_callback,
description="Please check your {} ({}) and enter the verification "
"code below".format(login_method, username),
submit_caption='Verify',
fields=[{
'id': 'verification_code',
'name': "Verification code",
'type': 'string'}]
)
def setup_august(hass, config, api, authenticator):
"""Set up the August component."""
from august.authenticator import AuthenticationState
authentication = None
try:
authentication = authenticator.authenticate()
except RequestException as ex:
_LOGGER.error("Unable to connect to August service: %s", str(ex))
hass.components.persistent_notification.create(
"Error: {}<br />"
"You will need to restart hass after fixing."
"".format(ex),
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)
state = authentication.state
if state == AuthenticationState.AUTHENTICATED:
if DOMAIN in _CONFIGURING:
hass.components.configurator.request_done(_CONFIGURING.pop(DOMAIN))
hass.data[DATA_AUGUST] = AugustData(api, authentication.access_token)
for component in AUGUST_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
elif state == AuthenticationState.BAD_PASSWORD:
return False
elif state == AuthenticationState.REQUIRES_VALIDATION:
request_configuration(hass, config, api, authenticator)
return True
return False
def setup(hass, config):
"""Set up the August component."""
from august.api import Api
from august.authenticator import Authenticator
conf = config[DOMAIN]
api = Api(timeout=conf.get(CONF_TIMEOUT))
authenticator = Authenticator(
api,
conf.get(CONF_LOGIN_METHOD),
conf.get(CONF_USERNAME),
conf.get(CONF_PASSWORD),
install_id=conf.get(CONF_INSTALL_ID),
access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE))
return setup_august(hass, config, api, authenticator)
class AugustData:
"""August data object."""
def __init__(self, api, access_token):
"""Init August data object."""
self._api = api
self._access_token = access_token
self._doorbells = self._api.get_doorbells(self._access_token) or []
self._locks = self._api.get_locks(self._access_token) or []
self._house_ids = [d.house_id for d in self._doorbells + self._locks]
self._doorbell_detail_by_id = {}
self._lock_status_by_id = {}
self._lock_detail_by_id = {}
self._activities_by_id = {}
@property
def house_ids(self):
"""Return a list of house_ids."""
return self._house_ids
@property
def doorbells(self):
"""Return a list of doorbells."""
return self._doorbells
@property
def locks(self):
"""Return a list of locks."""
return self._locks
def get_device_activities(self, device_id, *activity_types):
"""Return a list of activities."""
self._update_device_activities()
activities = self._activities_by_id.get(device_id, [])
if activity_types:
return [a for a in activities if a.activity_type in activity_types]
return activities
def get_latest_device_activity(self, device_id, *activity_types):
"""Return latest activity."""
activities = self.get_device_activities(device_id, *activity_types)
return next(iter(activities or []), None)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT):
"""Update data object with latest from August API."""
for house_id in self.house_ids:
activities = self._api.get_house_activities(self._access_token,
house_id,
limit=limit)
device_ids = {a.device_id for a in activities}
for device_id in device_ids:
self._activities_by_id[device_id] = [a for a in activities if
a.device_id == device_id]
def get_doorbell_detail(self, doorbell_id):
"""Return doorbell detail."""
self._update_doorbells()
return self._doorbell_detail_by_id.get(doorbell_id)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_doorbells(self):
detail_by_id = {}
for doorbell in self._doorbells:
detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail(
self._access_token, doorbell.device_id)
self._doorbell_detail_by_id = detail_by_id
def get_lock_status(self, lock_id):
"""Return lock status."""
self._update_locks()
return self._lock_status_by_id.get(lock_id)
def get_lock_detail(self, lock_id):
"""Return lock detail."""
self._update_locks()
return self._lock_detail_by_id.get(lock_id)
@Throttle(MIN_TIME_BETWEEN_UPDATES)
def _update_locks(self):
status_by_id = {}
detail_by_id = {}
for lock in self._locks:
status_by_id[lock.device_id] = self._api.get_lock_status(
self._access_token, lock.device_id)
detail_by_id[lock.device_id] = self._api.get_lock_detail(
self._access_token, lock.device_id)
self._lock_status_by_id = status_by_id
self._lock_detail_by_id = detail_by_id
def lock(self, device_id):
"""Lock the device."""
return self._api.lock(self._access_token, device_id)
def unlock(self, device_id):
"""Unlock the device."""
return self._api.unlock(self._access_token, device_id)

View File

@ -0,0 +1,97 @@
"""
Support for August binary sensors.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.august/
"""
from datetime import timedelta, datetime
from homeassistant.components.august import DATA_AUGUST
from homeassistant.components.binary_sensor import (BinarySensorDevice)
DEPENDENCIES = ['august']
SCAN_INTERVAL = timedelta(seconds=5)
def _retrieve_online_state(data, doorbell):
"""Get the latest state of the sensor."""
detail = data.get_doorbell_detail(doorbell.device_id)
return detail.is_online
def _retrieve_motion_state(data, doorbell):
from august.activity import ActivityType
return _activity_time_based_state(data, doorbell,
[ActivityType.DOORBELL_MOTION,
ActivityType.DOORBELL_DING])
def _retrieve_ding_state(data, doorbell):
from august.activity import ActivityType
return _activity_time_based_state(data, doorbell,
[ActivityType.DOORBELL_DING])
def _activity_time_based_state(data, doorbell, activity_types):
"""Get the latest state of the sensor."""
latest = data.get_latest_device_activity(doorbell.device_id,
*activity_types)
if latest is not None:
start = latest.activity_start_time
end = latest.activity_end_time + timedelta(seconds=30)
return start <= datetime.now() <= end
return None
# Sensor types: Name, device_class, state_provider
SENSOR_TYPES = {
'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state],
'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state],
'doorbell_online': ['Online', 'connectivity', _retrieve_online_state],
}
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the August binary sensors."""
data = hass.data[DATA_AUGUST]
devices = []
for doorbell in data.doorbells:
for sensor_type in SENSOR_TYPES:
devices.append(AugustBinarySensor(data, sensor_type, doorbell))
add_devices(devices, True)
class AugustBinarySensor(BinarySensorDevice):
"""Representation of an August binary sensor."""
def __init__(self, data, sensor_type, doorbell):
"""Initialize the sensor."""
self._data = data
self._sensor_type = sensor_type
self._doorbell = doorbell
self._state = None
@property
def is_on(self):
"""Return true if the binary sensor is on."""
return self._state
@property
def device_class(self):
"""Return the class of this device, from component DEVICE_CLASSES."""
return SENSOR_TYPES[self._sensor_type][1]
@property
def name(self):
"""Return the name of the binary sensor."""
return "{} {}".format(self._doorbell.device_name,
SENSOR_TYPES[self._sensor_type][0])
def update(self):
"""Get the latest state of the sensor."""
state_provider = SENSOR_TYPES[self._sensor_type][2]
self._state = state_provider(self._data, self._doorbell)

View File

@ -24,7 +24,7 @@ SENSOR_TYPES = {
} }
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES): vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
}) })

View File

@ -7,7 +7,8 @@ https://home-assistant.io/components/binary_sensor.deconz/
import asyncio import asyncio
from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.binary_sensor import BinarySensorDevice
from homeassistant.components.deconz import DOMAIN as DECONZ_DATA from homeassistant.components.deconz import (
DOMAIN as DATA_DECONZ, DATA_DECONZ_ID)
from homeassistant.const import ATTR_BATTERY_LEVEL from homeassistant.const import ATTR_BATTERY_LEVEL
from homeassistant.core import callback from homeassistant.core import callback
@ -21,7 +22,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
return return
from pydeconz.sensor import DECONZ_BINARY_SENSOR from pydeconz.sensor import DECONZ_BINARY_SENSOR
sensors = hass.data[DECONZ_DATA].sensors sensors = hass.data[DATA_DECONZ].sensors
entities = [] entities = []
for key in sorted(sensors.keys(), key=int): for key in sorted(sensors.keys(), key=int):
@ -42,6 +43,7 @@ class DeconzBinarySensor(BinarySensorDevice):
def async_added_to_hass(self): def async_added_to_hass(self):
"""Subscribe sensors events.""" """Subscribe sensors events."""
self._sensor.register_async_callback(self.async_update_callback) self._sensor.register_async_callback(self.async_update_callback)
self.hass.data[DATA_DECONZ_ID][self.entity_id] = self._sensor.deconz_id
@callback @callback
def async_update_callback(self, reason): def async_update_callback(self, reason):

View File

@ -50,7 +50,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice):
self._zone_type = zone_type self._zone_type = zone_type
self._zone_number = zone_number self._zone_number = zone_number
_LOGGER.debug('Setting up zone: ' + zone_name) _LOGGER.debug('Setting up zone: %s', zone_name)
super().__init__(zone_name, info, controller) super().__init__(zone_name, info, controller)
@asyncio.coroutine @asyncio.coroutine

View File

@ -56,7 +56,7 @@ CUSTOMIZE_SCHEMA = vol.Schema({
}) })
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=None): cv.string, vol.Optional(CONF_NAME): cv.string,
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port,
vol.Optional(CONF_SSL, default=False): cv.boolean, vol.Optional(CONF_SSL, default=False): cv.boolean,

View File

@ -25,7 +25,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All({ vol.All({
vol.Required(CONF_ID): cv.positive_int, vol.Required(CONF_ID): cv.positive_int,
vol.Optional(CONF_NAME): cv.string, vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_TYPE, default=None): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_INVERTING, default=False): cv.boolean, vol.Optional(CONF_INVERTING, default=False): cv.boolean,
}, validate_name) }, validate_name)
]) ])
@ -43,7 +43,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
product_cfg = device['product_cfg'] product_cfg = device['product_cfg']
product = device['product'] product = device['product']
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
product_cfg[CONF_TYPE], product_cfg.get(CONF_TYPE),
product_cfg[CONF_INVERTING], product_cfg[CONF_INVERTING],
product) product)
devices.append(sensor) devices.append(sensor)
@ -52,7 +52,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
for sensor_cfg in binary_sensors: for sensor_cfg in binary_sensors:
ihc_id = sensor_cfg[CONF_ID] ihc_id = sensor_cfg[CONF_ID]
name = sensor_cfg[CONF_NAME] name = sensor_cfg[CONF_NAME]
sensor_type = sensor_cfg[CONF_TYPE] sensor_type = sensor_cfg.get(CONF_TYPE)
inverting = sensor_cfg[CONF_INVERTING] inverting = sensor_cfg[CONF_INVERTING]
sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info,
sensor_type, inverting) sensor_type, inverting)
@ -70,7 +70,7 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice):
def __init__(self, ihc_controller, name, ihc_id: int, info: bool, def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool, sensor_type: str, inverting: bool,
product: Element=None) -> None: product: Element = None) -> None:
"""Initialize the IHC binary sensor.""" """Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product) super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None self._state = None

View File

@ -35,7 +35,7 @@ DEPENDENCIES = ['knx']
AUTOMATION_SCHEMA = vol.Schema({ AUTOMATION_SCHEMA = vol.Schema({
vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string, vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string,
vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port, vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port,
vol.Required(CONF_ACTION, default=None): cv.SCRIPT_SCHEMA vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA
}) })
AUTOMATIONS_SCHEMA = vol.All( AUTOMATIONS_SCHEMA = vol.All(
@ -49,16 +49,13 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICE_CLASS): cv.string, vol.Optional(CONF_DEVICE_CLASS): cv.string,
vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT): vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT):
cv.positive_int, cv.positive_int,
vol.Optional(CONF_AUTOMATION, default=None): AUTOMATIONS_SCHEMA, vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA,
}) })
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up binary sensor(s) for KNX platform.""" """Set up binary sensor(s) for KNX platform."""
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None: if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices) async_add_devices_discovery(hass, discovery_info, async_add_devices)
else: else:

View File

@ -50,10 +50,10 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_CAMERAS, default=[]): vol.Optional(CONF_CAMERAS, default=[]):
vol.All(cv.ensure_list, [cv.string]), vol.All(cv.ensure_list, [cv.string]),
vol.Optional(CONF_HOME): cv.string, vol.Optional(CONF_HOME): cv.string,
vol.Optional(CONF_PRESENCE_SENSORS, default=PRESENCE_SENSOR_TYPES): vol.Optional(CONF_PRESENCE_SENSORS, default=list(PRESENCE_SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]), vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]),
vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int,
vol.Optional(CONF_WELCOME_SENSORS, default=WELCOME_SENSOR_TYPES): vol.Optional(CONF_WELCOME_SENSORS, default=list(WELCOME_SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]), vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]),
}) })

View File

@ -27,7 +27,7 @@ SENSOR_TYPES = {
} }
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_MONITORED_CONDITIONS, default=SENSOR_TYPES): vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)):
vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]),
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
}) })

View File

@ -28,15 +28,15 @@ DEPENDENCIES = ['rfxtrx']
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_DEVICES, default={}): { vol.Optional(CONF_DEVICES, default={}): {
cv.string: vol.Schema({ cv.string: vol.Schema({
vol.Optional(CONF_NAME, default=None): cv.string, vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_DEVICE_CLASS, default=None): vol.Optional(CONF_DEVICE_CLASS):
DEVICE_CLASSES_SCHEMA, DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean,
vol.Optional(CONF_OFF_DELAY, default=None): vol.Optional(CONF_OFF_DELAY):
vol.Any(cv.time_period, cv.positive_timedelta), vol.Any(cv.time_period, cv.positive_timedelta),
vol.Optional(CONF_DATA_BITS, default=None): cv.positive_int, vol.Optional(CONF_DATA_BITS): cv.positive_int,
vol.Optional(CONF_COMMAND_ON, default=None): cv.byte, vol.Optional(CONF_COMMAND_ON): cv.byte,
vol.Optional(CONF_COMMAND_OFF, default=None): cv.byte vol.Optional(CONF_COMMAND_OFF): cv.byte
}) })
}, },
vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean,
@ -48,7 +48,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
import RFXtrx as rfxtrxmod import RFXtrx as rfxtrxmod
sensors = [] sensors = []
for packet_id, entity in config['devices'].items(): for packet_id, entity in config[CONF_DEVICES].items():
event = rfxtrx.get_rfx_object(packet_id) event = rfxtrx.get_rfx_object(packet_id)
device_id = slugify(event.device.id_string.lower()) device_id = slugify(event.device.id_string.lower())
@ -64,10 +64,10 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
entity[ATTR_NAME], entity[CONF_DEVICE_CLASS]) entity[ATTR_NAME], entity[CONF_DEVICE_CLASS])
device = RfxtrxBinarySensor( device = RfxtrxBinarySensor(
event, entity[ATTR_NAME], entity[CONF_DEVICE_CLASS], event, entity.get(CONF_NAME), entity.get(CONF_DEVICE_CLASS),
entity[CONF_FIRE_EVENT], entity[CONF_OFF_DELAY], entity[CONF_FIRE_EVENT], entity.get(CONF_OFF_DELAY),
entity[CONF_DATA_BITS], entity[CONF_COMMAND_ON], entity.get(CONF_DATA_BITS), entity.get(CONF_COMMAND_ON),
entity[CONF_COMMAND_OFF]) entity.get(CONF_COMMAND_OFF))
device.hass = hass device.hass = hass
sensors.append(device) sensors.append(device)
rfxtrx.RFX_DEVICES[device_id] = device rfxtrx.RFX_DEVICES[device_id] = device

View File

@ -26,7 +26,7 @@ DEFAULT_SETTLE_TIME = 20
DEPENDENCIES = ['rpi_pfio'] DEPENDENCIES = ['rpi_pfio']
PORT_SCHEMA = vol.Schema({ PORT_SCHEMA = vol.Schema({
vol.Optional(CONF_NAME, default=None): cv.string, vol.Optional(CONF_NAME): cv.string,
vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME): vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME):
cv.positive_int, cv.positive_int,
vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean,
@ -44,7 +44,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
binary_sensors = [] binary_sensors = []
ports = config.get(CONF_PORTS) ports = config.get(CONF_PORTS)
for port, port_entity in ports.items(): for port, port_entity in ports.items():
name = port_entity[CONF_NAME] name = port_entity.get(CONF_NAME)
settle_time = port_entity[CONF_SETTLE_TIME] / 1000 settle_time = port_entity[CONF_SETTLE_TIME] / 1000
invert_logic = port_entity[CONF_INVERT_LOGIC] invert_logic = port_entity[CONF_INVERT_LOGIC]

View File

@ -47,7 +47,7 @@ DEFAULT_OFFSET = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES), vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES),
vol.Optional(CONF_PROVINCE, default=None): cv.string, vol.Optional(CONF_PROVINCE): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int), vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int),
vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS):

View File

@ -0,0 +1,105 @@
"""
Reads vehicle status from BMW connected drive portal.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/bmw_connected_drive/
"""
import logging
import datetime
import voluptuous as vol
from homeassistant.helpers import discovery
from homeassistant.helpers.event import track_utc_time_change
import homeassistant.helpers.config_validation as cv
from homeassistant.const import (
CONF_USERNAME, CONF_PASSWORD
)
REQUIREMENTS = ['bimmer_connected==0.3.0']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'bmw_connected_drive'
CONF_VALUES = 'values'
CONF_COUNTRY = 'country'
ACCOUNT_SCHEMA = vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_COUNTRY): cv.string,
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: {
cv.string: ACCOUNT_SCHEMA
},
}, extra=vol.ALLOW_EXTRA)
BMW_COMPONENTS = ['device_tracker', 'sensor']
UPDATE_INTERVAL = 5 # in minutes
def setup(hass, config):
"""Set up the BMW connected drive components."""
accounts = []
for name, account_config in config[DOMAIN].items():
username = account_config[CONF_USERNAME]
password = account_config[CONF_PASSWORD]
country = account_config[CONF_COUNTRY]
_LOGGER.debug('Adding new account %s', name)
bimmer = BMWConnectedDriveAccount(username, password, country, name)
accounts.append(bimmer)
# update every UPDATE_INTERVAL minutes, starting now
# this should even out the load on the servers
now = datetime.datetime.now()
track_utc_time_change(
hass, bimmer.update,
minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL),
second=now.second)
hass.data[DOMAIN] = accounts
for account in accounts:
account.update()
for component in BMW_COMPONENTS:
discovery.load_platform(hass, component, DOMAIN, {}, config)
return True
class BMWConnectedDriveAccount(object):
"""Representation of a BMW vehicle."""
def __init__(self, username: str, password: str, country: str,
name: str) -> None:
"""Constructor."""
from bimmer_connected.account import ConnectedDriveAccount
self.account = ConnectedDriveAccount(username, password, country)
self.name = name
self._update_listeners = []
def update(self, *_):
"""Update the state of all vehicles.
Notify all listeners about the update.
"""
_LOGGER.debug('Updating vehicle state for account %s, '
'notifying %d listeners',
self.name, len(self._update_listeners))
try:
self.account.update_vehicle_states()
for listener in self._update_listeners:
listener()
except IOError as exception:
_LOGGER.error('Error updating the vehicle state.')
_LOGGER.exception(exception)
def add_update_listener(self, listener):
"""Add a listener for update notifications."""
self._update_listeners.append(listener)

View File

@ -166,7 +166,7 @@ class WebDavCalendarData(object):
self.event = { self.event = {
"summary": vevent.summary.value, "summary": vevent.summary.value,
"start": self.get_hass_date(vevent.dtstart.value), "start": self.get_hass_date(vevent.dtstart.value),
"end": self.get_hass_date(vevent.dtend.value), "end": self.get_hass_date(self.get_end_date(vevent)),
"location": self.get_attr_value(vevent, "location"), "location": self.get_attr_value(vevent, "location"),
"description": self.get_attr_value(vevent, "description") "description": self.get_attr_value(vevent, "description")
} }
@ -194,7 +194,7 @@ class WebDavCalendarData(object):
@staticmethod @staticmethod
def is_over(vevent): def is_over(vevent):
"""Return if the event is over.""" """Return if the event is over."""
return dt.now() > WebDavCalendarData.to_datetime(vevent.dtend.value) return dt.now() > WebDavCalendarData.get_end_date(vevent)
@staticmethod @staticmethod
def get_hass_date(obj): def get_hass_date(obj):
@ -217,3 +217,17 @@ class WebDavCalendarData(object):
if hasattr(obj, attribute): if hasattr(obj, attribute):
return getattr(obj, attribute).value return getattr(obj, attribute).value
return None return None
@staticmethod
def get_end_date(obj):
"""Return the end datetime as determined by dtend or duration."""
if hasattr(obj, "dtend"):
enddate = obj.dtend.value
elif hasattr(obj, "duration"):
enddate = obj.dtstart.value + obj.duration.value
else:
enddate = obj.dtstart.value + timedelta(days=1)
return WebDavCalendarData.to_datetime(enddate)

View File

@ -498,7 +498,7 @@ class TodoistProjectData(object):
# Organize the best tasks (so users can see all the tasks # Organize the best tasks (so users can see all the tasks
# they have, organized) # they have, organized)
while len(project_tasks) > 0: while project_tasks:
best_task = self.select_best_task(project_tasks) best_task = self.select_best_task(project_tasks)
_LOGGER.debug("Found Todoist Task: %s", best_task[SUMMARY]) _LOGGER.debug("Found Todoist Task: %s", best_task[SUMMARY])
project_tasks.remove(best_task) project_tasks.remove(best_task)

View File

@ -0,0 +1,76 @@
"""
Support for August camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.august/
"""
from datetime import timedelta
import requests
from homeassistant.components.august import DATA_AUGUST, DEFAULT_TIMEOUT
from homeassistant.components.camera import Camera
DEPENDENCIES = ['august']
SCAN_INTERVAL = timedelta(seconds=5)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up August cameras."""
data = hass.data[DATA_AUGUST]
devices = []
for doorbell in data.doorbells:
devices.append(AugustCamera(data, doorbell, DEFAULT_TIMEOUT))
add_devices(devices, True)
class AugustCamera(Camera):
"""An implementation of a Canary security camera."""
def __init__(self, data, doorbell, timeout):
"""Initialize a Canary security camera."""
super().__init__()
self._data = data
self._doorbell = doorbell
self._timeout = timeout
self._image_url = None
self._image_content = None
@property
def name(self):
"""Return the name of this device."""
return self._doorbell.device_name
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._doorbell.has_subscription
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return True
@property
def brand(self):
"""Return the camera brand."""
return 'August'
@property
def model(self):
"""Return the camera model."""
return 'Doorbell'
def camera_image(self):
"""Return bytes of camera image."""
latest = self._data.get_doorbell_detail(self._doorbell.device_id)
if self._image_url is not latest.image_url:
self._image_url = latest.image_url
self._image_content = requests.get(self._image_url,
timeout=self._timeout).content
return self._image_content

View File

@ -18,8 +18,10 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
DEPENDENCIES = ['doorbird'] DEPENDENCIES = ['doorbird']
_CAMERA_LAST_VISITOR = "DoorBird Last Ring" _CAMERA_LAST_VISITOR = "DoorBird Last Ring"
_CAMERA_LAST_MOTION = "DoorBird Last Motion"
_CAMERA_LIVE = "DoorBird Live" _CAMERA_LIVE = "DoorBird Live"
_LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1) _LAST_VISITOR_INTERVAL = datetime.timedelta(minutes=1)
_LAST_MOTION_INTERVAL = datetime.timedelta(minutes=1)
_LIVE_INTERVAL = datetime.timedelta(seconds=1) _LIVE_INTERVAL = datetime.timedelta(seconds=1)
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_TIMEOUT = 10 # seconds _TIMEOUT = 10 # seconds
@ -34,6 +36,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
DoorBirdCamera( DoorBirdCamera(
device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR, device.history_image_url(1, 'doorbell'), _CAMERA_LAST_VISITOR,
_LAST_VISITOR_INTERVAL), _LAST_VISITOR_INTERVAL),
DoorBirdCamera(
device.history_image_url(1, 'motionsensor'), _CAMERA_LAST_MOTION,
_LAST_MOTION_INTERVAL),
]) ])

View File

@ -119,6 +119,8 @@ class MjpegCamera(Camera):
else: else:
req = requests.get(self._mjpeg_url, stream=True, timeout=10) req = requests.get(self._mjpeg_url, stream=True, timeout=10)
# https://github.com/PyCQA/pylint/issues/1437
# pylint: disable=no-member
with closing(req) as response: with closing(req) as response:
return extract_image_from_mjpeg(response.iter_content(102400)) return extract_image_from_mjpeg(response.iter_content(102400))

View File

@ -6,18 +6,19 @@ https://home-assistant.io/components/camera.onvif/
""" """
import asyncio import asyncio
import logging import logging
import os
import voluptuous as vol import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT,
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA ATTR_ENTITY_ID)
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, DOMAIN
from homeassistant.components.ffmpeg import ( from homeassistant.components.ffmpeg import (
DATA_FFMPEG, CONF_EXTRA_ARGUMENTS) DATA_FFMPEG, CONF_EXTRA_ARGUMENTS)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.aiohttp_client import ( from homeassistant.helpers.aiohttp_client import (
async_aiohttp_proxy_stream) async_aiohttp_proxy_stream)
from homeassistant.helpers.service import extract_entity_ids
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -33,6 +34,22 @@ DEFAULT_USERNAME = 'admin'
DEFAULT_PASSWORD = '888888' DEFAULT_PASSWORD = '888888'
DEFAULT_ARGUMENTS = '-q:v 2' DEFAULT_ARGUMENTS = '-q:v 2'
ATTR_PAN = "pan"
ATTR_TILT = "tilt"
ATTR_ZOOM = "zoom"
DIR_UP = "UP"
DIR_DOWN = "DOWN"
DIR_LEFT = "LEFT"
DIR_RIGHT = "RIGHT"
ZOOM_OUT = "ZOOM_OUT"
ZOOM_IN = "ZOOM_IN"
SERVICE_PTZ = "onvif_ptz"
ONVIF_DATA = "onvif"
ENTITIES = "entities"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
@ -42,36 +59,98 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string,
}) })
SERVICE_PTZ_SCHEMA = vol.Schema({
ATTR_ENTITY_ID: cv.entity_ids,
ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]),
ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]),
ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN])
})
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up a ONVIF camera.""" """Set up a ONVIF camera."""
if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)): if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_HOST)):
return return
async_add_devices([ONVIFCamera(hass, config)])
def handle_ptz(service):
"""Handle PTZ service call."""
pan = service.data.get(ATTR_PAN, None)
tilt = service.data.get(ATTR_TILT, None)
zoom = service.data.get(ATTR_ZOOM, None)
all_cameras = hass.data[ONVIF_DATA][ENTITIES]
entity_ids = extract_entity_ids(hass, service)
target_cameras = []
if not entity_ids:
target_cameras = all_cameras
else:
target_cameras = [camera for camera in all_cameras
if camera.entity_id in entity_ids]
for camera in target_cameras:
camera.perform_ptz(pan, tilt, zoom)
hass.services.async_register(DOMAIN, SERVICE_PTZ, handle_ptz,
schema=SERVICE_PTZ_SCHEMA)
async_add_devices([ONVIFHassCamera(hass, config)])
class ONVIFCamera(Camera): class ONVIFHassCamera(Camera):
"""An implementation of an ONVIF camera.""" """An implementation of an ONVIF camera."""
def __init__(self, hass, config): def __init__(self, hass, config):
"""Initialize a ONVIF camera.""" """Initialize a ONVIF camera."""
from onvif import ONVIFService from onvif import ONVIFCamera, exceptions
import onvif
super().__init__() super().__init__()
self._name = config.get(CONF_NAME) self._name = config.get(CONF_NAME)
self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS)
media = ONVIFService( self._input = None
'http://{}:{}/onvif/device_service'.format( camera = None
config.get(CONF_HOST), config.get(CONF_PORT)), try:
config.get(CONF_USERNAME), _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s",
config.get(CONF_PASSWORD), config.get(CONF_HOST), config.get(CONF_PORT))
'{}/wsdl/media.wsdl'.format(os.path.dirname(onvif.__file__)) camera = ONVIFCamera(
) config.get(CONF_HOST), config.get(CONF_PORT),
self._input = media.GetStreamUri().Uri config.get(CONF_USERNAME), config.get(CONF_PASSWORD)
_LOGGER.debug("ONVIF Camera Using the following URL for %s: %s", )
self._name, self._input) media_service = camera.create_media_service()
stream_uri = media_service.GetStreamUri(
{'StreamSetup': {'Stream': 'RTP-Unicast', 'Transport': 'RTSP'}}
)
self._input = stream_uri.Uri.replace(
'rtsp://', 'rtsp://{}:{}@'.format(
config.get(CONF_USERNAME),
config.get(CONF_PASSWORD)), 1)
_LOGGER.debug(
"ONVIF Camera Using the following URL for %s: %s",
self._name, self._input)
except Exception as err:
_LOGGER.error("Unable to communicate with ONVIF Camera: %s", err)
raise
try:
self._ptz = camera.create_ptz_service()
except exceptions.ONVIFError as err:
self._ptz = None
_LOGGER.warning("Unable to setup PTZ for ONVIF Camera: %s", err)
def perform_ptz(self, pan, tilt, zoom):
"""Perform a PTZ action on the camera."""
if self._ptz:
pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0
tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0
zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0
req = {"Velocity": {
"PanTilt": {"_x": pan_val, "_y": tilt_val},
"Zoom": {"_x": zoom_val}}}
self._ptz.ContinuousMove(req)
@asyncio.coroutine
def async_added_to_hass(self):
"""Callback when entity is added to hass."""
if ONVIF_DATA not in self.hass.data:
self.hass.data[ONVIF_DATA] = {}
self.hass.data[ONVIF_DATA][ENTITIES] = []
self.hass.data[ONVIF_DATA][ENTITIES].append(self)
@asyncio.coroutine @asyncio.coroutine
def async_camera_image(self): def async_camera_image(self):

View File

@ -8,6 +8,7 @@ import os
import subprocess import subprocess
import logging import logging
import shutil import shutil
from tempfile import NamedTemporaryFile
import voluptuous as vol import voluptuous as vol
@ -36,7 +37,7 @@ DEFAULT_TIMELAPSE = 1000
DEFAULT_VERTICAL_FLIP = 0 DEFAULT_VERTICAL_FLIP = 0
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FILE_PATH): cv.string, vol.Optional(CONF_FILE_PATH): cv.isfile,
vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP): vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP):
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT): vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT):
@ -77,25 +78,32 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
CONF_TIMELAPSE: config.get(CONF_TIMELAPSE), CONF_TIMELAPSE: config.get(CONF_TIMELAPSE),
CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP), CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP),
CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP), CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP),
CONF_FILE_PATH: config.get(CONF_FILE_PATH, CONF_FILE_PATH: config.get(CONF_FILE_PATH)
os.path.join(os.path.dirname(__file__),
'image.jpg'))
} }
) )
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill) hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill)
try: file_path = setup_config[CONF_FILE_PATH]
# Try to create an empty file (or open existing) to ensure we have
# proper permissions.
open(setup_config[CONF_FILE_PATH], 'a').close()
add_devices([RaspberryCamera(setup_config)]) def delete_temp_file(*args):
except PermissionError: """Delete the temporary file to prevent saving multiple temp images.
_LOGGER.error("File path is not writable")
return False Only used when no path is defined
except FileNotFoundError: """
_LOGGER.error("Could not create output file (missing directory?)") os.remove(file_path)
# If no file path is defined, use a temporary file
if file_path is None:
temp_file = NamedTemporaryFile(suffix='.jpg', delete=False)
temp_file.close()
file_path = temp_file.name
setup_config[CONF_FILE_PATH] = file_path
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, delete_temp_file)
# Check whether the file path has been whitelisted
elif not hass.config.is_allowed_path(file_path):
_LOGGER.error("'%s' is not a whitelisted directory", file_path)
return False return False

View File

@ -23,3 +23,20 @@ snapshot:
filename: filename:
description: Template of a Filename. Variable is entity_id. description: Template of a Filename. Variable is entity_id.
example: '/tmp/snapshot_{{ entity_id }}' example: '/tmp/snapshot_{{ entity_id }}'
onvif_ptz:
description: Pan/Tilt/Zoom service for ONVIF camera.
fields:
entity_id:
description: Name(s) of entities to pan, tilt or zoom.
example: 'camera.living_room_camera'
pan:
description: "Direction of pan. Allowed values: LEFT, RIGHT."
example: 'LEFT'
tilt:
description: "Direction of tilt. Allowed values: DOWN, UP."
example: 'DOWN'
zoom:
description: "Zoom. Allowed values: ZOOM_IN, ZOOM_OUT"
example: "ZOOM_IN"

View File

@ -188,7 +188,7 @@ class UnifiVideoCamera(Camera):
self._nvr.set_recordmode(self._uuid, set_mode) self._nvr.set_recordmode(self._uuid, set_mode)
self._motion_status = mode self._motion_status = mode
except NvrError as err: except NvrError as err:
_LOGGER.error("Unable to set recordmode to " + set_mode) _LOGGER.error("Unable to set recordmode to %s", set_mode)
_LOGGER.debug(err) _LOGGER.debug(err)
def enable_motion_detection(self): def enable_motion_detection(self):

View File

@ -33,7 +33,7 @@ CAMERAS_SCHEMA = vol.Schema({
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_CAMERAS, default={}): vol.Optional(CONF_CAMERAS):
vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])), vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])),
vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean, vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean,
vol.Optional(CONF_PASSWORD): cv.string, vol.Optional(CONF_PASSWORD): cv.string,
@ -42,7 +42,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
# pylint: disable=unused-argument
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Discover and setup Xeoma Cameras.""" """Discover and setup Xeoma Cameras."""
from pyxeoma.xeoma import Xeoma, XeomaError from pyxeoma.xeoma import Xeoma, XeomaError
@ -68,7 +67,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for image_name, username, pw in discovered_image_names for image_name, username, pw in discovered_image_names
] ]
for cam in config[CONF_CAMERAS]: for cam in config.get(CONF_CAMERAS, []):
# https://github.com/PyCQA/pylint/issues/1830
# pylint: disable=stop-iteration-return
camera = next( camera = next(
(dc for dc in discovered_cameras (dc for dc in discovered_cameras
if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None) if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None)

View File

@ -669,16 +669,16 @@ class ClimateDevice(Entity):
""" """
return self.hass.async_add_job(self.set_humidity, humidity) return self.hass.async_add_job(self.set_humidity, humidity)
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set new target fan mode.""" """Set new target fan mode."""
raise NotImplementedError() raise NotImplementedError()
def async_set_fan_mode(self, fan): def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode. """Set new target fan mode.
This method must be run in the event loop and returns a coroutine. This method must be run in the event loop and returns a coroutine.
""" """
return self.hass.async_add_job(self.set_fan_mode, fan) return self.hass.async_add_job(self.set_fan_mode, fan_mode)
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set new target operation mode.""" """Set new target operation mode."""

View File

@ -28,7 +28,7 @@ _LOGGER = logging.getLogger(__name__)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_HOST): cv.string, vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_NAME, default=None): cv.string, vol.Optional(CONF_NAME): cv.string,
}) })
HA_STATE_TO_DAIKIN = { HA_STATE_TO_DAIKIN = {
@ -236,9 +236,9 @@ class DaikinClimate(ClimateDevice):
"""Return the fan setting.""" """Return the fan setting."""
return self.get(ATTR_FAN_MODE) return self.get(ATTR_FAN_MODE)
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set fan mode.""" """Set fan mode."""
self.set({ATTR_FAN_MODE: fan}) self.set({ATTR_FAN_MODE: fan_mode})
@property @property
def fan_list(self): def fan_list(self):

View File

@ -195,9 +195,9 @@ class DemoClimate(ClimateDevice):
self._current_swing_mode = swing_mode self._current_swing_mode = swing_mode
self.schedule_update_ha_state() self.schedule_update_ha_state()
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set new target temperature.""" """Set new target temperature."""
self._current_fan_mode = fan self._current_fan_mode = fan_mode
self.schedule_update_ha_state() self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
@ -225,9 +225,9 @@ class DemoClimate(ClimateDevice):
self._away = False self._away = False
self.schedule_update_ha_state() self.schedule_update_ha_state()
def set_hold_mode(self, hold): def set_hold_mode(self, hold_mode):
"""Update hold mode on.""" """Update hold_mode on."""
self._hold = hold self._hold = hold_mode
self.schedule_update_ha_state() self.schedule_update_ha_state()
def turn_aux_heat_on(self): def turn_aux_heat_on(self):

View File

@ -98,8 +98,7 @@ class EphEmberThermostat(ClimateDevice):
"""Return current operation ie. heat, cool, idle.""" """Return current operation ie. heat, cool, idle."""
if self._zone['isCurrentlyActive']: if self._zone['isCurrentlyActive']:
return STATE_HEAT return STATE_HEAT
else: return STATE_IDLE
return STATE_IDLE
@property @property
def is_aux_heat_on(self): def is_aux_heat_on(self):

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES) CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES)
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['python-eq3bt==0.1.8'] REQUIREMENTS = ['python-eq3bt==0.1.9']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -53,7 +53,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(devices) add_devices(devices)
# pylint: disable=import-error # pylint: disable=import-error, no-name-in-module
class EQ3BTSmartThermostat(ClimateDevice): class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of an eQ-3 Bluetooth Smart thermostat.""" """Representation of an eQ-3 Bluetooth Smart thermostat."""
@ -75,6 +75,8 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._name = _name self._name = _name
self._thermostat = eq3.Thermostat(_mac) self._thermostat = eq3.Thermostat(_mac)
self._target_temperature = None
self._target_mode = None
@property @property
def supported_features(self): def supported_features(self):
@ -116,6 +118,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
temperature = kwargs.get(ATTR_TEMPERATURE) temperature = kwargs.get(ATTR_TEMPERATURE)
if temperature is None: if temperature is None:
return return
self._target_temperature = temperature
self._thermostat.target_temperature = temperature self._thermostat.target_temperature = temperature
@property @property
@ -132,6 +135,7 @@ class EQ3BTSmartThermostat(ClimateDevice):
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
self._target_mode = operation_mode
self._thermostat.mode = self.reverse_modes[operation_mode] self._thermostat.mode = self.reverse_modes[operation_mode]
def turn_away_mode_off(self): def turn_away_mode_off(self):
@ -177,3 +181,15 @@ class EQ3BTSmartThermostat(ClimateDevice):
self._thermostat.update() self._thermostat.update()
except BTLEException as ex: except BTLEException as ex:
_LOGGER.warning("Updating the state failed: %s", ex) _LOGGER.warning("Updating the state failed: %s", ex)
if (self._target_temperature and
self._thermostat.target_temperature
!= self._target_temperature):
self.set_temperature(temperature=self._target_temperature)
else:
self._target_temperature = None
if (self._target_mode and
self.modes[self._thermostat.mode] != self._target_mode):
self.set_operation_mode(operation_mode=self._target_mode)
else:
self._target_mode = None

View File

@ -152,6 +152,6 @@ class Flexit(ClimateDevice):
self._target_temperature = kwargs.get(ATTR_TEMPERATURE) self._target_temperature = kwargs.get(ATTR_TEMPERATURE)
self.unit.set_temp(self._target_temperature) self.unit.set_temp(self._target_temperature)
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set new fan mode.""" """Set new fan mode."""
self.unit.set_fan_speed(self._fan_list.index(fan)) self.unit.set_fan_speed(self._fan_list.index(fan_mode))

View File

@ -190,11 +190,9 @@ class GenericThermostat(ClimateDevice):
"""Return the current state.""" """Return the current state."""
if self._is_device_active: if self._is_device_active:
return self.current_operation return self.current_operation
else: if self._enabled:
if self._enabled: return STATE_IDLE
return STATE_IDLE return STATE_OFF
else:
return STATE_OFF
@property @property
def should_poll(self): def should_poll(self):

View File

@ -48,9 +48,9 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
default=DEFAULT_SETPOINT_SHIFT_STEP): vol.All( default=DEFAULT_SETPOINT_SHIFT_STEP): vol.All(
float, vol.Range(min=0, max=2)), float, vol.Range(min=0, max=2)),
vol.Optional(CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX): vol.Optional(CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX):
vol.All(int, vol.Range(min=-32, max=0)),
vol.Optional(CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN):
vol.All(int, vol.Range(min=0, max=32)), vol.All(int, vol.Range(min=0, max=32)),
vol.Optional(CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN):
vol.All(int, vol.Range(min=-32, max=0)),
vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string, vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string,
vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string, vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string,
vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string, vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string,
@ -64,9 +64,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up climate(s) for KNX platform.""" """Set up climate(s) for KNX platform."""
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None: if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices) async_add_devices_discovery(hass, discovery_info, async_add_devices)
else: else:

View File

@ -26,7 +26,7 @@ SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE) SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
OP_MODES = [ OP_MODES = [
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
] ]
FAN_MODES = [ FAN_MODES = [
@ -42,8 +42,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
all_devices = [] all_devices = []
for device in devices: for device in devices:
all_devices.append(MelissaClimate( if device['type'] == 'melissa':
api, device['serial_number'], device)) all_devices.append(MelissaClimate(
api, device['serial_number'], device))
add_devices(all_devices) add_devices(all_devices)
@ -146,10 +147,10 @@ class MelissaClimate(ClimateDevice):
temp = kwargs.get(ATTR_TEMPERATURE) temp = kwargs.get(ATTR_TEMPERATURE)
self.send({self._api.TEMP: temp}) self.send({self._api.TEMP: temp})
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set fan mode.""" """Set fan mode."""
fan_mode = self.hass_fan_to_melissa(fan) melissa_fan_mode = self.hass_fan_to_melissa(fan_mode)
self.send({self._api.FAN: fan_mode}) self.send({self._api.FAN: melissa_fan_mode})
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set operation mode.""" """Set operation mode."""
@ -174,8 +175,7 @@ class MelissaClimate(ClimateDevice):
if not self._api.send(self._serial_number, self._cur_settings): if not self._api.send(self._serial_number, self._cur_settings):
self._cur_settings = old_value self._cur_settings = old_value
return False return False
else: return True
return True
def update(self): def update(self):
"""Get latest data from Melissa.""" """Get latest data from Melissa."""
@ -196,14 +196,11 @@ class MelissaClimate(ClimateDevice):
return STATE_OFF return STATE_OFF
elif state == self._api.STATE_IDLE: elif state == self._api.STATE_IDLE:
return STATE_IDLE return STATE_IDLE
else: return None
return None
def melissa_op_to_hass(self, mode): def melissa_op_to_hass(self, mode):
"""Translate Melissa modes to hass states.""" """Translate Melissa modes to hass states."""
if mode == self._api.MODE_AUTO: if mode == self._api.MODE_HEAT:
return STATE_AUTO
elif mode == self._api.MODE_HEAT:
return STATE_HEAT return STATE_HEAT
elif mode == self._api.MODE_COOL: elif mode == self._api.MODE_COOL:
return STATE_COOL return STATE_COOL
@ -211,10 +208,9 @@ class MelissaClimate(ClimateDevice):
return STATE_DRY return STATE_DRY
elif mode == self._api.MODE_FAN: elif mode == self._api.MODE_FAN:
return STATE_FAN_ONLY return STATE_FAN_ONLY
else: _LOGGER.warning(
_LOGGER.warning( "Operation mode %s could not be mapped to hass", mode)
"Operation mode %s could not be mapped to hass", mode) return None
return None
def melissa_fan_to_hass(self, fan): def melissa_fan_to_hass(self, fan):
"""Translate Melissa fan modes to hass modes.""" """Translate Melissa fan modes to hass modes."""
@ -226,15 +222,12 @@ class MelissaClimate(ClimateDevice):
return SPEED_MEDIUM return SPEED_MEDIUM
elif fan == self._api.FAN_HIGH: elif fan == self._api.FAN_HIGH:
return SPEED_HIGH return SPEED_HIGH
else: _LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
_LOGGER.warning("Fan mode %s could not be mapped to hass", fan) return None
return None
def hass_mode_to_melissa(self, mode): def hass_mode_to_melissa(self, mode):
"""Translate hass states to melissa modes.""" """Translate hass states to melissa modes."""
if mode == STATE_AUTO: if mode == STATE_HEAT:
return self._api.MODE_AUTO
elif mode == STATE_HEAT:
return self._api.MODE_HEAT return self._api.MODE_HEAT
elif mode == STATE_COOL: elif mode == STATE_COOL:
return self._api.MODE_COOL return self._api.MODE_COOL

View File

@ -482,15 +482,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@asyncio.coroutine @asyncio.coroutine
def async_set_fan_mode(self, fan): def async_set_fan_mode(self, fan_mode):
"""Set new target temperature.""" """Set new target temperature."""
if self._send_if_off or self._current_operation != STATE_OFF: if self._send_if_off or self._current_operation != STATE_OFF:
mqtt.async_publish( mqtt.async_publish(
self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC], self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC],
fan, self._qos, self._retain) fan_mode, self._qos, self._retain)
if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None: if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None:
self._current_fan_mode = fan self._current_fan_mode = fan_mode
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@asyncio.coroutine @asyncio.coroutine
@ -552,15 +552,15 @@ class MqttClimate(MqttAvailability, ClimateDevice):
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@asyncio.coroutine @asyncio.coroutine
def async_set_hold_mode(self, hold): def async_set_hold_mode(self, hold_mode):
"""Update hold mode on.""" """Update hold mode on."""
if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None: if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, mqtt.async_publish(self.hass,
self._topic[CONF_HOLD_COMMAND_TOPIC], self._topic[CONF_HOLD_COMMAND_TOPIC],
hold, self._qos, self._retain) hold_mode, self._qos, self._retain)
if self._topic[CONF_HOLD_STATE_TOPIC] is None: if self._topic[CONF_HOLD_STATE_TOPIC] is None:
self._hold = hold self._hold = hold_mode
self.async_schedule_update_ha_state() self.async_schedule_update_ha_state()
@asyncio.coroutine @asyncio.coroutine

View File

@ -143,14 +143,14 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self._values[value_type] = value self._values[value_type] = value
self.schedule_update_ha_state() self.schedule_update_ha_state()
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set new target temperature.""" """Set new target temperature."""
set_req = self.gateway.const.SetReq set_req = self.gateway.const.SetReq
self.gateway.set_child_value( self.gateway.set_child_value(
self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan) self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan_mode)
if self.gateway.optimistic: if self.gateway.optimistic:
# Optimistically assume that device has changed state # Optimistically assume that device has changed state
self._values[set_req.V_HVAC_SPEED] = fan self._values[set_req.V_HVAC_SPEED] = fan_mode
self.schedule_update_ha_state() self.schedule_update_ha_state()
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):

View File

@ -207,9 +207,9 @@ class NestThermostat(ClimateDevice):
"""List of available fan modes.""" """List of available fan modes."""
return self._fan_list return self._fan_list
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Turn fan on/off.""" """Turn fan on/off."""
self.device.fan = fan.lower() self.device.fan = fan_mode.lower()
@property @property
def min_temp(self): def min_temp(self):
@ -225,7 +225,7 @@ class NestThermostat(ClimateDevice):
"""Cache value from Python-nest.""" """Cache value from Python-nest."""
self._location = self.device.where self._location = self.device.where
self._name = self.device.name self._name = self.device.name
self._humidity = self.device.humidity, self._humidity = self.device.humidity
self._temperature = self.device.temperature self._temperature = self.device.temperature
self._mode = self.device.mode self._mode = self.device.mode
self._target_temperature = self.device.target self._target_temperature = self.device.target

View File

@ -185,7 +185,7 @@ class NuHeatThermostat(ClimateDevice):
self._thermostat.resume_schedule() self._thermostat.resume_schedule()
self._force_update = True self._force_update = True
def set_hold_mode(self, hold_mode, **kwargs): def set_hold_mode(self, hold_mode):
"""Update the hold mode of the thermostat.""" """Update the hold mode of the thermostat."""
if hold_mode == MODE_AUTO: if hold_mode == MODE_AUTO:
schedule_mode = SCHEDULE_RUN schedule_mode = SCHEDULE_RUN

View File

@ -183,17 +183,16 @@ class RadioThermostat(ClimateDevice):
"""List of available fan modes.""" """List of available fan modes."""
if self._is_model_ct80: if self._is_model_ct80:
return CT80_FAN_OPERATION_LIST return CT80_FAN_OPERATION_LIST
else: return CT30_FAN_OPERATION_LIST
return CT30_FAN_OPERATION_LIST
@property @property
def current_fan_mode(self): def current_fan_mode(self):
"""Return whether the fan is on.""" """Return whether the fan is on."""
return self._fmode return self._fmode
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Turn fan on/off.""" """Turn fan on/off."""
code = FAN_MODE_TO_CODE.get(fan, None) code = FAN_MODE_TO_CODE.get(fan_mode, None)
if code is not None: if code is not None:
self.device.fmode = code self.device.fmode = code

View File

@ -240,13 +240,13 @@ class SensiboClimate(ClimateDevice):
def min_temp(self): def min_temp(self):
"""Return the minimum temperature.""" """Return the minimum temperature."""
return self._temperatures_list[0] \ return self._temperatures_list[0] \
if len(self._temperatures_list) else super().min_temp() if self._temperatures_list else super().min_temp()
@property @property
def max_temp(self): def max_temp(self):
"""Return the maximum temperature.""" """Return the maximum temperature."""
return self._temperatures_list[-1] \ return self._temperatures_list[-1] \
if len(self._temperatures_list) else super().max_temp() if self._temperatures_list else super().max_temp()
@asyncio.coroutine @asyncio.coroutine
def async_set_temperature(self, **kwargs): def async_set_temperature(self, **kwargs):
@ -273,11 +273,11 @@ class SensiboClimate(ClimateDevice):
self._id, 'targetTemperature', temperature, self._ac_states) self._id, 'targetTemperature', temperature, self._ac_states)
@asyncio.coroutine @asyncio.coroutine
def async_set_fan_mode(self, fan): def async_set_fan_mode(self, fan_mode):
"""Set new target fan mode.""" """Set new target fan mode."""
with async_timeout.timeout(TIMEOUT): with async_timeout.timeout(TIMEOUT):
yield from self._client.async_set_ac_state_property( yield from self._client.async_set_ac_state_property(
self._id, 'fanLevel', fan, self._ac_states) self._id, 'fanLevel', fan_mode, self._ac_states)
@asyncio.coroutine @asyncio.coroutine
def async_set_operation_mode(self, operation_mode): def async_set_operation_mode(self, operation_mode):

View File

@ -213,6 +213,7 @@ class TadoClimate(ClimateDevice):
self._target_temp = temperature self._target_temp = temperature
self._control_heating() self._control_heating()
# pylint: disable=arguments-differ
def set_operation_mode(self, readable_operation_mode): def set_operation_mode(self, readable_operation_mode):
"""Set new operation mode.""" """Set new operation mode."""
operation_mode = CONST_MODE_SMART_SCHEDULE operation_mode = CONST_MODE_SMART_SCHEDULE

View File

@ -51,8 +51,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice):
mode = self.tesla_device.is_hvac_enabled() mode = self.tesla_device.is_hvac_enabled()
if mode: if mode:
return OPERATION_LIST[0] # On return OPERATION_LIST[0] # On
else: return OPERATION_LIST[1] # Off
return OPERATION_LIST[1] # Off
@property @property
def operation_list(self): def operation_list(self):

View File

@ -111,8 +111,7 @@ class VenstarThermostat(ClimateDevice):
"""Return the unit of measurement, as defined by the API.""" """Return the unit of measurement, as defined by the API."""
if self._client.tempunits == self._client.TEMPUNITS_F: if self._client.tempunits == self._client.TEMPUNITS_F:
return TEMP_FAHRENHEIT return TEMP_FAHRENHEIT
else: return TEMP_CELSIUS
return TEMP_CELSIUS
@property @property
def fan_list(self): def fan_list(self):
@ -143,16 +142,14 @@ class VenstarThermostat(ClimateDevice):
return STATE_COOL return STATE_COOL
elif self._client.mode == self._client.MODE_AUTO: elif self._client.mode == self._client.MODE_AUTO:
return STATE_AUTO return STATE_AUTO
else: return STATE_OFF
return STATE_OFF
@property @property
def current_fan_mode(self): def current_fan_mode(self):
"""Return the fan setting.""" """Return the fan setting."""
if self._client.fan == self._client.FAN_AUTO: if self._client.fan == self._client.FAN_AUTO:
return STATE_AUTO return STATE_AUTO
else: return STATE_ON
return STATE_ON
@property @property
def device_state_attributes(self): def device_state_attributes(self):
@ -169,24 +166,21 @@ class VenstarThermostat(ClimateDevice):
return self._client.heattemp return self._client.heattemp
elif self._client.mode == self._client.MODE_COOL: elif self._client.mode == self._client.MODE_COOL:
return self._client.cooltemp return self._client.cooltemp
else: return None
return None
@property @property
def target_temperature_low(self): def target_temperature_low(self):
"""Return the lower bound temp if auto mode is on.""" """Return the lower bound temp if auto mode is on."""
if self._client.mode == self._client.MODE_AUTO: if self._client.mode == self._client.MODE_AUTO:
return self._client.heattemp return self._client.heattemp
else: return None
return None
@property @property
def target_temperature_high(self): def target_temperature_high(self):
"""Return the upper bound temp if auto mode is on.""" """Return the upper bound temp if auto mode is on."""
if self._client.mode == self._client.MODE_AUTO: if self._client.mode == self._client.MODE_AUTO:
return self._client.cooltemp return self._client.cooltemp
else: return None
return None
@property @property
def target_humidity(self): def target_humidity(self):
@ -245,9 +239,9 @@ class VenstarThermostat(ClimateDevice):
if not success: if not success:
_LOGGER.error("Failed to change the temperature") _LOGGER.error("Failed to change the temperature")
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set new target fan mode.""" """Set new target fan mode."""
if fan == STATE_ON: if fan_mode == STATE_ON:
success = self._client.set_fan(self._client.FAN_ON) success = self._client.set_fan(self._client.FAN_ON)
else: else:
success = self._client.set_fan(self._client.FAN_AUTO) success = self._client.set_fan(self._client.FAN_AUTO)

View File

@ -85,13 +85,13 @@ class VeraThermostat(VeraDevice, ClimateDevice):
"""Return a list of available fan modes.""" """Return a list of available fan modes."""
return FAN_OPERATION_LIST return FAN_OPERATION_LIST
def set_fan_mode(self, mode): def set_fan_mode(self, fan_mode):
"""Set new target temperature.""" """Set new target temperature."""
if mode == FAN_OPERATION_LIST[0]: if fan_mode == FAN_OPERATION_LIST[0]:
self.vera_device.fan_on() self.vera_device.fan_on()
elif mode == FAN_OPERATION_LIST[1]: elif fan_mode == FAN_OPERATION_LIST[1]:
self.vera_device.fan_auto() self.vera_device.fan_auto()
elif mode == FAN_OPERATION_LIST[2]: elif fan_mode == FAN_OPERATION_LIST[2]:
return self.vera_device.fan_cycle() return self.vera_device.fan_cycle()
@property @property

View File

@ -324,9 +324,9 @@ class WinkThermostat(WinkDevice, ClimateDevice):
return self.wink.fan_modes() return self.wink.fan_modes()
return None return None
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Turn fan on/off.""" """Turn fan on/off."""
self.wink.set_fan_mode(fan.lower()) self.wink.set_fan_mode(fan_mode.lower())
def turn_aux_heat_on(self): def turn_aux_heat_on(self):
"""Turn auxiliary heater on.""" """Turn auxiliary heater on."""
@ -486,26 +486,25 @@ class WinkAC(WinkDevice, ClimateDevice):
return SPEED_LOW return SPEED_LOW
elif speed <= 0.66: elif speed <= 0.66:
return SPEED_MEDIUM return SPEED_MEDIUM
else: return SPEED_HIGH
return SPEED_HIGH
@property @property
def fan_list(self): def fan_list(self):
"""Return a list of available fan modes.""" """Return a list of available fan modes."""
return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH] return [SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH]
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
""" """
Set fan speed. Set fan speed.
The official Wink app only supports 3 modes [low, medium, high] The official Wink app only supports 3 modes [low, medium, high]
which are equal to [0.33, 0.66, 1.0] respectively. which are equal to [0.33, 0.66, 1.0] respectively.
""" """
if fan == SPEED_LOW: if fan_mode == SPEED_LOW:
speed = 0.33 speed = 0.33
elif fan == SPEED_MEDIUM: elif fan_mode == SPEED_MEDIUM:
speed = 0.66 speed = 0.66
elif fan == SPEED_HIGH: elif fan_mode == SPEED_HIGH:
speed = 1.0 speed = 1.0
self.wink.set_ac_fan_speed(speed) self.wink.set_ac_fan_speed(speed)

View File

@ -198,10 +198,10 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice):
self.values.primary.data = temperature self.values.primary.data = temperature
def set_fan_mode(self, fan): def set_fan_mode(self, fan_mode):
"""Set new target fan mode.""" """Set new target fan mode."""
if self.values.fan_mode: if self.values.fan_mode:
self.values.fan_mode.data = fan self.values.fan_mode.data = fan_mode
def set_operation_mode(self, operation_mode): def set_operation_mode(self, operation_mode):
"""Set new target operation mode.""" """Set new target operation mode."""

View File

@ -56,10 +56,7 @@ GOOGLE_ENTITY_SCHEMA = vol.Schema({
}) })
ASSISTANT_SCHEMA = vol.Schema({ ASSISTANT_SCHEMA = vol.Schema({
vol.Optional( vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA,
CONF_FILTER,
default=lambda: entityfilter.generate_filter([], [], [], [])
): entityfilter.FILTER_SCHEMA,
}) })
ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({ ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({
@ -222,7 +219,7 @@ class Cloud:
# Fetching keyset can fail if internet is not up yet. # Fetching keyset can fail if internet is not up yet.
if not success: if not success:
self.hass.helpers.async_call_later(5, self.async_start) self.hass.helpers.event.async_call_later(5, self.async_start)
return return
def load_config(): def load_config():

View File

@ -1,7 +1,4 @@
"""Package to communicate with the authentication API.""" """Package to communicate with the authentication API."""
import logging
_LOGGER = logging.getLogger(__name__)
class CloudError(Exception): class CloudError(Exception):
@ -31,6 +28,8 @@ class InvalidCode(CloudError):
class PasswordChangeRequired(CloudError): class PasswordChangeRequired(CloudError):
"""Raised when a password change is required.""" """Raised when a password change is required."""
# https://github.com/PyCQA/pylint/issues/1085
# pylint: disable=useless-super-delegation
def __init__(self, message='Password change required.'): def __init__(self, message='Password change required.'):
"""Initialize a password change required error.""" """Initialize a password change required error."""
super().__init__(message) super().__init__(message)

View File

@ -6,8 +6,9 @@ import logging
import async_timeout import async_timeout
import voluptuous as vol import voluptuous as vol
from homeassistant.components.http import ( from homeassistant.components.http import HomeAssistantView
HomeAssistantView, RequestDataValidator) from homeassistant.components.http.data_validator import (
RequestDataValidator)
from . import auth_api from . import auth_api
from .const import DOMAIN, REQUEST_TIMEOUT from .const import DOMAIN, REQUEST_TIMEOUT

View File

@ -44,20 +44,13 @@ class CloudIoT:
@asyncio.coroutine @asyncio.coroutine
def connect(self): def connect(self):
"""Connect to the IoT broker.""" """Connect to the IoT broker."""
if self.state != STATE_DISCONNECTED:
raise RuntimeError('Connect called while not disconnected')
hass = self.cloud.hass hass = self.cloud.hass
if self.cloud.subscription_expired: self.close_requested = False
# Try refreshing the token to see if it is still expired. self.state = STATE_CONNECTING
yield from hass.async_add_job(auth_api.check_token, self.cloud) self.tries = 0
if self.cloud.subscription_expired:
hass.components.persistent_notification.async_create(
MESSAGE_EXPIRATION, 'Subscription expired',
'cloud_subscription_expired')
self.state = STATE_DISCONNECTED
return
if self.state == STATE_CONNECTED:
raise RuntimeError('Already connected')
@asyncio.coroutine @asyncio.coroutine
def _handle_hass_stop(event): def _handle_hass_stop(event):
@ -66,17 +59,60 @@ class CloudIoT:
remove_hass_stop_listener = None remove_hass_stop_listener = None
yield from self.disconnect() yield from self.disconnect()
self.state = STATE_CONNECTING
self.close_requested = False
remove_hass_stop_listener = hass.bus.async_listen_once( remove_hass_stop_listener = hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STOP, _handle_hass_stop) EVENT_HOMEASSISTANT_STOP, _handle_hass_stop)
while True:
try:
yield from self._handle_connection()
except Exception: # pylint: disable=broad-except
# Safety net. This should never hit.
# Still adding it here to make sure we can always reconnect
_LOGGER.exception("Unexpected error")
if self.close_requested:
break
self.state = STATE_CONNECTING
self.tries += 1
try:
# Sleep 0, 5, 10, 15 ... 30 seconds between retries
self.retry_task = hass.async_add_job(asyncio.sleep(
min(30, (self.tries - 1) * 5), loop=hass.loop))
yield from self.retry_task
self.retry_task = None
except asyncio.CancelledError:
# Happens if disconnect called
break
self.state = STATE_DISCONNECTED
if remove_hass_stop_listener is not None:
remove_hass_stop_listener()
@asyncio.coroutine
def _handle_connection(self):
"""Connect to the IoT broker."""
hass = self.cloud.hass
try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
except auth_api.CloudError as err:
_LOGGER.warning("Unable to connect: %s", err)
return
if self.cloud.subscription_expired:
hass.components.persistent_notification.async_create(
MESSAGE_EXPIRATION, 'Subscription expired',
'cloud_subscription_expired')
self.close_requested = True
return
session = async_get_clientsession(self.cloud.hass) session = async_get_clientsession(self.cloud.hass)
client = None client = None
disconnect_warn = None disconnect_warn = None
try: try:
yield from hass.async_add_job(auth_api.check_token, self.cloud)
self.client = client = yield from session.ws_connect( self.client = client = yield from session.ws_connect(
self.cloud.relayer, heartbeat=55, headers={ self.cloud.relayer, heartbeat=55, headers={
hdrs.AUTHORIZATION: hdrs.AUTHORIZATION:
@ -90,9 +126,11 @@ class CloudIoT:
while not client.closed: while not client.closed:
msg = yield from client.receive() msg = yield from client.receive()
if msg.type in (WSMsgType.ERROR, WSMsgType.CLOSED, if msg.type in (WSMsgType.CLOSED, WSMsgType.CLOSING):
WSMsgType.CLOSING): break
disconnect_warn = 'Connection cancelled.'
elif msg.type == WSMsgType.ERROR:
disconnect_warn = 'Connection error'
break break
elif msg.type != WSMsgType.TEXT: elif msg.type != WSMsgType.TEXT:
@ -131,9 +169,6 @@ class CloudIoT:
_LOGGER.debug("Publishing message: %s", response) _LOGGER.debug("Publishing message: %s", response)
yield from client.send_json(response) yield from client.send_json(response)
except auth_api.CloudError:
_LOGGER.warning("Unable to connect: Unable to refresh token.")
except client_exceptions.WSServerHandshakeError as err: except client_exceptions.WSServerHandshakeError as err:
if err.code == 401: if err.code == 401:
disconnect_warn = 'Invalid auth.' disconnect_warn = 'Invalid auth.'
@ -145,38 +180,11 @@ class CloudIoT:
except client_exceptions.ClientError as err: except client_exceptions.ClientError as err:
_LOGGER.warning("Unable to connect: %s", err) _LOGGER.warning("Unable to connect: %s", err)
except Exception: # pylint: disable=broad-except
if not self.close_requested:
_LOGGER.exception("Unexpected error")
finally: finally:
if disconnect_warn is not None: if disconnect_warn is None:
_LOGGER.warning("Connection closed: %s", disconnect_warn) _LOGGER.info("Connection closed")
if remove_hass_stop_listener is not None:
remove_hass_stop_listener()
if client is not None:
self.client = None
yield from client.close()
if self.close_requested:
self.state = STATE_DISCONNECTED
else: else:
self.state = STATE_CONNECTING _LOGGER.warning("Connection closed: %s", disconnect_warn)
self.tries += 1
try:
# Sleep 0, 5, 10, 15 ... up to 30 seconds between retries
self.retry_task = hass.async_add_job(asyncio.sleep(
min(30, (self.tries - 1) * 5), loop=hass.loop))
yield from self.retry_task
self.retry_task = None
hass.async_add_job(self.connect())
except asyncio.CancelledError:
# Happens if disconnect called
pass
@asyncio.coroutine @asyncio.coroutine
def disconnect(self): def disconnect(self):

View File

@ -14,15 +14,23 @@ from homeassistant.util.yaml import load_yaml, dump
DOMAIN = 'config' DOMAIN = 'config'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script') SECTIONS = ('core', 'customize', 'group', 'hassbian', 'automation', 'script')
ON_DEMAND = ('zwave') ON_DEMAND = ('zwave',)
FEATURE_FLAGS = ('config_entries',)
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):
"""Set up the config component.""" """Set up the config component."""
global SECTIONS
yield from hass.components.frontend.async_register_built_in_panel( yield from hass.components.frontend.async_register_built_in_panel(
'config', 'config', 'mdi:settings') 'config', 'config', 'mdi:settings')
# Temporary way of allowing people to opt-in for unreleased config sections
for key, value in config.get(DOMAIN, {}).items():
if key in FEATURE_FLAGS and value:
SECTIONS += (key,)
@asyncio.coroutine @asyncio.coroutine
def setup_panel(panel_name): def setup_panel(panel_name):
"""Set up a panel.""" """Set up a panel."""
@ -151,7 +159,7 @@ class EditKeyBasedConfigView(BaseEditConfigView):
def _get_value(self, hass, data, config_key): def _get_value(self, hass, data, config_key):
"""Get value.""" """Get value."""
return data.get(config_key, {}) return data.get(config_key)
def _write_value(self, hass, data, config_key, new_value): def _write_value(self, hass, data, config_key, new_value):
"""Set value.""" """Set value."""

View File

@ -0,0 +1,182 @@
"""Http views to control the config manager."""
import asyncio
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.components.http import HomeAssistantView
from homeassistant.components.http.data_validator import RequestDataValidator
REQUIREMENTS = ['voluptuous-serialize==1']
@asyncio.coroutine
def async_setup(hass):
"""Enable the Home Assistant views."""
hass.http.register_view(ConfigManagerEntryIndexView)
hass.http.register_view(ConfigManagerEntryResourceView)
hass.http.register_view(ConfigManagerFlowIndexView)
hass.http.register_view(ConfigManagerFlowResourceView)
hass.http.register_view(ConfigManagerAvailableFlowView)
return True
def _prepare_json(result):
"""Convert result for JSON."""
if result['type'] != config_entries.RESULT_TYPE_FORM:
return result
import voluptuous_serialize
data = result.copy()
schema = data['data_schema']
if schema is None:
data['data_schema'] = []
else:
data['data_schema'] = voluptuous_serialize.convert(schema)
return data
class ConfigManagerEntryIndexView(HomeAssistantView):
"""View to get available config entries."""
url = '/api/config/config_entries/entry'
name = 'api:config:config_entries:entry'
@asyncio.coroutine
def get(self, request):
"""List flows in progress."""
hass = request.app['hass']
return self.json([{
'entry_id': entry.entry_id,
'domain': entry.domain,
'title': entry.title,
'source': entry.source,
'state': entry.state,
} for entry in hass.config_entries.async_entries()])
class ConfigManagerEntryResourceView(HomeAssistantView):
"""View to interact with a config entry."""
url = '/api/config/config_entries/entry/{entry_id}'
name = 'api:config:config_entries:entry:resource'
@asyncio.coroutine
def delete(self, request, entry_id):
"""Delete a config entry."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.async_remove(entry_id)
except config_entries.UnknownEntry:
return self.json_message('Invalid entry specified', 404)
return self.json(result)
class ConfigManagerFlowIndexView(HomeAssistantView):
"""View to create config flows."""
url = '/api/config/config_entries/flow'
name = 'api:config:config_entries:flow'
@asyncio.coroutine
def get(self, request):
"""List flows that are in progress but not started by a user.
Example of a non-user initiated flow is a discovered Hue hub that
requires user interaction to finish setup.
"""
hass = request.app['hass']
return self.json([
flow for flow in hass.config_entries.flow.async_progress()
if flow['source'] != config_entries.SOURCE_USER])
@asyncio.coroutine
@RequestDataValidator(vol.Schema({
vol.Required('domain'): str,
}))
def post(self, request, data):
"""Handle a POST request."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.flow.async_init(
data['domain'])
except config_entries.UnknownHandler:
return self.json_message('Invalid handler specified', 404)
except config_entries.UnknownStep:
return self.json_message('Handler does not support init', 400)
result = _prepare_json(result)
return self.json(result)
class ConfigManagerFlowResourceView(HomeAssistantView):
"""View to interact with the flow manager."""
url = '/api/config/config_entries/flow/{flow_id}'
name = 'api:config:config_entries:flow:resource'
@asyncio.coroutine
def get(self, request, flow_id):
"""Get the current state of a flow."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.flow.async_configure(
flow_id)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
result = _prepare_json(result)
return self.json(result)
@asyncio.coroutine
@RequestDataValidator(vol.Schema(dict), allow_empty=True)
def post(self, request, flow_id, data):
"""Handle a POST request."""
hass = request.app['hass']
try:
result = yield from hass.config_entries.flow.async_configure(
flow_id, data)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
except vol.Invalid:
return self.json_message('User input malformed', 400)
result = _prepare_json(result)
return self.json(result)
@asyncio.coroutine
def delete(self, request, flow_id):
"""Cancel a flow in progress."""
hass = request.app['hass']
try:
hass.config_entries.async_abort(flow_id)
except config_entries.UnknownFlow:
return self.json_message('Invalid flow specified', 404)
return self.json_message('Flow aborted')
class ConfigManagerAvailableFlowView(HomeAssistantView):
"""View to query available flows."""
url = '/api/config/config_entries/flow_handlers'
name = 'api:config:config_entries:flow_handlers'
@asyncio.coroutine
def get(self, request):
"""List available flow handlers."""
return self.json(config_entries.FLOWS)

View File

@ -0,0 +1,102 @@
"""Example component to show how config entries work."""
import asyncio
import voluptuous as vol
from homeassistant import config_entries
from homeassistant.const import ATTR_FRIENDLY_NAME
from homeassistant.util import slugify
DOMAIN = 'config_entry_example'
@asyncio.coroutine
def async_setup(hass, config):
"""Setup for our example component."""
return True
@asyncio.coroutine
def async_setup_entry(hass, entry):
"""Initialize an entry."""
entity_id = '{}.{}'.format(DOMAIN, entry.data['object_id'])
hass.states.async_set(entity_id, 'loaded', {
ATTR_FRIENDLY_NAME: entry.data['name']
})
# Indicate setup was successful.
return True
@asyncio.coroutine
def async_unload_entry(hass, entry):
"""Unload an entry."""
entity_id = '{}.{}'.format(DOMAIN, entry.data['object_id'])
hass.states.async_remove(entity_id)
# Indicate unload was successful.
return True
@config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(config_entries.ConfigFlowHandler):
"""Handle an example configuration flow."""
VERSION = 1
def __init__(self):
"""Initialize a Hue config handler."""
self.object_id = None
@asyncio.coroutine
def async_step_init(self, user_input=None):
"""Start config flow."""
errors = None
if user_input is not None:
object_id = user_input['object_id']
if object_id != '' and object_id == slugify(object_id):
self.object_id = user_input['object_id']
return (yield from self.async_step_name())
errors = {
'object_id': 'Invalid object id.'
}
return self.async_show_form(
title='Pick object id',
step_id='init',
description="Please enter an object_id for the test entity.",
data_schema=vol.Schema({
'object_id': str
}),
errors=errors
)
@asyncio.coroutine
def async_step_name(self, user_input=None):
"""Ask user to enter the name."""
errors = None
if user_input is not None:
name = user_input['name']
if name != '':
return self.async_create_entry(
title=name,
data={
'name': name,
'object_id': self.object_id,
}
)
return self.async_show_form(
title='Name of the entity',
step_id='name',
description="Please enter a name for the test entity.",
data_schema=vol.Schema({
'name': str
}),
errors=errors
)

View File

@ -7,19 +7,17 @@ https://home-assistant.io/components/conversation/
import asyncio import asyncio
import logging import logging
import re import re
import warnings
import voluptuous as vol import voluptuous as vol
from homeassistant import core from homeassistant import core
from homeassistant.components import http from homeassistant.components import http
from homeassistant.const import ( from homeassistant.components.http.data_validator import (
ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON) RequestDataValidator)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import intent from homeassistant.helpers import intent
from homeassistant.loader import bind_hass
REQUIREMENTS = ['fuzzywuzzy==0.16.0'] from homeassistant.loader import bind_hass
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -28,9 +26,6 @@ ATTR_TEXT = 'text'
DEPENDENCIES = ['http'] DEPENDENCIES = ['http']
DOMAIN = 'conversation' DOMAIN = 'conversation'
INTENT_TURN_OFF = 'HassTurnOff'
INTENT_TURN_ON = 'HassTurnOn'
REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)') REGEX_TURN_COMMAND = re.compile(r'turn (?P<name>(?: |\w)+) (?P<command>\w+)')
REGEX_TYPE = type(re.compile('')) REGEX_TYPE = type(re.compile(''))
@ -50,7 +45,7 @@ CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({
@core.callback @core.callback
@bind_hass @bind_hass
def async_register(hass, intent_type, utterances): def async_register(hass, intent_type, utterances):
"""Register an intent. """Register utterances and any custom intents.
Registrations don't require conversations to be loaded. They will become Registrations don't require conversations to be loaded. They will become
active once the conversation component is loaded. active once the conversation component is loaded.
@ -75,8 +70,6 @@ def async_register(hass, intent_type, utterances):
@asyncio.coroutine @asyncio.coroutine
def async_setup(hass, config): def async_setup(hass, config):
"""Register the process service.""" """Register the process service."""
warnings.filterwarnings('ignore', module='fuzzywuzzy')
config = config.get(DOMAIN, {}) config = config.get(DOMAIN, {})
intents = hass.data.get(DOMAIN) intents = hass.data.get(DOMAIN)
@ -102,12 +95,12 @@ def async_setup(hass, config):
hass.http.register_view(ConversationProcessView) hass.http.register_view(ConversationProcessView)
hass.helpers.intent.async_register(TurnOnIntent()) async_register(hass, intent.INTENT_TURN_ON,
hass.helpers.intent.async_register(TurnOffIntent())
async_register(hass, INTENT_TURN_ON,
['Turn {name} on', 'Turn on {name}']) ['Turn {name} on', 'Turn on {name}'])
async_register(hass, INTENT_TURN_OFF, [ async_register(hass, intent.INTENT_TURN_OFF,
'Turn {name} off', 'Turn off {name}']) ['Turn {name} off', 'Turn off {name}'])
async_register(hass, intent.INTENT_TOGGLE,
['Toggle {name}', '{name} toggle'])
return True return True
@ -151,86 +144,13 @@ def _process(hass, text):
return response return response
@core.callback
def _match_entity(hass, name):
"""Match a name to an entity."""
from fuzzywuzzy import process as fuzzyExtract
entities = {state.entity_id: state.name for state
in hass.states.async_all()}
entity_id = fuzzyExtract.extractOne(
name, entities, score_cutoff=65)[2]
return hass.states.get(entity_id) if entity_id else None
class TurnOnIntent(intent.IntentHandler):
"""Handle turning item on intents."""
intent_type = INTENT_TURN_ON
slot_schema = {
'name': cv.string,
}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle turn on intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name = slots['name']['value']
entity = _match_entity(hass, name)
if not entity:
_LOGGER.error("Could not find entity id for %s", name)
return None
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
}, blocking=True)
response = intent_obj.create_response()
response.async_set_speech(
'Turned on {}'.format(entity.name))
return response
class TurnOffIntent(intent.IntentHandler):
"""Handle turning item off intents."""
intent_type = INTENT_TURN_OFF
slot_schema = {
'name': cv.string,
}
@asyncio.coroutine
def async_handle(self, intent_obj):
"""Handle turn off intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name = slots['name']['value']
entity = _match_entity(hass, name)
if not entity:
_LOGGER.error("Could not find entity id for %s", name)
return None
yield from hass.services.async_call(
core.DOMAIN, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id,
}, blocking=True)
response = intent_obj.create_response()
response.async_set_speech(
'Turned off {}'.format(entity.name))
return response
class ConversationProcessView(http.HomeAssistantView): class ConversationProcessView(http.HomeAssistantView):
"""View to retrieve shopping list content.""" """View to retrieve shopping list content."""
url = '/api/conversation/process' url = '/api/conversation/process'
name = "api:conversation:process" name = "api:conversation:process"
@http.RequestDataValidator(vol.Schema({ @RequestDataValidator(vol.Schema({
vol.Required('text'): str, vol.Required('text'): str,
})) }))
@asyncio.coroutine @asyncio.coroutine

View File

@ -5,7 +5,8 @@ For more details about this platform, please refer to the documentation
https://home-assistant.io/components/demo/ https://home-assistant.io/components/demo/
""" """
from homeassistant.components.cover import ( from homeassistant.components.cover import (
CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE) CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION,
ATTR_TILT_POSITION)
from homeassistant.helpers.event import track_utc_time_change from homeassistant.helpers.event import track_utc_time_change
@ -137,8 +138,9 @@ class DemoCover(CoverDevice):
self._listen_cover_tilt() self._listen_cover_tilt()
self._requested_closing_tilt = False self._requested_closing_tilt = False
def set_cover_position(self, position, **kwargs): def set_cover_position(self, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
self._set_position = round(position, -1) self._set_position = round(position, -1)
if self._position == position: if self._position == position:
return return
@ -146,8 +148,9 @@ class DemoCover(CoverDevice):
self._listen_cover() self._listen_cover()
self._requested_closing = position < self._position self._requested_closing = position < self._position
def set_cover_tilt_position(self, tilt_position, **kwargs): def set_cover_tilt_position(self, **kwargs):
"""Move the cover til to a specific position.""" """Move the cover til to a specific position."""
tilt_position = kwargs.get(ATTR_TILT_POSITION)
self._set_tilt_position = round(tilt_position, -1) self._set_tilt_position = round(tilt_position, -1)
if self._tilt_position == tilt_position: if self._tilt_position == tilt_position:
return return

View File

@ -201,21 +201,21 @@ class GaradgetCover(CoverDevice):
"""Check the state of the service during an operation.""" """Check the state of the service during an operation."""
self.schedule_update_ha_state(True) self.schedule_update_ha_state(True)
def close_cover(self): def close_cover(self, **kwargs):
"""Close the cover.""" """Close the cover."""
if self._state not in ['close', 'closing']: if self._state not in ['close', 'closing']:
ret = self._put_command('setState', 'close') ret = self._put_command('setState', 'close')
self._start_watcher('close') self._start_watcher('close')
return ret.get('return_value') == 1 return ret.get('return_value') == 1
def open_cover(self): def open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""
if self._state not in ['open', 'opening']: if self._state not in ['open', 'opening']:
ret = self._put_command('setState', 'open') ret = self._put_command('setState', 'open')
self._start_watcher('open') self._start_watcher('open')
return ret.get('return_value') == 1 return ret.get('return_value') == 1
def stop_cover(self): def stop_cover(self, **kwargs):
"""Stop the door where it is.""" """Stop the door where it is."""
if self._state not in ['stopped']: if self._state not in ['stopped']:
ret = self._put_command('setState', 'stop') ret = self._put_command('setState', 'stop')

View File

@ -42,10 +42,6 @@ def setup_platform(hass, config: ConfigType,
class ISYCoverDevice(ISYDevice, CoverDevice): class ISYCoverDevice(ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device.""" """Representation of an ISY994 cover device."""
def __init__(self, node: object) -> None:
"""Initialize the ISY994 cover device."""
super().__init__(node)
@property @property
def current_cover_position(self) -> int: def current_cover_position(self) -> int:
"""Return the current cover position.""" """Return the current cover position."""
@ -61,8 +57,7 @@ class ISYCoverDevice(ISYDevice, CoverDevice):
"""Get the state of the ISY994 cover device.""" """Get the state of the ISY994 cover device."""
if self.is_unknown(): if self.is_unknown():
return None return None
else: return VALUE_TO_STATE.get(self.value, STATE_OPEN)
return VALUE_TO_STATE.get(self.value, STATE_OPEN)
def open_cover(self, **kwargs) -> None: def open_cover(self, **kwargs) -> None:
"""Send the open cover command to the ISY994 cover device.""" """Send the open cover command to the ISY994 cover device."""

View File

@ -53,9 +53,6 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine @asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None): def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up cover(s) for KNX platform.""" """Set up cover(s) for KNX platform."""
if DATA_KNX not in hass.data or not hass.data[DATA_KNX].initialized:
return
if discovery_info is not None: if discovery_info is not None:
async_add_devices_discovery(hass, discovery_info, async_add_devices) async_add_devices_discovery(hass, discovery_info, async_add_devices)
else: else:

View File

@ -65,9 +65,9 @@ TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT |
SUPPORT_SET_TILT_POSITION) SUPPORT_SET_TILT_POSITION)
PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_COMMAND_TOPIC, default=None): valid_publish_topic, vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_POSITION_TOPIC, default=None): valid_publish_topic, vol.Optional(CONF_POSITION_TOPIC): valid_publish_topic,
vol.Optional(CONF_SET_POSITION_TEMPLATE, default=None): cv.template, vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template,
vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean,
vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_VALUE_TEMPLATE): cv.template, vol.Optional(CONF_VALUE_TEMPLATE): cv.template,
@ -78,8 +78,8 @@ PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string,
vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string,
vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean,
vol.Optional(CONF_TILT_COMMAND_TOPIC, default=None): valid_publish_topic, vol.Optional(CONF_TILT_COMMAND_TOPIC): valid_publish_topic,
vol.Optional(CONF_TILT_STATUS_TOPIC, default=None): valid_subscribe_topic, vol.Optional(CONF_TILT_STATUS_TOPIC): valid_subscribe_topic,
vol.Optional(CONF_TILT_CLOSED_POSITION, vol.Optional(CONF_TILT_CLOSED_POSITION,
default=DEFAULT_TILT_CLOSED_POSITION): int, default=DEFAULT_TILT_CLOSED_POSITION): int,
vol.Optional(CONF_TILT_OPEN_POSITION, vol.Optional(CONF_TILT_OPEN_POSITION,

View File

@ -84,11 +84,11 @@ class MyQDevice(CoverDevice):
"""Return true if cover is closed, else False.""" """Return true if cover is closed, else False."""
return self._status == STATE_CLOSED return self._status == STATE_CLOSED
def close_cover(self): def close_cover(self, **kwargs):
"""Issue close command to cover.""" """Issue close command to cover."""
self.myq.close_device(self.device_id) self.myq.close_device(self.device_id)
def open_cover(self): def open_cover(self, **kwargs):
"""Issue open command to cover.""" """Issue open command to cover."""
self.myq.open_device(self.device_id) self.myq.open_device(self.device_id)

View File

@ -115,18 +115,18 @@ class OpenGarageCover(CoverDevice):
@property @property
def is_closed(self): def is_closed(self):
"""Return if the cover is closed.""" """Return if the cover is closed."""
if self._state == STATE_UNKNOWN: if self._state in [STATE_UNKNOWN, STATE_OFFLINE]:
return None return None
return self._state in [STATE_CLOSED, STATE_OPENING] return self._state in [STATE_CLOSED, STATE_OPENING]
def close_cover(self): def close_cover(self, **kwargs):
"""Close the cover.""" """Close the cover."""
if self._state not in [STATE_CLOSED, STATE_CLOSING]: if self._state not in [STATE_CLOSED, STATE_CLOSING]:
self._state_before_move = self._state self._state_before_move = self._state
self._state = STATE_CLOSING self._state = STATE_CLOSING
self._push_button() self._push_button()
def open_cover(self): def open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""
if self._state not in [STATE_OPEN, STATE_OPENING]: if self._state not in [STATE_OPEN, STATE_OPENING]:
self._state_before_move = self._state self._state_before_move = self._state

View File

@ -109,12 +109,12 @@ class RPiGPIOCover(CoverDevice):
sleep(self._relay_time) sleep(self._relay_time)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay) rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
def close_cover(self): def close_cover(self, **kwargs):
"""Close the cover.""" """Close the cover."""
if not self.is_closed: if not self.is_closed:
self._trigger() self._trigger()
def open_cover(self): def open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""
if self.is_closed: if self.is_closed:
self._trigger() self._trigger()

View File

@ -1,63 +1,63 @@
# Describes the format for available cover services # Describes the format for available cover services
open_cover: open_cover:
description: Open all or specified cover. description: Open all or specified cover.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to open. description: Name(s) of cover(s) to open.
example: 'cover.living_room' example: 'cover.living_room'
close_cover: close_cover:
description: Close all or specified cover. description: Close all or specified cover.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to close. description: Name(s) of cover(s) to close.
example: 'cover.living_room' example: 'cover.living_room'
set_cover_position: set_cover_position:
description: Move to specific position all or specified cover. description: Move to specific position all or specified cover.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to set cover position. description: Name(s) of cover(s) to set cover position.
example: 'cover.living_room' example: 'cover.living_room'
position: position:
description: Position of the cover (0 to 100). description: Position of the cover (0 to 100).
example: 30 example: 30
stop_cover: stop_cover:
description: Stop all or specified cover. description: Stop all or specified cover.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to stop. description: Name(s) of cover(s) to stop.
example: 'cover.living_room' example: 'cover.living_room'
open_cover_tilt: open_cover_tilt:
description: Open all or specified cover tilt. description: Open all or specified cover tilt.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) tilt to open. description: Name(s) of cover(s) tilt to open.
example: 'cover.living_room' example: 'cover.living_room'
close_cover_tilt: close_cover_tilt:
description: Close all or specified cover tilt. description: Close all or specified cover tilt.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to close tilt. description: Name(s) of cover(s) to close tilt.
example: 'cover.living_room' example: 'cover.living_room'
set_cover_tilt_position: set_cover_tilt_position:
description: Move to specific position all or specified cover tilt. description: Move to specific position all or specified cover tilt.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to set cover tilt position. description: Name(s) of cover(s) to set cover tilt position.
example: 'cover.living_room' example: 'cover.living_room'
tilt_position: tilt_position:
description: Tilt position of the cover (0 to 100). description: Tilt position of the cover (0 to 100).
example: 30 example: 30
stop_cover_tilt: stop_cover_tilt:
description: Stop all or specified cover. description: Stop all or specified cover.
fields: fields:
entity_id: entity_id:
description: Name(s) of cover(s) to stop. description: Name(s) of cover(s) to stop.
example: 'cover.living_room' example: 'cover.living_room'

View File

@ -6,7 +6,7 @@ https://home-assistant.io/components/cover.tahoma/
""" """
import logging import logging
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.tahoma import ( from homeassistant.components.tahoma import (
DOMAIN as TAHOMA_DOMAIN, TahomaDevice) DOMAIN as TAHOMA_DOMAIN, TahomaDevice)
@ -49,9 +49,9 @@ class TahomaCover(TahomaDevice, CoverDevice):
except KeyError: except KeyError:
return None return None
def set_cover_position(self, position, **kwargs): def set_cover_position(self, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
self.apply_action('setPosition', 100 - position) self.apply_action('setPosition', 100 - kwargs.get(ATTR_POSITION))
@property @property
def is_closed(self): def is_closed(self):
@ -64,8 +64,7 @@ class TahomaCover(TahomaDevice, CoverDevice):
"""Return the class of the device.""" """Return the class of the device."""
if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent': if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent':
return 'window' return 'window'
else: return None
return None
def open_cover(self, **kwargs): def open_cover(self, **kwargs):
"""Open the cover.""" """Open the cover."""

View File

@ -6,7 +6,8 @@ https://home-assistant.io/components/cover.vera/
""" """
import logging import logging
from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT, \
ATTR_POSITION
from homeassistant.components.vera import ( from homeassistant.components.vera import (
VERA_CONTROLLER, VERA_DEVICES, VeraDevice) VERA_CONTROLLER, VERA_DEVICES, VeraDevice)
@ -44,9 +45,9 @@ class VeraCover(VeraDevice, CoverDevice):
return 100 return 100
return position return position
def set_cover_position(self, position, **kwargs): def set_cover_position(self, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
self.vera_device.set_level(position) self.vera_device.set_level(kwargs.get(ATTR_POSITION))
self.schedule_update_ha_state() self.schedule_update_ha_state()
@property @property

View File

@ -6,7 +6,8 @@ https://home-assistant.io/components/cover.wink/
""" """
import asyncio import asyncio
from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, \
ATTR_POSITION
from homeassistant.components.wink import WinkDevice, DOMAIN from homeassistant.components.wink import WinkDevice, DOMAIN
DEPENDENCIES = ['wink'] DEPENDENCIES = ['wink']
@ -42,17 +43,17 @@ class WinkCoverDevice(WinkDevice, CoverDevice):
"""Open the cover.""" """Open the cover."""
self.wink.set_state(1) self.wink.set_state(1)
def set_cover_position(self, position, **kwargs): def set_cover_position(self, **kwargs):
"""Move the cover shutter to a specific position.""" """Move the cover shutter to a specific position."""
self.wink.set_state(float(position)/100) position = kwargs.get(ATTR_POSITION)
self.wink.set_state(position/100)
@property @property
def current_cover_position(self): def current_cover_position(self):
"""Return the current position of cover shutter.""" """Return the current position of cover shutter."""
if self.wink.state() is not None: if self.wink.state() is not None:
return int(self.wink.state()*100) return int(self.wink.state()*100)
else: return STATE_UNKNOWN
return STATE_UNKNOWN
@property @property
def is_closed(self): def is_closed(self):

View File

@ -1,7 +1,7 @@
"""Support for Xiaomi curtain.""" """Support for Xiaomi curtain."""
import logging import logging
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice, ATTR_POSITION
from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY,
XiaomiDevice) XiaomiDevice)
@ -55,8 +55,9 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice):
"""Stop the cover.""" """Stop the cover."""
self._write_to_hub(self._sid, **{self._data_key['status']: 'stop'}) self._write_to_hub(self._sid, **{self._data_key['status']: 'stop'})
def set_cover_position(self, position, **kwargs): def set_cover_position(self, **kwargs):
"""Move the cover to a specific position.""" """Move the cover to a specific position."""
position = kwargs.get(ATTR_POSITION)
self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)}) self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)})
def parse_data(self, data, raw_data): def parse_data(self, data, raw_data):

View File

@ -8,7 +8,7 @@ https://home-assistant.io/components/cover.zwave/
# pylint: disable=import-error # pylint: disable=import-error
import logging import logging
from homeassistant.components.cover import ( from homeassistant.components.cover import (
DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE) DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION)
from homeassistant.components.zwave import ZWaveDeviceEntity from homeassistant.components.zwave import ZWaveDeviceEntity
from homeassistant.components import zwave from homeassistant.components import zwave
from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import from homeassistant.components.zwave import async_setup_platform # noqa # pylint: disable=unused-import
@ -97,9 +97,10 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice):
"""Move the roller shutter down.""" """Move the roller shutter down."""
self._network.manager.pressButton(self._close_id) self._network.manager.pressButton(self._close_id)
def set_cover_position(self, position, **kwargs): def set_cover_position(self, **kwargs):
"""Move the roller shutter to a specific position.""" """Move the roller shutter to a specific position."""
self.node.set_dimmer(self.values.primary.value_id, position) self.node.set_dimmer(self.values.primary.value_id,
kwargs.get(ATTR_POSITION))
def stop_cover(self, **kwargs): def stop_cover(self, **kwargs):
"""Stop the roller shutter.""" """Stop the roller shutter."""
@ -139,11 +140,11 @@ class ZwaveGarageDoorSwitch(ZwaveGarageDoorBase):
"""Return the current position of Zwave garage door.""" """Return the current position of Zwave garage door."""
return not self._state return not self._state
def close_cover(self): def close_cover(self, **kwargs):
"""Close the garage door.""" """Close the garage door."""
self.values.primary.data = False self.values.primary.data = False
def open_cover(self): def open_cover(self, **kwargs):
"""Open the garage door.""" """Open the garage door."""
self.values.primary.data = True self.values.primary.data = True
@ -166,10 +167,10 @@ class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase):
"""Return the current position of Zwave garage door.""" """Return the current position of Zwave garage door."""
return self._state == "Closed" return self._state == "Closed"
def close_cover(self): def close_cover(self, **kwargs):
"""Close the garage door.""" """Close the garage door."""
self.values.primary.data = "Closed" self.values.primary.data = "Closed"
def open_cover(self): def open_cover(self, **kwargs):
"""Open the garage door.""" """Open the garage door."""
self.values.primary.data = "Opened" self.values.primary.data = "Opened"

View File

@ -4,6 +4,7 @@ Support for deCONZ devices.
For more details about this component, please refer to the documentation at For more details about this component, please refer to the documentation at
https://home-assistant.io/components/deconz/ https://home-assistant.io/components/deconz/
""" """
import asyncio import asyncio
import logging import logging
@ -17,11 +18,12 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['pydeconz==27'] REQUIREMENTS = ['pydeconz==30']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'deconz' DOMAIN = 'deconz'
DATA_DECONZ_ID = 'deconz_entities'
CONFIG_FILE = 'deconz.conf' CONFIG_FILE = 'deconz.conf'
@ -34,13 +36,16 @@ CONFIG_SCHEMA = vol.Schema({
}, extra=vol.ALLOW_EXTRA) }, extra=vol.ALLOW_EXTRA)
SERVICE_FIELD = 'field' SERVICE_FIELD = 'field'
SERVICE_ENTITY = 'entity'
SERVICE_DATA = 'data' SERVICE_DATA = 'data'
SERVICE_SCHEMA = vol.Schema({ SERVICE_SCHEMA = vol.Schema({
vol.Required(SERVICE_FIELD): cv.string, vol.Exclusive(SERVICE_FIELD, 'deconz_id'): cv.string,
vol.Exclusive(SERVICE_ENTITY, 'deconz_id'): cv.entity_id,
vol.Required(SERVICE_DATA): dict, vol.Required(SERVICE_DATA): dict,
}) })
CONFIG_INSTRUCTIONS = """ CONFIG_INSTRUCTIONS = """
Unlock your deCONZ gateway to register with Home Assistant. Unlock your deCONZ gateway to register with Home Assistant.
@ -100,6 +105,7 @@ def async_setup_deconz(hass, config, deconz_config):
return False return False
hass.data[DOMAIN] = deconz hass.data[DOMAIN] = deconz
hass.data[DATA_DECONZ_ID] = {}
for component in ['binary_sensor', 'light', 'scene', 'sensor']: for component in ['binary_sensor', 'light', 'scene', 'sensor']:
hass.async_add_job(discovery.async_load_platform( hass.async_add_job(discovery.async_load_platform(
@ -112,6 +118,7 @@ def async_setup_deconz(hass, config, deconz_config):
Field is a string representing a specific device in deCONZ Field is a string representing a specific device in deCONZ
e.g. field='/lights/1/state'. e.g. field='/lights/1/state'.
Entity_id can be used to retrieve the proper field.
Data is a json object with what data you want to alter Data is a json object with what data you want to alter
e.g. data={'on': true}. e.g. data={'on': true}.
{ {
@ -121,9 +128,17 @@ def async_setup_deconz(hass, config, deconz_config):
See Dresden Elektroniks REST API documentation for details: See Dresden Elektroniks REST API documentation for details:
http://dresden-elektronik.github.io/deconz-rest-doc/rest/ http://dresden-elektronik.github.io/deconz-rest-doc/rest/
""" """
deconz = hass.data[DOMAIN]
field = call.data.get(SERVICE_FIELD) field = call.data.get(SERVICE_FIELD)
entity_id = call.data.get(SERVICE_ENTITY)
data = call.data.get(SERVICE_DATA) data = call.data.get(SERVICE_DATA)
deconz = hass.data[DOMAIN]
if entity_id:
entities = hass.data.get(DATA_DECONZ_ID)
if entities:
field = entities.get(entity_id)
if field is None:
_LOGGER.error('Could not find the entity %s', entity_id)
return
yield from deconz.async_put_state(field, data) yield from deconz.async_put_state(field, data)
hass.services.async_register( hass.services.async_register(
DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA) DOMAIN, 'configure', async_configure, schema=SERVICE_SCHEMA)

View File

@ -1,10 +1,13 @@
configure: configure:
description: Set attribute of device in Deconz. See Dresden Elektroniks REST API documentation for details http://dresden-elektronik.github.io/deconz-rest-doc/rest/ description: Set attribute of device in deCONZ. See https://home-assistant.io/components/deconz/#device-services for details.
fields: fields:
field: field:
description: Field is a string representing a specific device in Deconz. description: Field is a string representing a specific device in deCONZ.
example: '/lights/1/state' example: '/lights/1/state'
entity:
description: Entity id representing a specific device in deCONZ.
example: 'light.rgb_light'
data: data:
description: Data is a json object with what data you want to alter. description: Data is a json object with what data you want to alter.
example: '{"on": true}' example: '{"on": true}'

View File

@ -99,17 +99,17 @@ PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({
@bind_hass @bind_hass
def is_on(hass: HomeAssistantType, entity_id: str=None): def is_on(hass: HomeAssistantType, entity_id: str = None):
"""Return the state if any or a specified device is home.""" """Return the state if any or a specified device is home."""
entity = entity_id or ENTITY_ID_ALL_DEVICES entity = entity_id or ENTITY_ID_ALL_DEVICES
return hass.states.is_state(entity, STATE_HOME) return hass.states.is_state(entity, STATE_HOME)
def see(hass: HomeAssistantType, mac: str=None, dev_id: str=None, def see(hass: HomeAssistantType, mac: str = None, dev_id: str = None,
host_name: str=None, location_name: str=None, host_name: str = None, location_name: str = None,
gps: GPSType=None, gps_accuracy=None, gps: GPSType = None, gps_accuracy=None,
battery=None, attributes: dict=None): battery=None, attributes: dict = None):
"""Call service to notify you see device.""" """Call service to notify you see device."""
data = {key: value for key, value in data = {key: value for key, value in
((ATTR_MAC, mac), ((ATTR_MAC, mac),
@ -239,11 +239,11 @@ class DeviceTracker(object):
_LOGGER.warning('Duplicate device MAC addresses detected %s', _LOGGER.warning('Duplicate device MAC addresses detected %s',
dev.mac) dev.mac)
def see(self, mac: str=None, dev_id: str=None, host_name: str=None, def see(self, mac: str = None, dev_id: str = None, host_name: str = None,
location_name: str=None, gps: GPSType=None, gps_accuracy=None, location_name: str = None, gps: GPSType = None, gps_accuracy=None,
battery: str=None, attributes: dict=None, battery: str = None, attributes: dict = None,
source_type: str=SOURCE_TYPE_GPS, picture: str=None, source_type: str = SOURCE_TYPE_GPS, picture: str = None,
icon: str=None): icon: str = None):
"""Notify the device tracker that you see a device.""" """Notify the device tracker that you see a device."""
self.hass.add_job( self.hass.add_job(
self.async_see(mac, dev_id, host_name, location_name, gps, self.async_see(mac, dev_id, host_name, location_name, gps,
@ -252,11 +252,11 @@ class DeviceTracker(object):
) )
@asyncio.coroutine @asyncio.coroutine
def async_see(self, mac: str=None, dev_id: str=None, host_name: str=None, def async_see(self, mac: str = None, dev_id: str = None,
location_name: str=None, gps: GPSType=None, host_name: str = None, location_name: str = None,
gps_accuracy=None, battery: str=None, attributes: dict=None, gps: GPSType = None, gps_accuracy=None, battery: str = None,
source_type: str=SOURCE_TYPE_GPS, picture: str=None, attributes: dict = None, source_type: str = SOURCE_TYPE_GPS,
icon: str=None): picture: str = None, icon: str = None):
"""Notify the device tracker that you see a device. """Notify the device tracker that you see a device.
This method is a coroutine. This method is a coroutine.
@ -396,9 +396,9 @@ class Device(Entity):
_state = STATE_NOT_HOME _state = STATE_NOT_HOME
def __init__(self, hass: HomeAssistantType, consider_home: timedelta, def __init__(self, hass: HomeAssistantType, consider_home: timedelta,
track: bool, dev_id: str, mac: str, name: str=None, track: bool, dev_id: str, mac: str, name: str = None,
picture: str=None, gravatar: str=None, icon: str=None, picture: str = None, gravatar: str = None, icon: str = None,
hide_if_away: bool=False, vendor: str=None) -> None: hide_if_away: bool = False, vendor: str = None) -> None:
"""Initialize a device.""" """Initialize a device."""
self.hass = hass self.hass = hass
self.entity_id = ENTITY_ID_FORMAT.format(dev_id) self.entity_id = ENTITY_ID_FORMAT.format(dev_id)
@ -475,9 +475,10 @@ class Device(Entity):
return self.away_hide and self.state != STATE_HOME return self.away_hide and self.state != STATE_HOME
@asyncio.coroutine @asyncio.coroutine
def async_seen(self, host_name: str=None, location_name: str=None, def async_seen(self, host_name: str = None, location_name: str = None,
gps: GPSType=None, gps_accuracy=0, battery: str=None, gps: GPSType = None, gps_accuracy=0, battery: str = None,
attributes: dict=None, source_type: str=SOURCE_TYPE_GPS): attributes: dict = None,
source_type: str = SOURCE_TYPE_GPS):
"""Mark the device as seen.""" """Mark the device as seen."""
self.source_type = source_type self.source_type = source_type
self.last_seen = dt_util.utcnow() self.last_seen = dt_util.utcnow()
@ -504,7 +505,7 @@ class Device(Entity):
# pylint: disable=not-an-iterable # pylint: disable=not-an-iterable
yield from self.async_update() yield from self.async_update()
def stale(self, now: dt_util.dt.datetime=None): def stale(self, now: dt_util.dt.datetime = None):
"""Return if device state is stale. """Return if device state is stale.
Async friendly. Async friendly.
@ -621,16 +622,16 @@ class DeviceScanner(object):
""" """
return self.hass.async_add_job(self.scan_devices) return self.hass.async_add_job(self.scan_devices)
def get_device_name(self, mac: str) -> str: def get_device_name(self, device: str) -> str:
"""Get device name from mac.""" """Get the name of a device."""
raise NotImplementedError() raise NotImplementedError()
def async_get_device_name(self, mac: str) -> Any: def async_get_device_name(self, device: str) -> Any:
"""Get device name from mac. """Get the name of a device.
This method must be run in the event loop and returns a coroutine. This method must be run in the event loop and returns a coroutine.
""" """
return self.hass.async_add_job(self.get_device_name, mac) return self.hass.async_add_job(self.get_device_name, device)
def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta):
@ -648,8 +649,7 @@ def async_load_config(path: str, hass: HomeAssistantType,
""" """
dev_schema = vol.Schema({ dev_schema = vol.Schema({
vol.Required(CONF_NAME): cv.string, vol.Required(CONF_NAME): cv.string,
vol.Optional(CONF_ICON, default=False): vol.Optional(CONF_ICON, default=None): vol.Any(None, cv.icon),
vol.Any(None, cv.icon),
vol.Optional('track', default=False): cv.boolean, vol.Optional('track', default=False): cv.boolean,
vol.Optional(CONF_MAC, default=None): vol.Optional(CONF_MAC, default=None):
vol.Any(None, vol.All(cv.string, vol.Upper)), vol.Any(None, vol.All(cv.string, vol.Upper)),

View File

@ -63,6 +63,7 @@ _IP_NEIGH_REGEX = re.compile(
r'\w+\s' r'\w+\s'
r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s' r'(\w+\s(?P<mac>(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s'
r'\s?(router)?' r'\s?(router)?'
r'\s?(nud)?'
r'(?P<status>(\w+))') r'(?P<status>(\w+))')
_ARP_CMD = 'arp -n' _ARP_CMD = 'arp -n'
@ -118,11 +119,10 @@ class AsusWrtDeviceScanner(DeviceScanner):
if self.protocol == 'ssh': if self.protocol == 'ssh':
self.connection = SshConnection( self.connection = SshConnection(
self.host, self.port, self.username, self.password, self.host, self.port, self.username, self.password,
self.ssh_key, self.mode == 'ap') self.ssh_key)
else: else:
self.connection = TelnetConnection( self.connection = TelnetConnection(
self.host, self.port, self.username, self.password, self.host, self.port, self.username, self.password)
self.mode == 'ap')
self.last_results = {} self.last_results = {}
@ -212,6 +212,9 @@ class AsusWrtDeviceScanner(DeviceScanner):
result = _parse_lines(lines, _IP_NEIGH_REGEX) result = _parse_lines(lines, _IP_NEIGH_REGEX)
devices = {} devices = {}
for device in result: for device in result:
status = device['status']
if status is None or status.upper() != 'REACHABLE':
continue
if device['mac'] is not None: if device['mac'] is not None:
mac = device['mac'].upper() mac = device['mac'].upper()
old_device = cur_devices.get(mac) old_device = cur_devices.get(mac)
@ -226,7 +229,7 @@ class AsusWrtDeviceScanner(DeviceScanner):
result = _parse_lines(lines, _ARP_REGEX) result = _parse_lines(lines, _ARP_REGEX)
devices = {} devices = {}
for device in result: for device in result:
if device['mac']: if device['mac'] is not None:
mac = device['mac'].upper() mac = device['mac'].upper()
devices[mac] = Device(mac, device['ip'], None) devices[mac] = Device(mac, device['ip'], None)
return devices return devices
@ -253,7 +256,7 @@ class _Connection:
class SshConnection(_Connection): class SshConnection(_Connection):
"""Maintains an SSH connection to an ASUS-WRT router.""" """Maintains an SSH connection to an ASUS-WRT router."""
def __init__(self, host, port, username, password, ssh_key, ap): def __init__(self, host, port, username, password, ssh_key):
"""Initialize the SSH connection properties.""" """Initialize the SSH connection properties."""
super().__init__() super().__init__()
@ -263,7 +266,6 @@ class SshConnection(_Connection):
self._username = username self._username = username
self._password = password self._password = password
self._ssh_key = ssh_key self._ssh_key = ssh_key
self._ap = ap
def run_command(self, command): def run_command(self, command):
"""Run commands through an SSH connection. """Run commands through an SSH connection.
@ -323,7 +325,7 @@ class SshConnection(_Connection):
class TelnetConnection(_Connection): class TelnetConnection(_Connection):
"""Maintains a Telnet connection to an ASUS-WRT router.""" """Maintains a Telnet connection to an ASUS-WRT router."""
def __init__(self, host, port, username, password, ap): def __init__(self, host, port, username, password):
"""Initialize the Telnet connection properties.""" """Initialize the Telnet connection properties."""
super().__init__() super().__init__()
@ -332,7 +334,6 @@ class TelnetConnection(_Connection):
self._port = port self._port = port
self._username = username self._username = username
self._password = password self._password = password
self._ap = ap
self._prompt_string = None self._prompt_string = None
def run_command(self, command): def run_command(self, command):

View File

@ -23,7 +23,7 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.event import async_track_time_interval
REQUIREMENTS = ['aioautomatic==0.6.4'] REQUIREMENTS = ['aioautomatic==0.6.5']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -49,8 +49,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_CLIENT_ID): cv.string, vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_SECRET): cv.string, vol.Required(CONF_SECRET): cv.string,
vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean,
vol.Optional(CONF_DEVICES, default=None): vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]),
vol.All(cv.ensure_list, [cv.string]),
}) })
@ -109,7 +108,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
_write_refresh_token_to_file, hass, filename, _write_refresh_token_to_file, hass, filename,
session.refresh_token) session.refresh_token)
data = AutomaticData( data = AutomaticData(
hass, client, session, config[CONF_DEVICES], async_see) hass, client, session, config.get(CONF_DEVICES), async_see)
# Load the initial vehicle data # Load the initial vehicle data
vehicles = yield from session.get_vehicles() vehicles = yield from session.get_vehicles()
@ -177,10 +176,9 @@ class AutomaticAuthCallbackView(HomeAssistantView):
_LOGGER.error( _LOGGER.error(
"Error authorizing Automatic: %s", params['error']) "Error authorizing Automatic: %s", params['error'])
return response return response
else: _LOGGER.error(
_LOGGER.error( "Error authorizing Automatic. Invalid response returned")
"Error authorizing Automatic. Invalid response returned") return response
return response
if DATA_CONFIGURING not in hass.data or \ if DATA_CONFIGURING not in hass.data or \
params['state'] not in hass.data[DATA_CONFIGURING]: params['state'] not in hass.data[DATA_CONFIGURING]:

View File

@ -45,10 +45,10 @@ class BboxDeviceScanner(DeviceScanner):
return [device.mac for device in self.last_results] return [device.mac for device in self.last_results]
def get_device_name(self, mac): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results if filter_named = [result.name for result in self.last_results if
device.mac == mac] result.mac == device]
if filter_named: if filter_named:
return filter_named[0] return filter_named[0]

View File

@ -102,7 +102,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
"""Lookup Bluetooth LE devices and update status.""" """Lookup Bluetooth LE devices and update status."""
devs = discover_ble_devices() devs = discover_ble_devices()
for mac in devs_to_track: for mac in devs_to_track:
_LOGGER.debug("Checking " + mac) _LOGGER.debug("Checking %s", mac)
result = mac in devs result = mac in devs
if not result: if not result:
# Could not lookup device name # Could not lookup device name

View File

@ -41,7 +41,7 @@ def setup_scanner(hass, config, see, discovery_info=None):
result = bluetooth.discover_devices( result = bluetooth.discover_devices(
duration=8, lookup_names=True, flush_cache=True, duration=8, lookup_names=True, flush_cache=True,
lookup_class=False) lookup_class=False)
_LOGGER.debug("Bluetooth devices discovered = " + str(len(result))) _LOGGER.debug("Bluetooth devices discovered = %d", len(result))
return result return result
yaml_path = hass.config.path(YAML_DEVICES) yaml_path = hass.config.path(YAML_DEVICES)

View File

@ -0,0 +1,51 @@
"""Device tracker for BMW Connected Drive vehicles.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.bmw_connected_drive/
"""
import logging
from homeassistant.components.bmw_connected_drive import DOMAIN \
as BMW_DOMAIN
from homeassistant.util import slugify
DEPENDENCIES = ['bmw_connected_drive']
_LOGGER = logging.getLogger(__name__)
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the BMW tracker."""
accounts = hass.data[BMW_DOMAIN]
_LOGGER.debug('Found BMW accounts: %s',
', '.join([a.name for a in accounts]))
for account in accounts:
for vehicle in account.account.vehicles:
tracker = BMWDeviceTracker(see, vehicle)
account.add_update_listener(tracker.update)
tracker.update()
return True
class BMWDeviceTracker(object):
"""BMW Connected Drive device tracker."""
def __init__(self, see, vehicle):
"""Initialize the Tracker."""
self._see = see
self.vehicle = vehicle
def update(self) -> None:
"""Update the device info."""
dev_id = slugify(self.vehicle.modelName)
_LOGGER.debug('Updating %s', dev_id)
attrs = {
'trackr_id': dev_id,
'id': dev_id,
'name': self.vehicle.modelName
}
self._see(
dev_id=dev_id, host_name=self.vehicle.modelName,
gps=self.vehicle.state.gps_position, attributes=attrs,
icon='mdi:car'
)

View File

@ -75,9 +75,9 @@ class FritzBoxScanner(DeviceScanner):
active_hosts.append(known_host['mac']) active_hosts.append(known_host['mac'])
return active_hosts return active_hosts
def get_device_name(self, mac): def get_device_name(self, device):
"""Return the name of the given device or None if is not known.""" """Return the name of the given device or None if is not known."""
ret = self.fritz_box.get_specific_host_entry(mac).get( ret = self.fritz_box.get_specific_host_entry(device).get(
'NewHostName' 'NewHostName'
) )
if ret == {}: if ret == {}:

View File

@ -120,8 +120,7 @@ class GeofencyView(HomeAssistantView):
"""Return name of device tracker.""" """Return name of device tracker."""
if 'beaconUUID' in data: if 'beaconUUID' in data:
return "{}_{}".format(BEACON_DEV_PREFIX, data['name']) return "{}_{}".format(BEACON_DEV_PREFIX, data['name'])
else: return data['device']
return data['device']
@asyncio.coroutine @asyncio.coroutine
def _set_location(self, hass, data, location_name): def _set_location(self, hass, data, location_name):

View File

@ -60,11 +60,11 @@ class HitronCODADeviceScanner(DeviceScanner):
return [device.mac for device in self.last_results] return [device.mac for device in self.last_results]
def get_device_name(self, mac): def get_device_name(self, device):
"""Return the name of the device with the given MAC address.""" """Return the name of the device with the given MAC address."""
name = next(( name = next((
device.name for device in self.last_results result.name for result in self.last_results
if device.mac == mac), None) if result.mac == device), None)
return name return name
def _login(self): def _login(self):

View File

@ -86,6 +86,7 @@ class HuaweiDeviceScanner(DeviceScanner):
active_clients = [client for client in data if client.state] active_clients = [client for client in data if client.state]
self.last_results = active_clients self.last_results = active_clients
# pylint: disable=logging-not-lazy
_LOGGER.debug("Active clients: " + "\n" _LOGGER.debug("Active clients: " + "\n"
.join((client.mac + " " + client.name) .join((client.mac + " " + client.name)
for client in active_clients)) for client in active_clients))

View File

@ -67,10 +67,10 @@ class KeeneticNDMS2DeviceScanner(DeviceScanner):
return [device.mac for device in self.last_results] return [device.mac for device in self.last_results]
def get_device_name(self, mac): def get_device_name(self, device):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
filter_named = [device.name for device in self.last_results filter_named = [result.name for result in self.last_results
if device.mac == mac] if result.mac == device]
if filter_named: if filter_named:
return filter_named[0] return filter_named[0]

View File

@ -62,7 +62,7 @@ class LinksysAPDeviceScanner(DeviceScanner):
return self.last_results return self.last_results
# pylint: disable=no-self-use # pylint: disable=no-self-use
def get_device_name(self, mac): def get_device_name(self, device):
""" """
Return the name (if known) of the device. Return the name (if known) of the device.

View File

@ -45,9 +45,9 @@ class LinksysSmartWifiDeviceScanner(DeviceScanner):
return self.last_results.keys() return self.last_results.keys()
def get_device_name(self, mac): def get_device_name(self, device):
"""Return the name (if known) of the device.""" """Return the name (if known) of the device."""
return self.last_results.get(mac) return self.last_results.get(device)
def _update_info(self): def _update_info(self):
"""Check for connected devices.""" """Check for connected devices."""

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