Merge pull request #12267 from home-assistant/release-0-63

0.63
pull/12382/head 0.63
Paulus Schoutsen 2018-02-10 14:00:29 -08:00 committed by GitHub
commit 0e16f7f307
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
442 changed files with 10321 additions and 5898 deletions

View File

@ -145,6 +145,9 @@ omit =
homeassistant/components/maxcube.py
homeassistant/components/*/maxcube.py
homeassistant/components/mercedesme.py
homeassistant/components/*/mercedesme.py
homeassistant/components/mochad.py
homeassistant/components/*/mochad.py
@ -377,6 +380,7 @@ omit =
homeassistant/components/fan/xiaomi_miio.py
homeassistant/components/feedreader.py
homeassistant/components/foursquare.py
homeassistant/components/goalfeed.py
homeassistant/components/ifttt.py
homeassistant/components/image_processing/dlib_face_detect.py
homeassistant/components/image_processing/dlib_face_identify.py
@ -435,6 +439,7 @@ omit =
homeassistant/components/media_player/kodi.py
homeassistant/components/media_player/lg_netcast.py
homeassistant/components/media_player/liveboxplaytv.py
homeassistant/components/media_player/mediaroom.py
homeassistant/components/media_player/mpchc.py
homeassistant/components/media_player/mpd.py
homeassistant/components/media_player/nad.py
@ -449,7 +454,6 @@ omit =
homeassistant/components/media_player/roku.py
homeassistant/components/media_player/russound_rio.py
homeassistant/components/media_player/russound_rnet.py
homeassistant/components/media_player/samsungtv.py
homeassistant/components/media_player/snapcast.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/spotify.py
@ -506,6 +510,7 @@ omit =
homeassistant/components/remember_the_milk/__init__.py
homeassistant/components/remote/harmony.py
homeassistant/components/remote/itach.py
homeassistant/components/remote/xiaomi_miio.py
homeassistant/components/scene/hunterdouglas_powerview.py
homeassistant/components/scene/lifx_cloud.py
homeassistant/components/sensor/airvisual.py
@ -592,6 +597,7 @@ omit =
homeassistant/components/sensor/pi_hole.py
homeassistant/components/sensor/plex.py
homeassistant/components/sensor/pocketcasts.py
homeassistant/components/sensor/pollen.py
homeassistant/components/sensor/pushbullet.py
homeassistant/components/sensor/pvoutput.py
homeassistant/components/sensor/pyload.py

5
.gitignore vendored
View File

@ -16,7 +16,9 @@ Icon
# Thumbnails
._*
# IntelliJ IDEA
.idea
*.iml
# pytest
.cache
@ -98,3 +100,6 @@ desktop.ini
/home-assistant.pyproj
/home-assistant.sln
/.vs/*
# mypy
/.mypy_cache/*

View File

@ -41,7 +41,7 @@ homeassistant/components/*/zwave.py @home-assistant/z-wave
homeassistant/components/hassio.py @home-assistant/hassio
# Indiviudal components
# Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50
@ -61,6 +61,7 @@ homeassistant/components/sensor/airvisual.py @bachya
homeassistant/components/sensor/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git

View File

@ -2,5 +2,5 @@
<li><a href="https://home-assistant.io/">Homepage</a></li>
<li><a href="https://community.home-assistant.io">Community Forums</a></li>
<li><a href="https://github.com/home-assistant/home-assistant">GitHub</a></li>
<li><a href="https://gitter.im/home-assistant/home-assistant">Gitter</a></li>
<li><a href="https://discord.gg/c5DvZ4e">Discord</a></li>
</ul>

View File

@ -182,7 +182,8 @@ def check_pid(pid_file: str) -> None:
"""Check that Home Assistant is not already running."""
# Check pid file
try:
pid = int(open(pid_file, 'r').readline())
with open(pid_file, 'r') as file:
pid = int(file.readline())
except IOError:
# PID File does not exist
return
@ -204,7 +205,8 @@ def write_pid(pid_file: str) -> None:
"""Create a PID File."""
pid = os.getpid()
try:
open(pid_file, 'w').write(str(pid))
with open(pid_file, 'w') as file:
file.write(str(pid))
except IOError:
print('Fatal Error: Unable to write pid file {}'.format(pid_file))
sys.exit(1)

View File

@ -133,7 +133,7 @@ def async_setup(hass, config):
# have been processed. If a service does not exist it causes a 10
# second delay while we're blocking waiting for a response.
# But services can be registered on other HA instances that are
# listening to the bus too. So as a in between solution, we'll
# listening to the bus too. So as an in between solution, we'll
# block only if the service is defined in the current HA instance.
blocking = hass.services.has_service(domain, service.service)

View File

@ -277,7 +277,7 @@ class Alert(ToggleEntity):
yield from self.async_update_ha_state()
@asyncio.coroutine
def async_toggle(self):
def async_toggle(self, **kwargs):
"""Async toggle alert."""
if self._ack:
return self.async_turn_on()

View File

@ -17,7 +17,7 @@ from homeassistant.const import (
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS,
CONF_UNIT_OF_MEASUREMENT)
CONF_UNIT_OF_MEASUREMENT, STATE_LOCKED, STATE_UNLOCKED, STATE_ON)
from .const import CONF_FILTER, CONF_ENTITY_CONFIG
_LOGGER = logging.getLogger(__name__)
@ -40,6 +40,7 @@ CONF_DESCRIPTION = 'description'
CONF_DISPLAY_CATEGORIES = 'display_categories'
HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
class _DisplayCategory(object):
@ -50,8 +51,8 @@ class _DisplayCategory(object):
# Describes a combination of devices set to a specific state, when the
# state change must occur in a specific order. For example, a "watch
# Neflix" scene might require the: 1. TV to be powered on & 2. Input set to
# HDMI1. Applies to Scenes
# Netflix" scene might require the: 1. TV to be powered on & 2. Input set
# to HDMI1. Applies to Scenes
ACTIVITY_TRIGGER = "ACTIVITY_TRIGGER"
# Indicates media devices with video or photo capabilities.
@ -133,10 +134,36 @@ def _capability(interface,
return result
class _EntityCapabilities(object):
class _UnsupportedInterface(Exception):
"""This entity does not support the requested Smart Home API interface."""
class _UnsupportedProperty(Exception):
"""This entity does not support the requested Smart Home API property."""
class _AlexaEntity(object):
"""An adaptation of an entity, expressed in Alexa's terms.
The API handlers should manipulate entities only through this interface.
"""
def __init__(self, config, entity):
self.config = config
self.entity = entity
self.entity_conf = config.entity_config.get(entity.entity_id, {})
def friendly_name(self):
"""Return the Alexa API friendly name."""
return self.entity_conf.get(CONF_NAME, self.entity.name)
def description(self):
"""Return the Alexa API description."""
return self.entity_conf.get(CONF_DESCRIPTION, self.entity.entity_id)
def entity_id(self):
"""Return the Alexa API entity id."""
return self.entity.entity_id.replace('.', '#')
def display_categories(self):
"""Return a list of display categories."""
@ -154,17 +181,217 @@ class _EntityCapabilities(object):
"""
raise NotImplementedError
def capabilities(self):
"""Return a list of supported capabilities.
def get_interface(self, capability):
"""Return the given _AlexaInterface.
If the returned list is empty, the entity will not be discovered.
Raises _UnsupportedInterface.
"""
pass
You might find _capability() useful.
def interfaces(self):
"""Return a list of supported interfaces.
Used for discovery. The list should contain _AlexaInterface instances.
If the list is empty, this entity will not be discovered.
"""
raise NotImplementedError
class _GenericCapabilities(_EntityCapabilities):
class _AlexaInterface(object):
def __init__(self, entity):
self.entity = entity
def name(self):
"""Return the Alexa API name of this interface."""
raise NotImplementedError
@staticmethod
def properties_supported():
"""Return what properties this entity supports."""
return []
@staticmethod
def properties_proactively_reported():
"""Return True if properties asynchronously reported."""
return False
@staticmethod
def properties_retrievable():
"""Return True if properties can be retrieved."""
return False
@staticmethod
def get_property(name):
"""Read and return a property.
Return value should be a dict, or raise _UnsupportedProperty.
Properties can also have a timeOfSample and uncertaintyInMilliseconds,
but returning those metadata is not yet implemented.
"""
raise _UnsupportedProperty(name)
@staticmethod
def supports_deactivation():
"""Applicable only to scenes."""
return None
def serialize_discovery(self):
"""Serialize according to the Discovery API."""
result = {
'type': 'AlexaInterface',
'interface': self.name(),
'version': '3',
'properties': {
'supported': self.properties_supported(),
'proactivelyReported': self.properties_proactively_reported(),
'retrievable': self.properties_retrievable(),
},
}
# pylint: disable=assignment-from-none
supports_deactivation = self.supports_deactivation()
if supports_deactivation is not None:
result['supportsDeactivation'] = supports_deactivation
return result
def serialize_properties(self):
"""Return properties serialized for an API response."""
for prop in self.properties_supported():
prop_name = prop['name']
yield {
'name': prop_name,
'namespace': self.name(),
'value': self.get_property(prop_name),
}
class _AlexaPowerController(_AlexaInterface):
def name(self):
return 'Alexa.PowerController'
def properties_supported(self):
return [{'name': 'powerState'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'powerState':
raise _UnsupportedProperty(name)
if self.entity.state == STATE_ON:
return 'ON'
return 'OFF'
class _AlexaLockController(_AlexaInterface):
def name(self):
return 'Alexa.LockController'
def properties_supported(self):
return [{'name': 'lockState'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'lockState':
raise _UnsupportedProperty(name)
if self.entity.state == STATE_LOCKED:
return 'LOCKED'
elif self.entity.state == STATE_UNLOCKED:
return 'UNLOCKED'
return 'JAMMED'
class _AlexaSceneController(_AlexaInterface):
def __init__(self, entity, supports_deactivation):
_AlexaInterface.__init__(self, entity)
self.supports_deactivation = lambda: supports_deactivation
def name(self):
return 'Alexa.SceneController'
class _AlexaBrightnessController(_AlexaInterface):
def name(self):
return 'Alexa.BrightnessController'
def properties_supported(self):
return [{'name': 'brightness'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'brightness':
raise _UnsupportedProperty(name)
return round(self.entity.attributes['brightness'] / 255.0 * 100)
class _AlexaColorController(_AlexaInterface):
def name(self):
return 'Alexa.ColorController'
class _AlexaColorTemperatureController(_AlexaInterface):
def name(self):
return 'Alexa.ColorTemperatureController'
class _AlexaPercentageController(_AlexaInterface):
def name(self):
return 'Alexa.PercentageController'
class _AlexaSpeaker(_AlexaInterface):
def name(self):
return 'Alexa.Speaker'
class _AlexaStepSpeaker(_AlexaInterface):
def name(self):
return 'Alexa.StepSpeaker'
class _AlexaPlaybackController(_AlexaInterface):
def name(self):
return 'Alexa.PlaybackController'
class _AlexaInputController(_AlexaInterface):
def name(self):
return 'Alexa.InputController'
class _AlexaTemperatureSensor(_AlexaInterface):
def name(self):
return 'Alexa.TemperatureSensor'
def properties_supported(self):
return [{'name': 'temperature'}]
def properties_retrievable(self):
return True
def get_property(self, name):
if name != 'temperature':
raise _UnsupportedProperty(name)
unit = self.entity.attributes[CONF_UNIT_OF_MEASUREMENT]
return {
'value': float(self.entity.state),
'scale': API_TEMP_UNITS[unit],
}
@ENTITY_ADAPTERS.register(alert.DOMAIN)
@ENTITY_ADAPTERS.register(automation.DOMAIN)
@ENTITY_ADAPTERS.register(input_boolean.DOMAIN)
class _GenericCapabilities(_AlexaEntity):
"""A generic, on/off device.
The choice of last resort.
@ -173,78 +400,87 @@ class _GenericCapabilities(_EntityCapabilities):
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def capabilities(self):
return [_capability('Alexa.PowerController')]
def interfaces(self):
return [_AlexaPowerController(self.entity)]
class _SwitchCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(switch.DOMAIN)
class _SwitchCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SWITCH]
def capabilities(self):
return [_capability('Alexa.PowerController')]
def interfaces(self):
return [_AlexaPowerController(self.entity)]
class _CoverCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(cover.DOMAIN)
class _CoverCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.DOOR]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION:
capabilities.append(_capability('Alexa.PercentageController'))
return capabilities
yield _AlexaPercentageController(self.entity)
class _LightCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(light.DOMAIN)
class _LightCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.LIGHT]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & light.SUPPORT_BRIGHTNESS:
capabilities.append(_capability('Alexa.BrightnessController'))
yield _AlexaBrightnessController(self.entity)
if supported & light.SUPPORT_RGB_COLOR:
capabilities.append(_capability('Alexa.ColorController'))
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_XY_COLOR:
capabilities.append(_capability('Alexa.ColorController'))
yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_COLOR_TEMP:
capabilities.append(
_capability('Alexa.ColorTemperatureController'))
return capabilities
yield _AlexaColorTemperatureController(self.entity)
class _FanCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(fan.DOMAIN)
class _FanCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.OTHER]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & fan.SUPPORT_SET_SPEED:
capabilities.append(_capability('Alexa.PercentageController'))
return capabilities
yield _AlexaPercentageController(self.entity)
class _LockCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(lock.DOMAIN)
class _LockCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SMARTLOCK]
def capabilities(self):
return [_capability('Alexa.LockController')]
def interfaces(self):
return [_AlexaLockController(self.entity)]
class _MediaPlayerCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(media_player.DOMAIN)
class _MediaPlayerCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.TV]
def capabilities(self):
capabilities = [_capability('Alexa.PowerController')]
def interfaces(self):
yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & media_player.SUPPORT_VOLUME_SET:
capabilities.append(_capability('Alexa.Speaker'))
yield _AlexaSpeaker(self.entity)
step_volume_features = (media_player.SUPPORT_VOLUME_MUTE |
media_player.SUPPORT_VOLUME_STEP)
if supported & step_volume_features:
yield _AlexaStepSpeaker(self.entity)
playback_features = (media_player.SUPPORT_PLAY |
media_player.SUPPORT_PAUSE |
@ -252,89 +488,62 @@ class _MediaPlayerCapabilities(_EntityCapabilities):
media_player.SUPPORT_NEXT_TRACK |
media_player.SUPPORT_PREVIOUS_TRACK)
if supported & playback_features:
capabilities.append(_capability('Alexa.PlaybackController'))
yield _AlexaPlaybackController(self.entity)
return capabilities
if supported & media_player.SUPPORT_SELECT_SOURCE:
yield _AlexaInputController(self.entity)
class _SceneCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(scene.DOMAIN)
class _SceneCapabilities(_AlexaEntity):
def description(self):
# Required description as per Amazon Scene docs
scene_fmt = '{} (Scene connected via Home Assistant)'
return scene_fmt.format(_AlexaEntity.description(self))
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self):
return [_capability('Alexa.SceneController')]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=False)]
class _ScriptCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(script.DOMAIN)
class _ScriptCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.ACTIVITY_TRIGGER]
def capabilities(self):
def interfaces(self):
can_cancel = bool(self.entity.attributes.get('can_cancel'))
return [_capability('Alexa.SceneController',
supports_deactivation=can_cancel)]
return [_AlexaSceneController(self.entity,
supports_deactivation=can_cancel)]
class _GroupCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(group.DOMAIN)
class _GroupCapabilities(_AlexaEntity):
def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self):
return [_capability('Alexa.SceneController',
supports_deactivation=True)]
def interfaces(self):
return [_AlexaSceneController(self.entity,
supports_deactivation=True)]
class _SensorCapabilities(_EntityCapabilities):
@ENTITY_ADAPTERS.register(sensor.DOMAIN)
class _SensorCapabilities(_AlexaEntity):
def default_display_categories(self):
# although there are other kinds of sensors, all but temperature
# sensors are currently ignored.
return [_DisplayCategory.TEMPERATURE_SENSOR]
def capabilities(self):
capabilities = []
def interfaces(self):
attrs = self.entity.attributes
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT,
TEMP_CELSIUS,
):
capabilities.append(_capability(
'Alexa.TemperatureSensor',
retrievable=True,
properties_supported=[{'name': 'temperature'}]))
return capabilities
class _UnknownEntityDomainError(Exception):
pass
def _capabilities_for_entity(config, entity):
"""Return an _EntityCapabilities appropriate for given entity.
raises _UnknownEntityDomainError if the given domain is unsupported.
"""
if entity.domain not in _CAPABILITIES_FOR_DOMAIN:
raise _UnknownEntityDomainError()
return _CAPABILITIES_FOR_DOMAIN[entity.domain](config, entity)
_CAPABILITIES_FOR_DOMAIN = {
alert.DOMAIN: _GenericCapabilities,
automation.DOMAIN: _GenericCapabilities,
cover.DOMAIN: _CoverCapabilities,
fan.DOMAIN: _FanCapabilities,
group.DOMAIN: _GroupCapabilities,
input_boolean.DOMAIN: _GenericCapabilities,
light.DOMAIN: _LightCapabilities,
lock.DOMAIN: _LockCapabilities,
media_player.DOMAIN: _MediaPlayerCapabilities,
scene.DOMAIN: _SceneCapabilities,
script.DOMAIN: _ScriptCapabilities,
switch.DOMAIN: _SwitchCapabilities,
sensor.DOMAIN: _SensorCapabilities,
}
yield _AlexaTemperatureSensor(self.entity)
class _Cause(object):
@ -468,7 +677,7 @@ def api_message(request,
}
}
# If a correlation token exsits, add it to header / Need by Async requests
# If a correlation token exists, add it to header / Need by Async requests
token = request[API_HEADER].get('correlationToken')
if token:
response[API_EVENT][API_HEADER]['correlationToken'] = token
@ -511,36 +720,26 @@ def async_api_discovery(hass, config, request):
entity.entity_id)
continue
try:
entity_capabilities = _capabilities_for_entity(config, entity)
except _UnknownEntityDomainError:
if entity.domain not in ENTITY_ADAPTERS:
continue
entity_conf = config.entity_config.get(entity.entity_id, {})
friendly_name = entity_conf.get(CONF_NAME, entity.name)
description = entity_conf.get(CONF_DESCRIPTION, entity.entity_id)
# Required description as per Amazon Scene docs
if entity.domain == scene.DOMAIN:
scene_fmt = '{} (Scene connected via Home Assistant)'
description = scene_fmt.format(description)
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
endpoint = {
'displayCategories': entity_capabilities.display_categories(),
'displayCategories': alexa_entity.display_categories(),
'additionalApplianceDetails': {},
'endpointId': entity.entity_id.replace('.', '#'),
'friendlyName': friendly_name,
'description': description,
'endpointId': alexa_entity.entity_id(),
'friendlyName': alexa_entity.friendly_name(),
'description': alexa_entity.description(),
'manufacturerName': 'Home Assistant',
}
alexa_capabilities = entity_capabilities.capabilities()
if not alexa_capabilities:
endpoint['capabilities'] = [
i.serialize_discovery() for i in alexa_entity.interfaces()]
if not endpoint['capabilities']:
_LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id)
continue
endpoint['capabilities'] = alexa_capabilities
discovery_endpoints.append(endpoint)
return api_message(
@ -624,7 +823,7 @@ def async_api_set_brightness(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_adjust_brightness(hass, config, request, entity):
"""Process a adjust brightness request."""
"""Process an adjust brightness request."""
brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
# read current state
@ -812,7 +1011,7 @@ def async_api_set_percentage(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_adjust_percentage(hass, config, request, entity):
"""Process a adjust percentage request."""
"""Process an adjust percentage request."""
percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
service = None
data = {ATTR_ENTITY_ID: entity.entity_id}
@ -873,7 +1072,7 @@ def async_api_lock(hass, config, request, entity):
@extract_entity
@asyncio.coroutine
def async_api_unlock(hass, config, request, entity):
"""Process a unlock request."""
"""Process an unlock request."""
yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=False)
@ -900,11 +1099,46 @@ def async_api_set_volume(hass, config, request, entity):
return api_message(request)
@HANDLERS.register(('Alexa.InputController', 'SelectInput'))
@extract_entity
@asyncio.coroutine
def async_api_select_input(hass, config, request, entity):
"""Process a set input request."""
media_input = request[API_PAYLOAD]['input']
# attempt to map the ALL UPPERCASE payload name to a source
source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or []
for source in source_list:
# response will always be space separated, so format the source in the
# most likely way to find a match
formatted_source = source.lower().replace('-', ' ').replace('_', ' ')
if formatted_source in media_input.lower():
media_input = source
break
else:
msg = 'failed to map input {} to a media source on {}'.format(
media_input, entity.entity_id)
_LOGGER.error(msg)
return api_error(
request, error_type='INVALID_VALUE', error_message=msg)
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_INPUT_SOURCE: media_input,
}
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_SELECT_SOURCE,
data, blocking=False)
return api_message(request)
@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_volume(hass, config, request, entity):
"""Process a adjust volume request."""
"""Process an adjust volume request."""
volume_delta = int(request[API_PAYLOAD]['volume'])
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
@ -929,6 +1163,30 @@ def async_api_adjust_volume(hass, config, request, entity):
return api_message(request)
@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume'))
@extract_entity
@asyncio.coroutine
def async_api_adjust_volume_step(hass, config, request, entity):
"""Process an adjust volume step request."""
volume_step = round(float(request[API_PAYLOAD]['volume'] / 100), 2)
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL)
volume = current_level + volume_step
data = {
ATTR_ENTITY_ID: entity.entity_id,
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_SET,
data, blocking=False)
return api_message(request)
@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute'))
@HANDLERS.register(('Alexa.Speaker', 'SetMute'))
@extract_entity
@asyncio.coroutine
@ -1033,18 +1291,13 @@ def async_api_previous(hass, config, request, entity):
@asyncio.coroutine
def async_api_reportstate(hass, config, request, entity):
"""Process a ReportState request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT]
temp_property = {
'namespace': 'Alexa.TemperatureSensor',
'name': 'temperature',
'value': {
'value': float(entity.state),
'scale': API_TEMP_UNITS[unit],
},
}
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
properties = []
for interface in alexa_entity.interfaces():
properties.extend(interface.serialize_properties())
return api_message(
request,
name='StateReport',
context={'properties': [temp_property]}
context={'properties': properties}
)

View File

@ -251,7 +251,7 @@ class AndroidIPCamEntity(Entity):
"""The Android device running IP Webcam."""
def __init__(self, host, ipcam):
"""Initialize the data oject."""
"""Initialize the data object."""
self._host = host
self._ipcam = ipcam

View File

@ -66,7 +66,7 @@ class APCUPSdData(object):
"""
def __init__(self, host, port):
"""Initialize the data oject."""
"""Initialize the data object."""
from apcaccess import status
self._host = host
self._port = port

View File

@ -47,7 +47,7 @@ def setup(hass, config):
return False
hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex:
_LOGGER.error("Unable to connect to Netgar Arlo: %s", str(ex))
_LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex))
hass.components.persistent_notification.create(
'Error: {}<br />'
'You will need to restart hass after fixing.'

View File

@ -50,7 +50,6 @@ class BloomSkySensor(BinarySensorDevice):
self._device_id = device['DeviceID']
self._sensor_name = sensor_name
self._name = '{} {}'.format(device['DeviceName'], sensor_name)
self._unique_id = 'bloomsky_binary_sensor {}'.format(self._name)
self._state = None
@property
@ -58,11 +57,6 @@ class BloomSkySensor(BinarySensorDevice):
"""Return the name of the BloomSky device and this sensor."""
return self._name
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
return self._unique_id
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""

View File

@ -65,6 +65,11 @@ class DeconzBinarySensor(BinarySensorDevice):
"""Return the name of the sensor."""
return self._sensor.name
@property
def unique_id(self):
"""Return a unique identifier for this sensor."""
return self._sensor.uniqueid
@property
def device_class(self):
"""Return the class of the sensor."""

View File

@ -50,11 +50,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
"""Return the status of the sensor."""
return self._state == 'true'
@property
def unique_id(self):
"""Return the unique ID of this sensor."""
return "binary_sensor_ecobee_{}_{}".format(self._name, self.index)
@property
def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES."""

View File

@ -48,7 +48,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
@asyncio.coroutine
def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
"""Set up the FFmpeg binary moition sensor."""
"""Set up the FFmpeg binary motion sensor."""
manager = hass.data[DATA_FFMPEG]
if not manager.async_run_test(config.get(CONF_INPUT)):

View File

@ -118,7 +118,7 @@ class HikvisionData(object):
"""Hikvision device event stream object."""
def __init__(self, hass, url, port, name, username, password):
"""Initialize the data oject."""
"""Initialize the data object."""
from pyhik.hikvision import HikCamera
self._url = url
self._port = port
@ -212,7 +212,7 @@ class HikvisionBinarySensor(BinarySensorDevice):
@property
def unique_id(self):
"""Return an unique ID."""
return '{}.{}'.format(self.__class__, self._id)
return self._id
@property
def is_on(self):

View File

@ -59,5 +59,5 @@ class HiveBinarySensorEntity(BinarySensorDevice):
self.node_device_type)
def update(self):
"""Update all Node data frome Hive."""
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)

View File

@ -69,7 +69,8 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice):
"""
def __init__(self, ihc_controller, name, ihc_id: int, info: bool,
sensor_type: str, inverting: bool, product: Element=None):
sensor_type: str, inverting: bool,
product: Element=None) -> None:
"""Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None

View File

@ -83,5 +83,5 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice):
@callback
def async_binarysensor_update(self, message):
"""Receive notification from transport that new data exists."""
_LOGGER.info("Received update calback from PLM for %s", self._address)
_LOGGER.info("Received update callback from PLM for %s", self._address)
self._hass.async_add_job(self.async_update_ha_state())

View File

@ -67,8 +67,8 @@ def setup_platform(hass, config: ConfigType,
elif subnode_id == 2:
parent_device.add_negative_node(node)
elif device_type == 'moisture':
# Moisure nodes have a subnode 2, but we ignore it because it's
# just the inverse of the primary node.
# Moisture nodes have a subnode 2, but we ignore it because
# it's just the inverse of the primary node.
if subnode_id == 4:
# Heartbeat node
device = ISYBinarySensorHeartbeat(node, parent_device)

View File

@ -0,0 +1,94 @@
"""
Support for Mercedes cars with Mercedes ME.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/binary_sensor.mercedesme/
"""
import logging
import datetime
from homeassistant.components.binary_sensor import (BinarySensorDevice)
from homeassistant.components.mercedesme import (
DATA_MME, MercedesMeEntity, BINARY_SENSORS)
DEPENDENCIES = ['mercedesme']
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the sensor platform."""
data = hass.data[DATA_MME].data
if not data.cars:
_LOGGER.error("No cars found. Check component log.")
return
devices = []
for car in data.cars:
for key, value in sorted(BINARY_SENSORS.items()):
devices.append(MercedesMEBinarySensor(
data, key, value[0], car["vin"], None))
add_devices(devices, True)
class MercedesMEBinarySensor(MercedesMeEntity, BinarySensorDevice):
"""Representation of a Sensor."""
@property
def is_on(self):
"""Return the state of the binary sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
if self._internal_name == "windowsClosed":
return {
"window_front_left": self._car["windowStatusFrontLeft"],
"window_front_right": self._car["windowStatusFrontRight"],
"window_rear_left": self._car["windowStatusRearLeft"],
"window_rear_right": self._car["windowStatusRearRight"],
"original_value": self._car[self._internal_name],
"last_update": datetime.datetime.fromtimestamp(
self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
"car": self._car["license"]
}
elif self._internal_name == "tireWarningLight":
return {
"front_right_tire_pressure_kpa":
self._car["frontRightTirePressureKpa"],
"front_left_tire_pressure_kpa":
self._car["frontLeftTirePressureKpa"],
"rear_right_tire_pressure_kpa":
self._car["rearRightTirePressureKpa"],
"rear_left_tire_pressure_kpa":
self._car["rearLeftTirePressureKpa"],
"original_value": self._car[self._internal_name],
"last_update": datetime.datetime.fromtimestamp(
self._car["lastUpdate"]
).strftime('%Y-%m-%d %H:%M:%S'),
"car": self._car["license"],
}
return {
"original_value": self._car[self._internal_name],
"last_update": datetime.datetime.fromtimestamp(
self._car["lastUpdate"]).strftime('%Y-%m-%d %H:%M:%S'),
"car": self._car["license"]
}
def update(self):
"""Fetch new state data for the sensor."""
self._car = next(
car for car in self._data.cars if car["vin"] == self._vin)
if self._internal_name == "windowsClosed":
self._state = bool(self._car[self._internal_name] == "CLOSED")
elif self._internal_name == "tireWarningLight":
self._state = bool(self._car[self._internal_name] != "INACTIVE")
else:
self._state = self._car[self._internal_name] is True
_LOGGER.debug("Updated %s Value: %s IsOn: %s",
self._internal_name, self._state, self.is_on)

View File

@ -14,8 +14,8 @@ import homeassistant.components.mqtt as mqtt
from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF,
CONF_DEVICE_CLASS)
CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
@ -24,8 +24,10 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_FORCE_UPDATE = False
DEPENDENCIES = ['mqtt']
@ -34,6 +36,7 @@ PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({
vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean,
}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema)
@ -53,6 +56,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
config.get(CONF_AVAILABILITY_TOPIC),
config.get(CONF_DEVICE_CLASS),
config.get(CONF_QOS),
config.get(CONF_FORCE_UPDATE),
config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE),
@ -65,7 +69,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class,
qos, payload_on, payload_off, payload_available,
qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template):
"""Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available,
@ -77,6 +81,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._payload_on = payload_on
self._payload_off = payload_off
self._qos = qos
self._force_update = force_update
self._template = value_template
@asyncio.coroutine
@ -94,6 +99,11 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._state = True
elif payload == self._payload_off:
self._state = False
else: # Payload is not for this entity
_LOGGER.warning('No matching payload found'
' for entity: %s with state_topic: %s',
self._name, self._state_topic)
return
self.async_schedule_update_ha_state()
@ -119,3 +129,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def device_class(self):
"""Return the class of this sensor."""
return self._device_class
@property
def force_update(self):
"""Force update."""
return self._force_update

View File

@ -131,10 +131,8 @@ class NetatmoBinarySensor(BinarySensorDevice):
self._name += ' / ' + module_name
self._sensor_name = sensor
self._name += ' ' + sensor
camera_id = data.camera_data.cameraByName(
self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(
self._name, camera_id)
self._cameratype = camera_type
self._state = None

View File

@ -97,7 +97,7 @@ class PilightBinarySensor(BinarySensorDevice):
def _handle_code(self, call):
"""Handle received code by the pilight-daemon.
If the code matches the defined playload
If the code matches the defined payload
of this sensor the sensor state is changed accordingly.
"""
# Check if received code matches defined playoad
@ -162,10 +162,10 @@ class PilightTriggerSensor(BinarySensorDevice):
def _handle_code(self, call):
"""Handle received code by the pilight-daemon.
If the code matches the defined playload
If the code matches the defined payload
of this sensor the sensor state is changed accordingly.
"""
# Check if received code matches defined playoad
# Check if received code matches defined payload
# True if payload is contained in received code dict
payload_ok = True
for key in self._payload:

View File

@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_type))
else:
# create an sensor for each zone managed by faucet
# create a sensor for each zone managed by faucet
for zone in raincloud.controller.faucet.zones:
sensors.append(RainCloudBinarySensor(zone, sensor_type))

View File

@ -15,6 +15,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA)
from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE,
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv
@ -29,6 +30,8 @@ CONF_DELAY_OFF = 'delay_off'
SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template,
vol.Optional(CONF_ICON_TEMPLATE): cv.template,
vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template,
vol.Optional(ATTR_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
@ -38,11 +41,6 @@ SENSOR_SCHEMA = vol.Schema({
vol.All(cv.time_period, cv.positive_timedelta),
})
SENSOR_SCHEMA = vol.All(
cv.deprecated(ATTR_ENTITY_ID),
SENSOR_SCHEMA,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}),
})
@ -55,6 +53,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE]
icon_template = device_config.get(CONF_ICON_TEMPLATE)
entity_picture_template = device_config.get(
CONF_ENTITY_PICTURE_TEMPLATE)
entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device)
@ -65,10 +66,17 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if value_template is not None:
value_template.hass = hass
if icon_template is not None:
icon_template.hass = hass
if entity_picture_template is not None:
entity_picture_template.hass = hass
sensors.append(
BinarySensorTemplate(
hass, device, friendly_name, device_class, value_template,
entity_ids, delay_on, delay_off)
icon_template, entity_picture_template, entity_ids,
delay_on, delay_off)
)
if not sensors:
_LOGGER.error("No sensors added")
@ -82,7 +90,8 @@ class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary sensor that triggers from another sensor."""
def __init__(self, hass, device, friendly_name, device_class,
value_template, entity_ids, delay_on, delay_off):
value_template, icon_template, entity_picture_template,
entity_ids, delay_on, delay_off):
"""Initialize the Template binary sensor."""
self.hass = hass
self.entity_id = async_generate_entity_id(
@ -91,6 +100,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self._device_class = device_class
self._template = value_template
self._state = None
self._icon_template = icon_template
self._entity_picture_template = entity_picture_template
self._icon = None
self._entity_picture = None
self._entities = entity_ids
self._delay_on = delay_on
self._delay_off = delay_off
@ -119,6 +132,16 @@ class BinarySensorTemplate(BinarySensorDevice):
"""Return the name of the sensor."""
return self._name
@property
def icon(self):
"""Return the icon to use in the frontend, if any."""
return self._icon
@property
def entity_picture(self):
"""Return the entity_picture to use in the frontend, if any."""
return self._entity_picture
@property
def is_on(self):
"""Return true if sensor is on."""
@ -137,8 +160,9 @@ class BinarySensorTemplate(BinarySensorDevice):
@callback
def _async_render(self):
"""Get the state of template."""
state = None
try:
return self._template.async_render().lower() == 'true'
state = (self._template.async_render().lower() == 'true')
except TemplateError as ex:
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
@ -148,6 +172,29 @@ class BinarySensorTemplate(BinarySensorDevice):
return
_LOGGER.error("Could not render template %s: %s", self._name, ex)
for property_name, template in (
('_icon', self._icon_template),
('_entity_picture', self._entity_picture_template)):
if template is None:
continue
try:
setattr(self, property_name, template.async_render())
except TemplateError as ex:
friendly_property_name = property_name[1:].replace('_', ' ')
if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"):
# Common during HA startup - so just a warning
_LOGGER.warning('Could not render %s template %s,'
' the state is unknown.',
friendly_property_name, self._name)
else:
_LOGGER.error('Could not render %s template %s: %s',
friendly_property_name, self._name, ex)
return state
return state
@callback
def async_check_state(self):
"""Update the state from the template."""

View File

@ -126,11 +126,12 @@ class ThresholdSensor(BinarySensorDevice):
@property
def threshold_type(self):
"""Return the type of threshold this sensor represents."""
if self._threshold_lower and self._threshold_upper:
if self._threshold_lower is not None and \
self._threshold_upper is not None:
return TYPE_RANGE
elif self._threshold_lower:
elif self._threshold_lower is not None:
return TYPE_LOWER
elif self._threshold_upper:
elif self._threshold_upper is not None:
return TYPE_UPPER
@property

View File

@ -58,11 +58,11 @@ class WemoBinarySensor(BinarySensorDevice):
@property
def unique_id(self):
"""Return the id of this WeMo device."""
return '{}.{}'.format(self.__class__, self.wemo.serialnumber)
return self.wemo.serialnumber
@property
def name(self):
"""Return the name of the sevice if any."""
"""Return the name of the service if any."""
return self.wemo.name
@property

View File

@ -32,7 +32,7 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
if discovery_info is None:
return
from bellows.zigbee.zcl.clusters.security import IasZone
from zigpy.zcl.clusters.security import IasZone
in_clusters = discovery_info['in_clusters']
@ -63,7 +63,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
"""Initialize the ZHA binary sensor."""
super().__init__(**kwargs)
self._device_class = device_class
from bellows.zigbee.zcl.clusters.security import IasZone
from zigpy.zcl.clusters.security import IasZone
self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id]
@property
@ -78,7 +78,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice):
"""Return the class of this device, from component DEVICE_CLASSES."""
return self._device_class
def cluster_command(self, aps_frame, tsn, command_id, args):
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
if command_id == 0:
self._state = args[0] & 3

View File

@ -174,7 +174,7 @@ class WebDavCalendarData(object):
@staticmethod
def is_matching(vevent, search):
"""Return if the event matches the filter critera."""
"""Return if the event matches the filter criteria."""
if search is None:
return True

View File

@ -411,7 +411,7 @@ class TodoistProjectData(object):
The "best" event is determined by the following criteria:
* A proposed event must not be completed
* A proposed event must have a end date (otherwise we go with
* A proposed event must have an end date (otherwise we go with
the event at index 0, selected above)
* A proposed event must be on the same day or earlier as our
current event

View File

@ -91,13 +91,13 @@ def async_snapshot(hass, filename, entity_id=None):
@bind_hass
@asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10):
"""Fetch a image from a camera entity."""
"""Fetch an image from a camera entity."""
websession = async_get_clientsession(hass)
state = hass.states.get(entity_id)
if state is None:
raise HomeAssistantError(
"No entity '{0}' for grab a image".format(entity_id))
"No entity '{0}' for grab an image".format(entity_id))
url = "{0}{1}".format(
hass.config.api.base_url,

View File

@ -22,7 +22,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__)
def setup_platform(hass, config, add_devices, discoveryy_info=None):
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Abode camera devices."""
import abodepy.helpers.constants as CONST
import abodepy.helpers.timeline as TIMELINE

View File

@ -4,19 +4,30 @@ Support for Canary camera.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.canary/
"""
import asyncio
import logging
from datetime import timedelta
import requests
import voluptuous as vol
from homeassistant.components.camera import Camera
from homeassistant.components.camera import Camera, PLATFORM_SCHEMA
from homeassistant.components.canary import DATA_CANARY, DEFAULT_TIMEOUT
from homeassistant.components.ffmpeg import DATA_FFMPEG
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream
from homeassistant.util import Throttle
DEPENDENCIES = ['canary']
CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments'
DEPENDENCIES = ['canary', 'ffmpeg']
_LOGGER = logging.getLogger(__name__)
ATTR_MOTION_START_TIME = "motion_start_time"
ATTR_MOTION_END_TIME = "motion_end_time"
MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None):
@ -25,10 +36,11 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
devices = []
for location in data.locations:
entries = data.get_motion_entries(location.location_id)
if entries:
devices.append(CanaryCamera(data, location.location_id,
DEFAULT_TIMEOUT))
for device in location.devices:
if device.is_online:
devices.append(
CanaryCamera(hass, data, location, device, DEFAULT_TIMEOUT,
config.get(CONF_FFMPEG_ARGUMENTS)))
add_devices(devices, True)
@ -36,60 +48,65 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class CanaryCamera(Camera):
"""An implementation of a Canary security camera."""
def __init__(self, data, location_id, timeout):
def __init__(self, hass, data, location, device, timeout, ffmpeg_args):
"""Initialize a Canary security camera."""
super().__init__()
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = ffmpeg_args
self._data = data
self._location_id = location_id
self._location = location
self._device = device
self._timeout = timeout
self._location = None
self._motion_entry = None
self._image_content = None
def camera_image(self):
"""Update the status of the camera and return bytes of camera image."""
self.update()
return self._image_content
self._live_stream_session = None
@property
def name(self):
"""Return the name of this device."""
return self._location.name
return self._device.name
@property
def is_recording(self):
"""Return true if the device is recording."""
return self._location.is_recording
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
if self._motion_entry is None:
return None
return {
ATTR_MOTION_START_TIME: self._motion_entry.start_time,
ATTR_MOTION_END_TIME: self._motion_entry.end_time,
}
def update(self):
"""Update the status of the camera."""
self._data.update()
self._location = self._data.get_location(self._location_id)
entries = self._data.get_motion_entries(self._location_id)
if entries:
current = entries[0]
previous = self._motion_entry
if previous is None or previous.entry_id != current.entry_id:
self._motion_entry = current
self._image_content = requests.get(
current.thumbnails[0].image_url,
timeout=self._timeout).content
@property
def motion_detection_enabled(self):
"""Return the camera motion detection status."""
return not self._location.is_recording
@asyncio.coroutine
def async_camera_image(self):
"""Return a still image response from the camera."""
self.renew_live_stream_session()
from haffmpeg import ImageFrame, IMAGE_JPEG
ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop)
image = yield from asyncio.shield(ffmpeg.get_image(
self._live_stream_session.live_stream_url,
output_format=IMAGE_JPEG,
extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop)
return image
@asyncio.coroutine
def handle_async_mjpeg_stream(self, request):
"""Generate an HTTP MJPEG stream from the camera."""
if self._live_stream_session is None:
return
from haffmpeg import CameraMjpeg
stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop)
yield from stream.open_camera(
self._live_stream_session.live_stream_url,
extra_cmd=self._ffmpeg_arguments)
yield from async_aiohttp_proxy_stream(
self.hass, request, stream,
'multipart/x-mixed-replace;boundary=ffserver')
yield from stream.close()
@Throttle(MIN_TIME_BETWEEN_SESSION_RENEW)
def renew_live_stream_session(self):
"""Renew live stream session."""
self._live_stream_session = self._data.get_live_stream_session(
self._device)

View File

@ -64,13 +64,11 @@ class NetatmoCamera(Camera):
self._name = home + ' / ' + camera_name
else:
self._name = camera_name
camera_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._unique_id = "Welcome_camera {0} - {1}".format(
self._name, camera_id)
self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
camera=camera_name
)
self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._cameratype = camera_type
def camera_image(self):
@ -117,5 +115,5 @@ class NetatmoCamera(Camera):
@property
def unique_id(self):
"""Return the unique ID for this sensor."""
"""Return the unique ID for this camera."""
return self._unique_id

View File

@ -28,7 +28,7 @@ CONF_VERTICAL_FLIP = 'vertical_flip'
DEFAULT_HORIZONTAL_FLIP = 0
DEFAULT_IMAGE_HEIGHT = 480
DEFAULT_IMAGE_QUALITIY = 7
DEFAULT_IMAGE_QUALITY = 7
DEFAULT_IMAGE_ROTATION = 0
DEFAULT_IMAGE_WIDTH = 640
DEFAULT_NAME = 'Raspberry Pi Camera'
@ -41,7 +41,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.All(vol.Coerce(int), vol.Range(min=0, max=1)),
vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT):
vol.Coerce(int),
vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITIY):
vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY):
vol.All(vol.Coerce(int), vol.Range(min=0, max=100)),
vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION):
vol.All(vol.Coerce(int), vol.Range(min=0, max=359)),
@ -131,7 +131,7 @@ class RaspberryCamera(Camera):
stderr=subprocess.STDOUT)
def camera_image(self):
"""Return raspstill image response."""
"""Return raspistill image response."""
with open(self._config[CONF_FILE_PATH], 'rb') as file:
return file.read()

View File

@ -127,6 +127,9 @@ class UnifiVideoCamera(Camera):
else:
client_cls = uvc_camera.UVCCameraClient
if caminfo['username'] is None:
caminfo['username'] = 'ubnt'
camera = None
for addr in addrs:
try:

26
homeassistant/components/camera/xeoma.py Executable file → Normal file
View File

@ -14,7 +14,7 @@ from homeassistant.const import (
CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pyxeoma==1.2']
REQUIREMENTS = ['pyxeoma==1.3']
_LOGGER = logging.getLogger(__name__)
@ -22,6 +22,8 @@ CONF_CAMERAS = 'cameras'
CONF_HIDE = 'hide'
CONF_IMAGE_NAME = 'image_name'
CONF_NEW_VERSION = 'new_version'
CONF_VIEWER_PASSWORD = 'viewer_password'
CONF_VIEWER_USERNAME = 'viewer_username'
CAMERAS_SCHEMA = vol.Schema({
vol.Required(CONF_IMAGE_NAME): cv.string,
@ -48,9 +50,8 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
host = config[CONF_HOST]
login = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
new_version = config[CONF_NEW_VERSION]
xeoma = Xeoma(host, new_version, login, password)
xeoma = Xeoma(host, login, password)
try:
yield from xeoma.async_test_connection()
@ -59,9 +60,12 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
{
CONF_IMAGE_NAME: image_name,
CONF_HIDE: False,
CONF_NAME: image_name
CONF_NAME: image_name,
CONF_VIEWER_USERNAME: username,
CONF_VIEWER_PASSWORD: pw
}
for image_name in discovered_image_names
for image_name, username, pw in discovered_image_names
]
for cam in config[CONF_CAMERAS]:
@ -77,8 +81,9 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras))
async_add_devices(
[XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME])
for camera in cameras])
[XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME],
camera[CONF_VIEWER_USERNAME],
camera[CONF_VIEWER_PASSWORD]) for camera in cameras])
except XeomaError as err:
_LOGGER.error("Error: %s", err.message)
return
@ -87,12 +92,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class XeomaCamera(Camera):
"""Implementation of a Xeoma camera."""
def __init__(self, xeoma, image, name):
def __init__(self, xeoma, image, name, username, password):
"""Initialize a Xeoma camera."""
super().__init__()
self._xeoma = xeoma
self._name = name
self._image = image
self._username = username
self._password = password
self._last_image = None
@asyncio.coroutine
@ -100,7 +107,8 @@ class XeomaCamera(Camera):
"""Return a still image response from the camera."""
from pyxeoma.xeoma import XeomaError
try:
image = yield from self._xeoma.async_get_camera_image(self._image)
image = yield from self._xeoma.async_get_camera_image(
self._image, self._username, self._password)
self._last_image = image
except XeomaError as err:
_LOGGER.error("Error fetching image: %s", err.message)

View File

@ -15,7 +15,7 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT
from homeassistant.helpers import discovery
from homeassistant.util import Throttle
REQUIREMENTS = ['py-canary==0.2.3']
REQUIREMENTS = ['py-canary==0.4.0']
_LOGGER = logging.getLogger(__name__)
@ -111,7 +111,18 @@ class CanaryData(object):
"""Return a list of readings based on device_id."""
return self._readings_by_device_id.get(device_id, [])
def get_reading(self, device_id, sensor_type):
"""Return reading for device_id and sensor type."""
readings = self._readings_by_device_id.get(device_id, [])
return next((
reading.value for reading in readings
if reading.sensor_type == sensor_type), None)
def set_location_mode(self, location_id, mode_name, is_private=False):
"""Set location mode."""
self._api.set_location_mode(location_id, mode_name, is_private)
self.update(no_throttle=True)
def get_live_stream_session(self, device):
"""Return live stream session."""
return self._api.get_live_stream_session(device)

View File

@ -183,11 +183,6 @@ class DaikinClimate(ClimateDevice):
self._force_refresh = True
self._api.device.set(values)
@property
def unique_id(self):
"""Return the ID of this AC."""
return "{}.{}".format(self.__class__, self._api.ip_address)
@property
def supported_features(self):
"""Return the list of supported features."""

View File

@ -14,23 +14,19 @@ from homeassistant.components.climate import (
SUPPORT_ON_OFF)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AWAY_MODE | SUPPORT_HOLD_MODE | SUPPORT_FAN_MODE |
SUPPORT_OPERATION_MODE | SUPPORT_AUX_HEAT |
SUPPORT_SWING_MODE | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_ON_OFF)
SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo climate devices."""
add_devices([
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77,
'Auto Low', None, None, 'Auto', 'heat', None, None, None),
None, None, None, None, 'heat', None, None,
None, True),
DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
67, 54, 'Off', 'cool', False, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, None, 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21)
67, 54, 'Off', 'cool', False, None, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21, None)
])
@ -40,9 +36,37 @@ class DemoClimate(ClimateDevice):
def __init__(self, name, target_temperature, unit_of_measurement,
away, hold, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_mode,
current_operation, aux, target_temp_high, target_temp_low):
current_operation, aux, target_temp_high, target_temp_low,
is_on):
"""Initialize the climate device."""
self._name = name
self._support_flags = SUPPORT_FLAGS
if target_temperature is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE
if away is not None:
self._support_flags = self._support_flags | SUPPORT_AWAY_MODE
if hold is not None:
self._support_flags = self._support_flags | SUPPORT_HOLD_MODE
if current_fan_mode is not None:
self._support_flags = self._support_flags | SUPPORT_FAN_MODE
if target_humidity is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_HUMIDITY
if current_swing_mode is not None:
self._support_flags = self._support_flags | SUPPORT_SWING_MODE
if current_operation is not None:
self._support_flags = self._support_flags | SUPPORT_OPERATION_MODE
if aux is not None:
self._support_flags = self._support_flags | SUPPORT_AUX_HEAT
if target_temp_high is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH
if target_temp_low is not None:
self._support_flags = \
self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW
if is_on is not None:
self._support_flags = self._support_flags | SUPPORT_ON_OFF
self._target_temperature = target_temperature
self._target_humidity = target_humidity
self._unit_of_measurement = unit_of_measurement
@ -59,12 +83,12 @@ class DemoClimate(ClimateDevice):
self._swing_list = ['Auto', '1', '2', '3', 'Off']
self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low
self._on = True
self._on = is_on
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
return self._support_flags
@property
def should_poll(self):
@ -207,7 +231,7 @@ class DemoClimate(ClimateDevice):
self.schedule_update_ha_state()
def turn_aux_heat_on(self):
"""Turn auxillary heater on."""
"""Turn auxiliary heater on."""
self._aux = True
self.schedule_update_ha_state()

View File

@ -13,7 +13,9 @@ from homeassistant.components.climate import (
DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE,
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH)
SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH,
SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH,
SUPPORT_TARGET_TEMPERATURE_LOW)
from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
@ -46,7 +48,9 @@ RESUME_PROGRAM_SCHEMA = vol.Schema({
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH)
SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH |
SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH |
SUPPORT_TARGET_TEMPERATURE_LOW)
def setup_platform(hass, config, add_devices, discovery_info=None):

View File

@ -18,7 +18,7 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pyeconet==0.0.4']
REQUIREMENTS = ['pyeconet==0.0.5']
_LOGGER = logging.getLogger(__name__)

View File

@ -55,7 +55,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
# pylint: disable=import-error
class EQ3BTSmartThermostat(ClimateDevice):
"""Representation of a eQ-3 Bluetooth Smart thermostat."""
"""Representation of an eQ-3 Bluetooth Smart thermostat."""
def __init__(self, _mac, _name):
"""Initialize the thermostat."""

View File

@ -156,7 +156,7 @@ class GenericThermostat(ClimateDevice):
# If we have no initial temperature, restore
if self._target_temp is None:
# If we have a previously saved temperature
if old_state.attributes[ATTR_TEMPERATURE] is None:
if old_state.attributes.get(ATTR_TEMPERATURE) is None:
if self.ac_mode:
self._target_temp = self.max_temp
else:
@ -166,15 +166,15 @@ class GenericThermostat(ClimateDevice):
else:
self._target_temp = float(
old_state.attributes[ATTR_TEMPERATURE])
if old_state.attributes[ATTR_AWAY_MODE] is not None:
if old_state.attributes.get(ATTR_AWAY_MODE) is not None:
self._is_away = str(
old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON
if (self._initial_operation_mode is None and
old_state.attributes[ATTR_OPERATION_MODE] is not None):
self._current_operation = \
old_state.attributes[ATTR_OPERATION_MODE]
if self._current_operation != STATE_OFF:
self._enabled = True
self._enabled = self._current_operation != STATE_OFF
else:
# No previous state, try and restore defaults
if self._target_temp is None:
@ -249,7 +249,7 @@ class GenericThermostat(ClimateDevice):
else:
_LOGGER.error("Unrecognized operation mode: %s", operation_mode)
return
# Ensure we updae the current operation after changing the mode
# Ensure we update the current operation after changing the mode
self.schedule_update_ha_state()
@asyncio.coroutine

View File

@ -46,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
serport = connection.connection(ipaddress, port)
serport.open()
for thermostat, tstat in tstats.items():
for tstat in tstats.values():
add_devices([
HeatmiserV3Thermostat(
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport)

View File

@ -174,5 +174,5 @@ class HiveClimateEntity(ClimateDevice):
entity.handle_update(self.data_updatesource)
def update(self):
"""Update all Node data frome Hive."""
"""Update all Node data from Hive."""
self.session.core.update_data(self.node_id)

View File

@ -0,0 +1,259 @@
"""
Support for Melissa Climate A/C.
For more details about this platform, please refer to the documentation
https://home-assistant.io/components/climate.melissa/
"""
import logging
from homeassistant.components.climate import (
ClimateDevice, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_ON_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY,
STATE_FAN_ONLY, SUPPORT_FAN_MODE
)
from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH
from homeassistant.components.melissa import DATA_MELISSA
from homeassistant.const import (
TEMP_CELSIUS, STATE_ON, STATE_OFF, STATE_IDLE, ATTR_TEMPERATURE,
PRECISION_WHOLE
)
DEPENDENCIES = ['melissa']
_LOGGER = logging.getLogger(__name__)
SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE |
SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE)
OP_MODES = [
STATE_AUTO, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT
]
FAN_MODES = [
STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM
]
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Iterate through and add all Melissa devices."""
api = hass.data[DATA_MELISSA]
devices = api.fetch_devices().values()
all_devices = []
for device in devices:
all_devices.append(MelissaClimate(
api, device['serial_number'], device))
add_devices(all_devices)
class MelissaClimate(ClimateDevice):
"""Representation of a Melissa Climate device."""
def __init__(self, api, serial_number, init_data):
"""Initialize the climate device."""
self._name = init_data['name']
self._api = api
self._serial_number = serial_number
self._data = init_data['controller_log']
self._state = None
self._cur_settings = None
@property
def name(self):
"""Return the name of the thermostat, if any."""
return self._name
@property
def is_on(self):
"""Return current state."""
if self._cur_settings is not None:
return self._cur_settings[self._api.STATE] in (
self._api.STATE_ON, self._api.STATE_IDLE)
return None
@property
def current_fan_mode(self):
"""Return the current fan mode."""
if self._cur_settings is not None:
return self.melissa_fan_to_hass(
self._cur_settings[self._api.FAN])
@property
def current_temperature(self):
"""Return the current temperature."""
if self._data:
return self._data[self._api.TEMP]
@property
def target_temperature_step(self):
"""Return the supported step of target temperature."""
return PRECISION_WHOLE
@property
def current_operation(self):
"""Return the current operation mode."""
if self._cur_settings is not None:
return self.melissa_op_to_hass(
self._cur_settings[self._api.MODE])
@property
def operation_list(self):
"""Return the list of available operation modes."""
return OP_MODES
@property
def fan_list(self):
"""List of available fan modes."""
return FAN_MODES
@property
def target_temperature(self):
"""Return the temperature we try to reach."""
if self._cur_settings is not None:
return self._cur_settings[self._api.TEMP]
@property
def state(self):
"""Return current state."""
if self._cur_settings is not None:
return self.melissa_state_to_hass(
self._cur_settings[self._api.STATE])
@property
def temperature_unit(self):
"""Return the unit of measurement which this thermostat uses."""
return TEMP_CELSIUS
@property
def min_temp(self):
"""Return the minimum supported temperature for the thermostat."""
return 16
@property
def max_temp(self):
"""Return the maximum supported temperature for the thermostat."""
return 30
@property
def supported_features(self):
"""Return the list of supported features."""
return SUPPORT_FLAGS
def set_temperature(self, **kwargs):
"""Set new target temperature."""
temp = kwargs.get(ATTR_TEMPERATURE)
self.send({self._api.TEMP: temp})
def set_fan_mode(self, fan):
"""Set fan mode."""
fan_mode = self.hass_fan_to_melissa(fan)
self.send({self._api.FAN: fan_mode})
def set_operation_mode(self, operation_mode):
"""Set operation mode."""
mode = self.hass_mode_to_melissa(operation_mode)
self.send({self._api.MODE: mode})
def turn_on(self):
"""Turn on device."""
self.send({self._api.STATE: self._api.STATE_ON})
def turn_off(self):
"""Turn off device."""
self.send({self._api.STATE: self._api.STATE_OFF})
def send(self, value):
"""Sending action to service."""
try:
old_value = self._cur_settings.copy()
self._cur_settings.update(value)
except AttributeError:
old_value = None
if not self._api.send(self._serial_number, self._cur_settings):
self._cur_settings = old_value
return False
else:
return True
def update(self):
"""Get latest data from Melissa."""
try:
self._data = self._api.status(cached=True)[self._serial_number]
self._cur_settings = self._api.cur_settings(
self._serial_number
)['controller']['_relation']['command_log']
except KeyError:
_LOGGER.warning(
'Unable to update entity %s', self.entity_id)
def melissa_state_to_hass(self, state):
"""Translate Melissa states to hass states."""
if state == self._api.STATE_ON:
return STATE_ON
elif state == self._api.STATE_OFF:
return STATE_OFF
elif state == self._api.STATE_IDLE:
return STATE_IDLE
else:
return None
def melissa_op_to_hass(self, mode):
"""Translate Melissa modes to hass states."""
if mode == self._api.MODE_AUTO:
return STATE_AUTO
elif mode == self._api.MODE_HEAT:
return STATE_HEAT
elif mode == self._api.MODE_COOL:
return STATE_COOL
elif mode == self._api.MODE_DRY:
return STATE_DRY
elif mode == self._api.MODE_FAN:
return STATE_FAN_ONLY
else:
_LOGGER.warning(
"Operation mode %s could not be mapped to hass", mode)
return None
def melissa_fan_to_hass(self, fan):
"""Translate Melissa fan modes to hass modes."""
if fan == self._api.FAN_AUTO:
return STATE_AUTO
elif fan == self._api.FAN_LOW:
return SPEED_LOW
elif fan == self._api.FAN_MEDIUM:
return SPEED_MEDIUM
elif fan == self._api.FAN_HIGH:
return SPEED_HIGH
else:
_LOGGER.warning("Fan mode %s could not be mapped to hass", fan)
return None
def hass_mode_to_melissa(self, mode):
"""Translate hass states to melissa modes."""
if mode == STATE_AUTO:
return self._api.MODE_AUTO
elif mode == STATE_HEAT:
return self._api.MODE_HEAT
elif mode == STATE_COOL:
return self._api.MODE_COOL
elif mode == STATE_DRY:
return self._api.MODE_DRY
elif mode == STATE_FAN_ONLY:
return self._api.MODE_FAN
else:
_LOGGER.warning("Melissa have no setting for %s mode", mode)
def hass_fan_to_melissa(self, fan):
"""Translate hass fan modes to melissa modes."""
if fan == STATE_AUTO:
return self._api.FAN_AUTO
elif fan == SPEED_LOW:
return self._api.FAN_LOW
elif fan == SPEED_MEDIUM:
return self._api.FAN_MEDIUM
elif fan == SPEED_HIGH:
return self._api.FAN_HIGH
else:
_LOGGER.warning("Melissa have no setting for %s fan mode", fan)

View File

@ -565,7 +565,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@asyncio.coroutine
def async_turn_aux_heat_on(self):
"""Turn auxillary heater on."""
"""Turn auxiliary heater on."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain)
@ -576,7 +576,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@asyncio.coroutine
def async_turn_aux_heat_off(self):
"""Turn auxillary heater off."""
"""Turn auxiliary heater off."""
if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain)

View File

@ -139,8 +139,7 @@ class MySensorsHVAC(mysensors.MySensorsEntity, ClimateDevice):
self.gateway.set_child_value(
self.node_id, self.child_id, value_type, value)
if self.gateway.optimistic:
# O
# ptimistically assume that device has changed state
# Optimistically assume that device has changed state
self._values[value_type] = value
self.schedule_update_ha_state()

View File

@ -97,6 +97,11 @@ class NestThermostat(ClimateDevice):
"""Return the list of supported features."""
return SUPPORT_FLAGS
@property
def unique_id(self):
"""Unique ID for this device."""
return self.device.serial
@property
def name(self):
"""Return the name of the nest, if any."""

View File

@ -24,7 +24,7 @@ CONF_RELAY = 'relay'
CONF_THERMOSTAT = 'thermostat'
DEFAULT_AWAY_TEMPERATURE = 14
# # The default offeset is 2 hours (when you use the thermostat itself)
# # The default offset is 2 hours (when you use the thermostat itself)
DEFAULT_TIME_OFFSET = 7200
# # Return cached results if last scan was less then this time ago
# # NetAtmo Data is uploaded to server every hour

View File

@ -59,7 +59,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ThermostatDevice(ClimateDevice):
"""Interface class for the oemthermostat modul."""
"""Interface class for the oemthermostat module."""
def __init__(self, hass, thermostat, name, away_temp):
"""Initialize the device."""

View File

@ -249,7 +249,7 @@ class TadoClimate(ClimateDevice):
data = self._store.get_data(self._data_id)
if data is None:
_LOGGER.debug("Recieved no data for zone %s", self.zone_name)
_LOGGER.debug("Received no data for zone %s", self.zone_name)
return
if 'sensorDataPoints' in data:
@ -294,7 +294,7 @@ class TadoClimate(ClimateDevice):
overlay = False
overlay_data = None
termination = self._current_operation
termination = CONST_MODE_SMART_SCHEDULE
cooling = False
fan_speed = CONST_MODE_OFF
@ -317,7 +317,7 @@ class TadoClimate(ClimateDevice):
fan_speed = setting_data['fanSpeed']
if self._device_is_active:
# If you set mode manualy to off, there will be an overlay
# If you set mode manually to off, there will be an overlay
# and a termination, but we want to see the mode "OFF"
self._overlay_mode = termination
self._current_operation = termination

View File

@ -13,7 +13,7 @@ from homeassistant.components.climate import (
from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pytouchline==0.6']
REQUIREMENTS = ['pytouchline==0.7']
_LOGGER = logging.getLogger(__name__)

View File

@ -20,7 +20,7 @@ from homeassistant.const import (
TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['venstarcolortouch==0.5']
REQUIREMENTS = ['venstarcolortouch==0.6']
_LOGGER = logging.getLogger(__name__)

View File

@ -16,8 +16,7 @@ import voluptuous as vol
from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE)
from homeassistant.helpers import entityfilter
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh
@ -105,12 +104,7 @@ def async_setup(hass, config):
)
cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
success = yield from cloud.initialize()
if not success:
return False
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
yield from http_api.async_setup(hass)
return True
@ -192,19 +186,6 @@ class Cloud:
return self._gactions_config
@asyncio.coroutine
def initialize(self):
"""Initialize and load cloud info."""
jwt_success = yield from self._fetch_jwt_keyset()
if not jwt_success:
return False
self.hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_START, self._start_cloud)
return True
def path(self, *parts):
"""Get config path inside cloud dir.
@ -234,19 +215,34 @@ class Cloud:
'refresh_token': self.refresh_token,
}, indent=4))
def _start_cloud(self, event):
@asyncio.coroutine
def async_start(self, _):
"""Start the cloud component."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
os.mkdir(path)
success = yield from self._fetch_jwt_keyset()
user_info = self.user_info_path
if not os.path.isfile(user_info):
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.async_call_later(5, self.async_start)
return
with open(user_info, 'rt') as file:
info = json.loads(file.read())
def load_config():
"""Load config."""
# Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path):
os.mkdir(path)
user_info = self.user_info_path
if not os.path.isfile(user_info):
return None
with open(user_info, 'rt') as file:
return json.loads(file.read())
info = yield from self.hass.async_add_job(load_config)
if info is None:
return
# Validate tokens
try:

View File

@ -5,16 +5,17 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/components/coinbase/
"""
from datetime import timedelta
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_API_KEY
from homeassistant.util import Throttle
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = ['coinbase==2.0.7']
REQUIREMENTS = ['coinbase==2.0.6']
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'coinbase'
@ -46,14 +47,13 @@ def setup(hass, config):
api_secret = config[DOMAIN].get(CONF_API_SECRET)
exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES)
hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(api_key,
api_secret)
hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(
api_key, api_secret)
if not hasattr(coinbase_data, 'accounts'):
return False
for account in coinbase_data.accounts.data:
load_platform(hass, 'sensor', DOMAIN,
{'account': account}, config)
load_platform(hass, 'sensor', DOMAIN, {'account': account}, config)
for currency in exchange_currencies:
if currency not in coinbase_data.exchange_rates.rates:
_LOGGER.warning("Currency %s not found", currency)

View File

@ -68,7 +68,7 @@ class HMCover(HMDevice, CoverDevice):
self._hmdevice.stop(self._channel)
def _init_data_struct(self):
"""Generate a data dictoinary (self._data) from metadata."""
"""Generate a data dictionary (self._data) from metadata."""
self._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN})
if "LEVEL_2" in self._hmdevice.WRITENODE:

View File

@ -42,7 +42,7 @@ def setup_platform(hass, config: ConfigType,
class ISYCoverDevice(ISYDevice, CoverDevice):
"""Representation of an ISY994 cover device."""
def __init__(self, node: object):
def __init__(self, node: object) -> None:
"""Initialize the ISY994 cover device."""
super().__init__(node)

View File

@ -74,7 +74,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@callback
def async_add_devices_config(hass, config, async_add_devices):
"""Set up cover for KNX platform configured within plattform."""
"""Set up cover for KNX platform configured within platform."""
import xknx
cover = xknx.devices.Cover(
hass.data[DATA_KNX].xknx,

View File

@ -63,7 +63,7 @@ class LutronCover(LutronDevice, CoverDevice):
def update(self):
"""Call when forcing a refresh of the device."""
# Reading the property (rather than last_level()) fetchs value
# Reading the property (rather than last_level()) fetches value
level = self._lutron_device.level
_LOGGER.debug("Lutron ID: %d updated to %f",
self._lutron_device.id, level)

View File

@ -214,16 +214,6 @@ class MqttCover(MqttAvailability, CoverDevice):
self.async_schedule_update_ha_state()
@callback
def availability_message_received(topic, payload, qos):
"""Handle new MQTT availability messages."""
if payload == self._payload_available:
self._available = True
elif payload == self._payload_not_available:
self._available = False
self.async_schedule_update_ha_state()
if self._state_topic is None:
# Force into optimistic mode.
self._optimistic = True
@ -232,11 +222,6 @@ class MqttCover(MqttAvailability, CoverDevice):
self.hass, self._state_topic,
state_message_received, self._qos)
if self._availability_topic is not None:
yield from mqtt.async_subscribe(
self.hass, self._availability_topic,
availability_message_received, self._qos)
if self._tilt_status_topic is None:
self._tilt_optimistic = True
else:

View File

@ -89,11 +89,6 @@ class RPiGPIOCover(CoverDevice):
rpi_gpio.setup_input(self._state_pin, self._state_pull_mode)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay)
@property
def unique_id(self):
"""Return the ID of this cover."""
return '{}.{}'.format(self.__class__, self._name)
@property
def name(self):
"""Return the name of the cover if any."""

View File

@ -51,8 +51,8 @@ set_cover_tilt_position:
entity_id:
description: Name(s) of cover(s) to set cover tilt position.
example: 'cover.living_room'
position:
description: Position of the cover (0 to 100).
tilt_position:
description: Tilt position of the cover (0 to 100).
example: 30
stop_cover_tilt:

View File

@ -5,7 +5,6 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/cover.tahoma/
"""
import logging
from datetime import timedelta
from homeassistant.components.cover import CoverDevice
from homeassistant.components.tahoma import (
@ -15,8 +14,6 @@ DEPENDENCIES = ['tahoma']
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Tahoma covers."""

View File

@ -67,11 +67,6 @@ COVER_SCHEMA = vol.Schema({
vol.Optional(CONF_ENTITY_ID): cv.entity_ids
})
COVER_SCHEMA = vol.All(
cv.deprecated(CONF_ENTITY_ID),
COVER_SCHEMA,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}),
})

View File

@ -158,7 +158,7 @@ class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase):
@property
def is_closing(self):
"""Return true if cover is in an closing state."""
"""Return true if cover is in a closing state."""
return self._state == "Closing"
@property

View File

@ -17,7 +17,7 @@ from homeassistant.helpers import discovery
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util.json import load_json, save_json
REQUIREMENTS = ['pydeconz==25']
REQUIREMENTS = ['pydeconz==27']
_LOGGER = logging.getLogger(__name__)
@ -38,7 +38,7 @@ SERVICE_DATA = 'data'
SERVICE_SCHEMA = vol.Schema({
vol.Required(SERVICE_FIELD): cv.string,
vol.Required(SERVICE_DATA): cv.string,
vol.Required(SERVICE_DATA): dict,
})
CONFIG_INSTRUCTIONS = """

View File

@ -42,7 +42,7 @@ Device = namedtuple('Device', ['mac', 'ip', 'last_update'])
class ActiontecDeviceScanner(DeviceScanner):
"""This class queries a an actiontec router for connected devices."""
"""This class queries an actiontec router for connected devices."""
def __init__(self, config):
"""Initialize the scanner."""

View File

@ -242,7 +242,7 @@ class _Connection:
return self._connected
def connect(self):
"""Mark currenct connection state as connected."""
"""Mark current connection state as connected."""
self._connected = True
def disconnect(self):

View File

@ -2,7 +2,7 @@
Support for HUAWEI routers.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.huawei/
https://home-assistant.io/components/device_tracker.huawei_router/
"""
import base64
import logging
@ -119,7 +119,7 @@ class HuaweiDeviceScanner(DeviceScanner):
cnt = requests.post('http://{}/asp/GetRandCount.asp'.format(self.host))
cnt_str = str(cnt.content, cnt.apparent_encoding, errors='replace')
_LOGGER.debug("Loggin in")
_LOGGER.debug("Logging in")
cookie = requests.post('http://{}/login.cgi'.format(self.host),
data=[('UserName', self.username),
('PassWord', self.password),

View File

@ -0,0 +1,71 @@
"""
Support for Mercedes cars with Mercedes ME.
For more details about this component, please refer to the documentation at
https://home-assistant.io/components/device_tracker.mercedesme/
"""
import logging
from datetime import timedelta
from homeassistant.components.mercedesme import DATA_MME
from homeassistant.helpers.event import track_time_interval
from homeassistant.util import Throttle
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = ['mercedesme']
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30)
def setup_scanner(hass, config, see, discovery_info=None):
"""Set up the Mercedes ME tracker."""
if discovery_info is None:
return False
data = hass.data[DATA_MME].data
if not data.cars:
return False
MercedesMEDeviceTracker(hass, config, see, data)
return True
class MercedesMEDeviceTracker(object):
"""A class representing a Mercedes ME device tracker."""
def __init__(self, hass, config, see, data):
"""Initialize the Mercedes ME device tracker."""
self.see = see
self.data = data
self.update_info()
track_time_interval(
hass, self.update_info, MIN_TIME_BETWEEN_SCANS)
@Throttle(MIN_TIME_BETWEEN_SCANS)
def update_info(self, now=None):
"""Update the device info."""
for device in self.data.cars:
_LOGGER.debug("Updating %s", device["vin"])
location = self.data.get_location(device["vin"])
if location is None:
return False
dev_id = device["vin"]
name = device["license"]
lat = location['positionLat']['value']
lon = location['positionLong']['value']
attrs = {
'trackr_id': dev_id,
'id': dev_id,
'name': name
}
self.see(
dev_id=dev_id, host_name=name,
gps=(lat, lon), attributes=attrs
)
return True

View File

@ -14,7 +14,7 @@ from homeassistant.components.device_tracker import (
from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==1.0.4']
REQUIREMENTS = ['librouteros==1.0.5']
MTK_DEFAULT_API_PORT = '8728'

View File

@ -31,17 +31,14 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}
@callback
def async_tracker_message_received(topic, payload, qos):
"""Handle received MQTT message."""
hass.async_add_job(
async_see(dev_id=dev_id_lookup[topic], location_name=payload))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
@callback
def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message."""
hass.async_add_job(
async_see(dev_id=dev_id, location_name=payload))
yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos)
hass, topic, async_message_received, qos)
return True

View File

@ -41,32 +41,26 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
devices = config[CONF_DEVICES]
qos = config[CONF_QOS]
dev_id_lookup = {}
@callback
def async_tracker_message_received(topic, payload, qos):
"""Handle received MQTT message."""
dev_id = dev_id_lookup[topic]
try:
data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload))
except vol.MultipleInvalid:
_LOGGER.error("Skipping update for following data "
"because of missing or malformatted data: %s",
payload)
return
except ValueError:
_LOGGER.error("Error parsing JSON payload: %s", payload)
return
kwargs = _parse_see_args(dev_id, data)
hass.async_add_job(
async_see(**kwargs))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
@callback
def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message."""
try:
data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload))
except vol.MultipleInvalid:
_LOGGER.error("Skipping update for following data "
"because of missing or malformatted data: %s",
payload)
return
except ValueError:
_LOGGER.error("Error parsing JSON payload: %s", payload)
return
kwargs = _parse_see_args(dev_id, data)
hass.async_add_job(async_see(**kwargs))
yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos)
hass, topic, async_message_received, qos)
return True

View File

@ -143,6 +143,8 @@ def _parse_see_args(message, subscribe_topic):
kwargs['attributes']['tid'] = message['tid']
if 'addr' in message:
kwargs['attributes']['address'] = message['addr']
if 'cog' in message:
kwargs['attributes']['course'] = message['cog']
if 't' in message:
if message['t'] == 'c':
kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_GPS

View File

@ -46,8 +46,8 @@ def get_scanner(hass, config):
return scanner if scanner.success_init else None
def _refresh_on_acccess_denied(func):
"""If remove rebooted, it lost our session so rebuld one and try again."""
def _refresh_on_access_denied(func):
"""If remove rebooted, it lost our session so rebuild one and try again."""
def decorator(self, *args, **kwargs):
"""Wrap the function to refresh session_id on PermissionError."""
try:
@ -95,16 +95,15 @@ class UbusDeviceScanner(DeviceScanner):
"""Must be implemented depending on the software."""
raise NotImplementedError
@_refresh_on_acccess_denied
@_refresh_on_access_denied
def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know."""
if self.mac2name is None:
self._generate_mac2name()
name = self.mac2name.get(mac.upper(), None)
self.mac2name = None
return name
@_refresh_on_acccess_denied
@_refresh_on_access_denied
def _update_info(self):
"""Ensure the information from the router is up to date.
@ -122,13 +121,18 @@ class UbusDeviceScanner(DeviceScanner):
self.last_results = []
results = 0
# for each access point
for hostapd in self.hostapd:
result = _req_json_rpc(
self.url, self.session_id, 'call', hostapd, 'get_clients')
if result:
results = results + 1
self.last_results.extend(result['clients'].keys())
# Check for each device is authorized (valid wpa key)
for key in result['clients'].keys():
device = result['clients'][key]
if device['authorized']:
self.last_results.append(key)
return bool(results)

View File

@ -75,7 +75,7 @@ def get_scanner(hass, config):
class UnifiScanner(DeviceScanner):
"""Provide device_tracker support from Unifi WAP client data."""
def __init__(self, controller, detection_time: timedelta):
def __init__(self, controller, detection_time: timedelta) -> None:
"""Initialize the scanner."""
self._detection_time = detection_time
self._controller = controller

View File

@ -1,7 +1,7 @@
"""
Support for Dominos Pizza ordering.
The Dominos Pizza component ceates a service which can be invoked to order
The Dominos Pizza component creates a service which can be invoked to order
from their menu
For more details about this platform, please refer to the documentation at

View File

@ -73,6 +73,7 @@ def setup(hass, config):
class DoorbirdRequestView(HomeAssistantView):
"""Provide a page for the device to call."""
requires_auth = False
url = API_URL
name = API_URL[1:].replace('/', ':')
extra_urls = [API_URL + '/{sensor}']

View File

@ -79,7 +79,7 @@ def setup(hass, config):
if req.status_code != 200:
_LOGGER.warning(
"downloading '%s' failed, stauts_code=%d",
"downloading '%s' failed, status_code=%d",
url,
req.status_code)

View File

@ -193,7 +193,7 @@ class EightSleepUserEntity(Entity):
"""The Eight Sleep device entity."""
def __init__(self, eight):
"""Initialize the data oject."""
"""Initialize the data object."""
self._eight = eight
@asyncio.coroutine
@ -217,7 +217,7 @@ class EightSleepHeatEntity(Entity):
"""The Eight Sleep device entity."""
def __init__(self, eight):
"""Initialize the data oject."""
"""Initialize the data object."""
self._eight = eight
@asyncio.coroutine

View File

@ -59,7 +59,7 @@ def setup(hass, config):
payload, fullurl, req.status_code)
def update_emoncms(time):
"""Send whitelisted entities states reguarly to Emoncms."""
"""Send whitelisted entities states regularly to Emoncms."""
payload_dict = {}
for entity_id in whitelist:

View File

@ -39,6 +39,9 @@ CONF_OFF_MAPS_TO_ON_DOMAINS = 'off_maps_to_on_domains'
CONF_EXPOSE_BY_DEFAULT = 'expose_by_default'
CONF_EXPOSED_DOMAINS = 'exposed_domains'
CONF_TYPE = 'type'
CONF_ENTITIES = 'entities'
CONF_ENTITY_NAME = 'name'
CONF_ENTITY_HIDDEN = 'hidden'
TYPE_ALEXA = 'alexa'
TYPE_GOOGLE = 'google_home'
@ -52,6 +55,11 @@ DEFAULT_EXPOSED_DOMAINS = [
]
DEFAULT_TYPE = TYPE_GOOGLE
CONFIG_ENTITY_SCHEMA = vol.Schema({
vol.Optional(CONF_ENTITY_NAME): cv.string,
vol.Optional(CONF_ENTITY_HIDDEN): cv.boolean
})
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Optional(CONF_HOST_IP): cv.string,
@ -63,11 +71,14 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_EXPOSE_BY_DEFAULT): cv.boolean,
vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list,
vol.Optional(CONF_TYPE, default=DEFAULT_TYPE):
vol.Any(TYPE_ALEXA, TYPE_GOOGLE)
vol.Any(TYPE_ALEXA, TYPE_GOOGLE),
vol.Optional(CONF_ENTITIES):
vol.Schema({cv.entity_id: CONFIG_ENTITY_SCHEMA})
})
}, extra=vol.ALLOW_EXTRA)
ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden'
@ -183,6 +194,8 @@ class Config(object):
self.advertise_port = conf.get(
CONF_ADVERTISE_PORT) or self.listen_port
self.entities = conf.get(CONF_ENTITIES, {})
def entity_id_to_number(self, entity_id):
"""Get a unique number for the entity id."""
if self.type == TYPE_ALEXA:
@ -215,6 +228,14 @@ class Config(object):
assert isinstance(number, str)
return self.numbers.get(number)
def get_entity_name(self, entity):
"""Get the name of an entity."""
if entity.entity_id in self.entities and \
CONF_ENTITY_NAME in self.entities[entity.entity_id]:
return self.entities[entity.entity_id][CONF_ENTITY_NAME]
return entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name)
def is_entity_exposed(self, entity):
"""Determine if an entity should be exposed on the emulated bridge.
@ -227,6 +248,12 @@ class Config(object):
domain = entity.domain.lower()
explicit_expose = entity.attributes.get(ATTR_EMULATED_HUE, None)
explicit_hidden = entity.attributes.get(ATTR_EMULATED_HUE_HIDDEN, None)
if entity.entity_id in self.entities and \
CONF_ENTITY_HIDDEN in self.entities[entity.entity_id]:
explicit_hidden = \
self.entities[entity.entity_id][CONF_ENTITY_HIDDEN]
if explicit_expose is True or explicit_hidden is False:
expose = True
elif explicit_expose is False or explicit_hidden is True:

View File

@ -24,9 +24,6 @@ from homeassistant.components.http import HomeAssistantView
_LOGGER = logging.getLogger(__name__)
ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
HUE_API_STATE_ON = 'on'
HUE_API_STATE_BRI = 'bri'
@ -77,7 +74,7 @@ class HueAllLightsStateView(HomeAssistantView):
number = self.config.entity_id_to_number(entity.entity_id)
json_response[number] = entity_to_json(
entity, state, brightness)
self.config, entity, state, brightness)
return self.json(json_response)
@ -110,7 +107,7 @@ class HueOneLightStateView(HomeAssistantView):
state, brightness = get_entity_state(self.config, entity)
json_response = entity_to_json(entity, state, brightness)
json_response = entity_to_json(self.config, entity, state, brightness)
return self.json(json_response)
@ -344,10 +341,8 @@ def get_entity_state(config, entity):
return (final_state, final_brightness)
def entity_to_json(entity, is_on=None, brightness=None):
def entity_to_json(config, entity, is_on=None, brightness=None):
"""Convert an entity to its Hue bridge JSON representation."""
name = entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name)
return {
'state':
{
@ -356,7 +351,7 @@ def entity_to_json(entity, is_on=None, brightness=None):
'reachable': True
},
'type': 'Dimmable light',
'name': name,
'name': config.get_entity_name(entity),
'modelid': 'HASS123',
'uniqueid': entity.entity_id,
'swversion': '123'

View File

@ -205,7 +205,7 @@ def async_setup(hass, config: dict):
@asyncio.coroutine
def async_handle_fan_service(service):
"""Hande service call for fans."""
"""Handle service call for fans."""
method = SERVICE_TO_METHOD.get(service.service)
params = service.data.copy()

View File

@ -37,7 +37,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class ComfoConnectFan(FanEntity):
"""Representation of the ComfoConnect fan platform."""
def __init__(self, hass, name, ccb: ComfoConnectBridge):
def __init__(self, hass, name, ccb: ComfoConnectBridge) -> None:
"""Initialize the ComfoConnect fan."""
from pycomfoconnect import SENSOR_FAN_SPEED_MODE
@ -93,7 +93,7 @@ class ComfoConnectFan(FanEntity):
speed = SPEED_LOW
self.set_speed(speed)
def turn_off(self) -> None:
def turn_off(self, **kwargs) -> None:
"""Turn off the fan (to away)."""
self.set_speed(SPEED_OFF)

View File

@ -60,7 +60,7 @@ class InsteonLocalFanDevice(FanEntity):
@property
def unique_id(self):
"""Return the ID of this Insteon node."""
return 'insteon_local_{}_fan'.format(self.node.device_id)
return self.node.device_id
@property
def speed(self) -> str:

View File

@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
})
REQUIREMENTS = ['python-miio==0.3.4']
REQUIREMENTS = ['python-miio==0.3.5']
ATTR_TEMPERATURE = 'temperature'
ATTR_HUMIDITY = 'humidity'
@ -200,7 +200,7 @@ class XiaomiAirPurifier(FanEntity):
@asyncio.coroutine
def _try_command(self, mask_error, func, *args, **kwargs):
"""Call a air purifier command handling error messages."""
"""Call an air purifier command handling error messages."""
from miio import DeviceException
try:
result = yield from self.hass.async_add_job(

View File

@ -153,8 +153,7 @@ class StoredData(object):
with self._lock, open(self._data_file, 'rb') as myfile:
self._data = pickle.load(myfile) or {}
self._cache_outdated = False
# pylint: disable=bare-except
except:
except: # noqa: E722 # pylint: disable=bare-except
_LOGGER.error("Error loading data from pickled file %s",
self._data_file)
@ -172,8 +171,7 @@ class StoredData(object):
url, self._data_file)
try:
pickle.dump(self._data, myfile)
# pylint: disable=bare-except
except:
except: # noqa: E722 # pylint: disable=bare-except
_LOGGER.error(
"Error saving pickled data to %s", self._data_file)
self._cache_outdated = True

View File

@ -23,7 +23,7 @@ from homeassistant.const import CONF_NAME, EVENT_THEMES_UPDATED
from homeassistant.core import callback
from homeassistant.loader import bind_hass
REQUIREMENTS = ['home-assistant-frontend==20180130.0', 'user-agents==1.1.0']
REQUIREMENTS = ['home-assistant-frontend==20180209.0', 'user-agents==1.1.0']
DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
@ -408,7 +408,7 @@ def async_setup_themes(hass, themes):
@callback
def set_theme(call):
"""Set backend-prefered theme."""
"""Set backend-preferred theme."""
data = call.data
name = data[CONF_NAME]
if name == DEFAULT_THEME or name in hass.data[DATA_THEMES]:
@ -585,11 +585,13 @@ def _is_latest(js_option, request):
return useragent.os.version[0] >= 12
family_min_version = {
'Chrome': 50, # Probably can reduce this
'Firefox': 43, # Array.protopype.includes added in 43
'Opera': 40, # Probably can reduce this
'Edge': 14, # Array.protopype.includes added in 14
'Safari': 10, # many features not supported by 9
'Chrome': 54, # Object.values
'Chrome Mobile': 54,
'Firefox': 47, # Object.values
'Firefox Mobile': 47,
'Opera': 41, # Object.values
'Edge': 14, # Array.prototype.includes added in 14
'Safari': 10, # Many features not supported by 9
}
version = family_min_version.get(useragent.browser.family)
return version and useragent.browser.version[0] >= version

View File

@ -0,0 +1,62 @@
"""
Component for the Goalfeed service.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/goalfeed/
"""
import json
import requests
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
REQUIREMENTS = ['pysher==0.2.0']
DOMAIN = 'goalfeed'
CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
})
}, extra=vol.ALLOW_EXTRA)
GOALFEED_HOST = 'feed.goalfeed.ca'
GOALFEED_AUTH_ENDPOINT = 'https://goalfeed.ca/feed/auth'
GOALFEED_APP_ID = 'bfd4ed98c1ff22c04074'
def setup(hass, config):
"""Set up the Goalfeed component."""
import pysher
conf = config[DOMAIN]
username = conf.get(CONF_USERNAME)
password = conf.get(CONF_PASSWORD)
def goal_handler(data):
"""Handle goal events."""
goal = json.loads(json.loads(data))
hass.bus.fire('goal', event_data=goal)
def connect_handler(data):
"""Handle connection."""
post_data = {
'username': username,
'password': password,
'connection_info': data}
resp = requests.post(GOALFEED_AUTH_ENDPOINT, post_data,
timeout=30).json()
channel = pusher.subscribe('private-goals', resp['auth'])
channel.bind('goal', goal_handler)
pusher = pysher.Pusher(GOALFEED_APP_ID, secure=False, port=8080,
custom_host=GOALFEED_HOST)
pusher.connection.bind('pusher:connection_established', connect_handler)
pusher.connect()
return True

View File

@ -128,7 +128,7 @@ def do_authentication(hass, config):
"""Keep trying to validate the user_code until it expires."""
if now >= dt.as_local(dev_flow.user_code_expiry):
hass.components.persistent_notification.create(
'Authenication code expired, please restart '
'Authentication code expired, please restart '
'Home-Assistant and try again',
title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID)

View File

@ -37,6 +37,7 @@ from .const import (
)
HANDLERS = Registry()
QUERY_HANDLERS = Registry()
_LOGGER = logging.getLogger(__name__)
# Mapping is [actions schema, primary trait, optional features]
@ -79,7 +80,7 @@ class SmartHomeError(Exception):
"""Log error code."""
super(SmartHomeError, self).__init__(msg)
_LOGGER.error(
"An error has ocurred in Google SmartHome: %s."
"An error has occurred in Google SmartHome: %s."
"Error code: %s", msg, code
)
self.code = code
@ -96,7 +97,7 @@ class Config:
def entity_to_device(entity: Entity, config: Config, units: UnitSystem):
"""Convert a hass entity into an google actions device."""
"""Convert a hass entity into a google actions device."""
entity_config = config.entity_config.get(entity.entity_id, {})
google_domain = entity_config.get(CONF_TYPE)
class_data = MAPPING_COMPONENT.get(
@ -177,120 +178,145 @@ def entity_to_device(entity: Entity, config: Config, units: UnitSystem):
return device
def query_device(entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Take an entity and return a properly formatted device object."""
def celsius(deg: Optional[float]) -> Optional[float]:
"""Convert a float to Celsius and rounds to one decimal place."""
if deg is None:
return None
return round(METRIC_SYSTEM.temperature(deg, units.temperature_unit), 1)
def celsius(deg: Optional[float], units: UnitSystem) -> Optional[float]:
"""Convert a float to Celsius and rounds to one decimal place."""
if deg is None:
return None
return round(METRIC_SYSTEM.temperature(deg, units.temperature_unit), 1)
if entity.domain == sensor.DOMAIN:
entity_config = config.entity_config.get(entity.entity_id, {})
google_domain = entity_config.get(CONF_TYPE)
if google_domain == climate.DOMAIN:
# check if we have a string value to convert it to number
value = entity.state
if isinstance(entity.state, str):
try:
value = float(value)
except ValueError:
value = None
if value is None:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Invalid value {} for the climate sensor"
.format(entity.state)
)
# detect if we report temperature or humidity
unit_of_measurement = entity.attributes.get(
ATTR_UNIT_OF_MEASUREMENT,
units.temperature_unit
)
if unit_of_measurement in [TEMP_FAHRENHEIT, TEMP_CELSIUS]:
value = celsius(value)
attr = 'thermostatTemperatureAmbient'
elif unit_of_measurement == '%':
attr = 'thermostatHumidityAmbient'
else:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Unit {} is not supported by the climate sensor"
.format(unit_of_measurement)
)
return {attr: value}
@QUERY_HANDLERS.register(sensor.DOMAIN)
def query_response_sensor(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a sensor entity to a QUERY response."""
entity_config = config.entity_config.get(entity.entity_id, {})
google_domain = entity_config.get(CONF_TYPE)
if google_domain != climate.DOMAIN:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Sensor type {} is not supported".format(google_domain)
)
if entity.domain == climate.DOMAIN:
mode = entity.attributes.get(climate.ATTR_OPERATION_MODE).lower()
if mode not in CLIMATE_SUPPORTED_MODES:
mode = 'heat'
response = {
'thermostatMode': mode,
'thermostatTemperatureSetpoint':
celsius(entity.attributes.get(climate.ATTR_TEMPERATURE)),
'thermostatTemperatureAmbient':
celsius(entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE)),
'thermostatTemperatureSetpointHigh':
celsius(entity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH)),
'thermostatTemperatureSetpointLow':
celsius(entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW)),
'thermostatHumidityAmbient':
entity.attributes.get(climate.ATTR_CURRENT_HUMIDITY),
}
return {k: v for k, v in response.items() if v is not None}
# check if we have a string value to convert it to number
value = entity.state
if isinstance(entity.state, str):
try:
value = float(value)
except ValueError:
value = None
final_state = entity.state != STATE_OFF
final_brightness = entity.attributes.get(light.ATTR_BRIGHTNESS, 255
if final_state else 0)
if value is None:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Invalid value {} for the climate sensor"
.format(entity.state)
)
if entity.domain == media_player.DOMAIN:
level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL, 1.0
if final_state else 0.0)
# Convert 0.0-1.0 to 0-255
final_brightness = round(min(1.0, level) * 255)
# detect if we report temperature or humidity
unit_of_measurement = entity.attributes.get(
ATTR_UNIT_OF_MEASUREMENT,
units.temperature_unit
)
if unit_of_measurement in [TEMP_FAHRENHEIT, TEMP_CELSIUS]:
value = celsius(value, units)
attr = 'thermostatTemperatureAmbient'
elif unit_of_measurement == '%':
attr = 'thermostatHumidityAmbient'
else:
raise SmartHomeError(
ERROR_NOT_SUPPORTED,
"Unit {} is not supported by the climate sensor"
.format(unit_of_measurement)
)
if final_brightness is None:
final_brightness = 255 if final_state else 0
return {attr: value}
final_brightness = 100 * (final_brightness / 255)
query_response = {
"on": final_state,
"online": True,
"brightness": int(final_brightness)
@QUERY_HANDLERS.register(climate.DOMAIN)
def query_response_climate(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a climate entity to a QUERY response."""
mode = entity.attributes.get(climate.ATTR_OPERATION_MODE).lower()
if mode not in CLIMATE_SUPPORTED_MODES:
mode = 'heat'
attrs = entity.attributes
response = {
'thermostatMode': mode,
'thermostatTemperatureSetpoint':
celsius(attrs.get(climate.ATTR_TEMPERATURE), units),
'thermostatTemperatureAmbient':
celsius(attrs.get(climate.ATTR_CURRENT_TEMPERATURE), units),
'thermostatTemperatureSetpointHigh':
celsius(attrs.get(climate.ATTR_TARGET_TEMP_HIGH), units),
'thermostatTemperatureSetpointLow':
celsius(attrs.get(climate.ATTR_TARGET_TEMP_LOW), units),
'thermostatHumidityAmbient':
attrs.get(climate.ATTR_CURRENT_HUMIDITY),
}
return {k: v for k, v in response.items() if v is not None}
@QUERY_HANDLERS.register(media_player.DOMAIN)
def query_response_media_player(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a media_player entity to a QUERY response."""
level = entity.attributes.get(
media_player.ATTR_MEDIA_VOLUME_LEVEL,
1.0 if entity.state != STATE_OFF else 0.0)
# Convert 0.0-1.0 to 0-255
brightness = int(level * 100)
return {'brightness': brightness}
@QUERY_HANDLERS.register(light.DOMAIN)
def query_response_light(
entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Convert a light entity to a QUERY response."""
response = {} # type: Dict[str, Any]
brightness = entity.attributes.get(light.ATTR_BRIGHTNESS)
if brightness is not None:
response['brightness'] = int(100 * (brightness / 255))
supported_features = entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported_features & \
(light.SUPPORT_COLOR_TEMP | light.SUPPORT_RGB_COLOR):
query_response["color"] = {}
response['color'] = {}
if entity.attributes.get(light.ATTR_COLOR_TEMP) is not None:
query_response["color"]["temperature"] = \
response['color']['temperature'] = \
int(round(color.color_temperature_mired_to_kelvin(
entity.attributes.get(light.ATTR_COLOR_TEMP))))
if entity.attributes.get(light.ATTR_COLOR_NAME) is not None:
query_response["color"]["name"] = \
response['color']['name'] = \
entity.attributes.get(light.ATTR_COLOR_NAME)
if entity.attributes.get(light.ATTR_RGB_COLOR) is not None:
color_rgb = entity.attributes.get(light.ATTR_RGB_COLOR)
if color_rgb is not None:
query_response["color"]["spectrumRGB"] = \
response['color']['spectrumRGB'] = \
int(color.color_rgb_to_hex(
color_rgb[0], color_rgb[1], color_rgb[2]), 16)
return query_response
return response
def query_device(entity: Entity, config: Config, units: UnitSystem) -> dict:
"""Take an entity and return a properly formatted device object."""
state = entity.state != STATE_OFF
defaults = {
'on': state,
'online': True
}
handler = QUERY_HANDLERS.get(entity.domain)
if callable(handler):
defaults.update(handler(entity, config, units))
return defaults
# erroneous bug on old pythons and pylint
@ -429,7 +455,7 @@ def async_devices_query(hass, config, payload):
devices = {}
for device in payload.get('devices', []):
devid = device.get('id')
# In theory this should never happpen
# In theory this should never happen
if not devid:
_LOGGER.error('Device missing ID: %s', device)
continue
@ -438,11 +464,11 @@ def async_devices_query(hass, config, payload):
if not state:
# If we can't find a state, the device is offline
devices[devid] = {'online': False}
try:
devices[devid] = query_device(state, config, hass.config.units)
except SmartHomeError as error:
devices[devid] = {'errorCode': error.code}
else:
try:
devices[devid] = query_device(state, config, hass.config.units)
except SmartHomeError as error:
devices[devid] = {'errorCode': error.code}
return {'devices': devices}

View File

@ -247,7 +247,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
@asyncio.coroutine
def async_setup(hass, config):
"""Set up all groups found definded in the configuration."""
"""Set up all groups found defined in the configuration."""
component = hass.data.get(DOMAIN)
if component is None:
@ -371,7 +371,6 @@ def async_setup(hass, config):
@asyncio.coroutine
def _async_process_config(hass, config, component):
"""Process group configuration."""
groups = []
for object_id, conf in config.get(DOMAIN, {}).items():
name = conf.get(CONF_NAME, object_id)
entity_ids = conf.get(CONF_ENTITIES) or []
@ -381,13 +380,9 @@ def _async_process_config(hass, config, component):
# Don't create tasks and await them all. The order is important as
# groups get a number based on creation order.
group = yield from Group.async_create_group(
yield from Group.async_create_group(
hass, name, entity_ids, icon=icon, view=view,
control=control, object_id=object_id)
groups.append(group)
if groups:
yield from component.async_add_entities(groups)
class Group(Entity):

View File

@ -320,7 +320,7 @@ def setup(hass: HomeAssistant, base_config):
class CecDevice(Entity):
"""Representation of a HDMI CEC device entity."""
def __init__(self, hass: HomeAssistant, device, logical):
def __init__(self, hass: HomeAssistant, device, logical) -> None:
"""Initialize the device."""
self._device = device
self.hass = hass

View File

@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass
REQUIREMENTS = ['pyhomematic==0.1.38']
REQUIREMENTS = ['pyhomematic==0.1.39']
DOMAIN = 'homematic'
_LOGGER = logging.getLogger(__name__)
@ -218,7 +218,7 @@ SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema({
@bind_hass
def virtualkey(hass, address, channel, param, interface=None):
"""Send virtual keypress to homematic controlller."""
"""Send virtual keypress to homematic controller."""
data = {
ATTR_ADDRESS: address,
ATTR_CHANNEL: channel,
@ -256,7 +256,7 @@ def set_device_value(hass, address, channel, param, value, interface=None):
@bind_hass
def set_install_mode(hass, interface, mode=None, time=None, address=None):
"""Call setInstallMode XML-RPC method of supplied inteface."""
"""Call setInstallMode XML-RPC method of supplied interface."""
data = {
key: value for key, value in (
(ATTR_INTERFACE, interface),
@ -466,7 +466,7 @@ def _system_callback_handler(hass, config, src, *args):
hass, discovery_type, addresses, interface)
# When devices of this type are found
# they are setup in HASS and an discovery event is fired
# they are setup in HASS and a discovery event is fired
if found_devices:
discovery.load_platform(hass, component_name, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices
@ -665,7 +665,7 @@ class HMHub(Entity):
self.schedule_update_ha_state()
def _update_variables(self, now):
"""Retrive all variable data and update hmvariable states."""
"""Retrieve all variable data and update hmvariable states."""
variables = self._homematic.getAllSystemVariables(self._name)
if variables is None:
return

View File

@ -13,7 +13,7 @@ virtualkey:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT.
example: PRESS_LONG
interface:
description: (Optional) for set a interface value.
description: (Optional) for set an interface value.
example: Interfaces name from config
set_variable_value:
@ -42,7 +42,7 @@ set_device_value:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT
example: PRESS_LONG
interface:
description: (Optional) for set a interface value
description: (Optional) for set an interface value
example: Interfaces name from config
value:
description: New value

View File

@ -23,7 +23,7 @@ def auth_middleware(request, handler):
# If no password set, just always set authenticated=True
if request.app['hass'].http.api_password is None:
request[KEY_AUTHENTICATED] = True
return handler(request)
return (yield from handler(request))
# Check authentication
authenticated = False
@ -46,7 +46,7 @@ def auth_middleware(request, handler):
authenticated = True
request[KEY_AUTHENTICATED] = authenticated
return handler(request)
return (yield from handler(request))
def is_trusted_ip(request):

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