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

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

5
.gitignore vendored
View File

@ -16,7 +16,9 @@ Icon
# Thumbnails # Thumbnails
._* ._*
# IntelliJ IDEA
.idea .idea
*.iml
# pytest # pytest
.cache .cache
@ -98,3 +100,6 @@ desktop.ini
/home-assistant.pyproj /home-assistant.pyproj
/home-assistant.sln /home-assistant.sln
/.vs/* /.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 homeassistant/components/hassio.py @home-assistant/hassio
# Indiviudal components # Individual components
homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt homeassistant/components/alarm_control_panel/egardia.py @jeroenterheerdt
homeassistant/components/camera/yi.py @bachya homeassistant/components/camera/yi.py @bachya
homeassistant/components/climate/ephember.py @ttroy50 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/gearbest.py @HerrHofrat
homeassistant/components/sensor/irish_rail_transport.py @ttroy50 homeassistant/components/sensor/irish_rail_transport.py @ttroy50
homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel homeassistant/components/sensor/miflora.py @danielhiversen @ChristianKuehnel
homeassistant/components/sensor/pollen.py @bachya
homeassistant/components/sensor/sytadin.py @gautric homeassistant/components/sensor/sytadin.py @gautric
homeassistant/components/sensor/tibber.py @danielhiversen homeassistant/components/sensor/tibber.py @danielhiversen
homeassistant/components/sensor/waqi.py @andrey-git homeassistant/components/sensor/waqi.py @andrey-git

View File

@ -2,5 +2,5 @@
<li><a href="https://home-assistant.io/">Homepage</a></li> <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://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://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> </ul>

View File

@ -182,7 +182,8 @@ def check_pid(pid_file: str) -> None:
"""Check that Home Assistant is not already running.""" """Check that Home Assistant is not already running."""
# Check pid file # Check pid file
try: try:
pid = int(open(pid_file, 'r').readline()) with open(pid_file, 'r') as file:
pid = int(file.readline())
except IOError: except IOError:
# PID File does not exist # PID File does not exist
return return
@ -204,7 +205,8 @@ def write_pid(pid_file: str) -> None:
"""Create a PID File.""" """Create a PID File."""
pid = os.getpid() pid = os.getpid()
try: try:
open(pid_file, 'w').write(str(pid)) with open(pid_file, 'w') as file:
file.write(str(pid))
except IOError: except IOError:
print('Fatal Error: Unable to write pid file {}'.format(pid_file)) print('Fatal Error: Unable to write pid file {}'.format(pid_file))
sys.exit(1) 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 # have been processed. If a service does not exist it causes a 10
# second delay while we're blocking waiting for a response. # second delay while we're blocking waiting for a response.
# But services can be registered on other HA instances that are # 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. # block only if the service is defined in the current HA instance.
blocking = hass.services.has_service(domain, service.service) blocking = hass.services.has_service(domain, service.service)

View File

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

View File

@ -17,7 +17,7 @@ from homeassistant.const import (
SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP,
SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS, 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 from .const import CONF_FILTER, CONF_ENTITY_CONFIG
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -40,6 +40,7 @@ CONF_DESCRIPTION = 'description'
CONF_DISPLAY_CATEGORIES = 'display_categories' CONF_DISPLAY_CATEGORIES = 'display_categories'
HANDLERS = Registry() HANDLERS = Registry()
ENTITY_ADAPTERS = Registry()
class _DisplayCategory(object): class _DisplayCategory(object):
@ -50,8 +51,8 @@ class _DisplayCategory(object):
# Describes a combination of devices set to a specific state, when the # Describes a combination of devices set to a specific state, when the
# state change must occur in a specific order. For example, a "watch # 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 # Netflix" scene might require the: 1. TV to be powered on & 2. Input set
# HDMI1. Applies to Scenes # to HDMI1. Applies to Scenes
ACTIVITY_TRIGGER = "ACTIVITY_TRIGGER" ACTIVITY_TRIGGER = "ACTIVITY_TRIGGER"
# Indicates media devices with video or photo capabilities. # Indicates media devices with video or photo capabilities.
@ -133,10 +134,36 @@ def _capability(interface,
return result 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): def __init__(self, config, entity):
self.config = config self.config = config
self.entity = entity 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): def display_categories(self):
"""Return a list of display categories.""" """Return a list of display categories."""
@ -154,17 +181,217 @@ class _EntityCapabilities(object):
""" """
raise NotImplementedError raise NotImplementedError
def capabilities(self): def get_interface(self, capability):
"""Return a list of supported capabilities. """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 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. """A generic, on/off device.
The choice of last resort. The choice of last resort.
@ -173,78 +400,87 @@ class _GenericCapabilities(_EntityCapabilities):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.OTHER] return [_DisplayCategory.OTHER]
def capabilities(self): def interfaces(self):
return [_capability('Alexa.PowerController')] return [_AlexaPowerController(self.entity)]
class _SwitchCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(switch.DOMAIN)
class _SwitchCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.SWITCH] return [_DisplayCategory.SWITCH]
def capabilities(self): def interfaces(self):
return [_capability('Alexa.PowerController')] return [_AlexaPowerController(self.entity)]
class _CoverCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(cover.DOMAIN)
class _CoverCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.DOOR] return [_DisplayCategory.DOOR]
def capabilities(self): def interfaces(self):
capabilities = [_capability('Alexa.PowerController')] yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & cover.SUPPORT_SET_POSITION: if supported & cover.SUPPORT_SET_POSITION:
capabilities.append(_capability('Alexa.PercentageController')) yield _AlexaPercentageController(self.entity)
return capabilities
class _LightCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(light.DOMAIN)
class _LightCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.LIGHT] return [_DisplayCategory.LIGHT]
def capabilities(self): def interfaces(self):
capabilities = [_capability('Alexa.PowerController')] yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & light.SUPPORT_BRIGHTNESS: if supported & light.SUPPORT_BRIGHTNESS:
capabilities.append(_capability('Alexa.BrightnessController')) yield _AlexaBrightnessController(self.entity)
if supported & light.SUPPORT_RGB_COLOR: if supported & light.SUPPORT_RGB_COLOR:
capabilities.append(_capability('Alexa.ColorController')) yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_XY_COLOR: if supported & light.SUPPORT_XY_COLOR:
capabilities.append(_capability('Alexa.ColorController')) yield _AlexaColorController(self.entity)
if supported & light.SUPPORT_COLOR_TEMP: if supported & light.SUPPORT_COLOR_TEMP:
capabilities.append( yield _AlexaColorTemperatureController(self.entity)
_capability('Alexa.ColorTemperatureController'))
return capabilities
class _FanCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(fan.DOMAIN)
class _FanCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.OTHER] return [_DisplayCategory.OTHER]
def capabilities(self): def interfaces(self):
capabilities = [_capability('Alexa.PowerController')] yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & fan.SUPPORT_SET_SPEED: if supported & fan.SUPPORT_SET_SPEED:
capabilities.append(_capability('Alexa.PercentageController')) yield _AlexaPercentageController(self.entity)
return capabilities
class _LockCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(lock.DOMAIN)
class _LockCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.SMARTLOCK] return [_DisplayCategory.SMARTLOCK]
def capabilities(self): def interfaces(self):
return [_capability('Alexa.LockController')] return [_AlexaLockController(self.entity)]
class _MediaPlayerCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(media_player.DOMAIN)
class _MediaPlayerCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.TV] return [_DisplayCategory.TV]
def capabilities(self): def interfaces(self):
capabilities = [_capability('Alexa.PowerController')] yield _AlexaPowerController(self.entity)
supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0)
if supported & media_player.SUPPORT_VOLUME_SET: 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 | playback_features = (media_player.SUPPORT_PLAY |
media_player.SUPPORT_PAUSE | media_player.SUPPORT_PAUSE |
@ -252,89 +488,62 @@ class _MediaPlayerCapabilities(_EntityCapabilities):
media_player.SUPPORT_NEXT_TRACK | media_player.SUPPORT_NEXT_TRACK |
media_player.SUPPORT_PREVIOUS_TRACK) media_player.SUPPORT_PREVIOUS_TRACK)
if supported & playback_features: 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): def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER] return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self): def interfaces(self):
return [_capability('Alexa.SceneController')] return [_AlexaSceneController(self.entity,
supports_deactivation=False)]
class _ScriptCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(script.DOMAIN)
class _ScriptCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.ACTIVITY_TRIGGER] return [_DisplayCategory.ACTIVITY_TRIGGER]
def capabilities(self): def interfaces(self):
can_cancel = bool(self.entity.attributes.get('can_cancel')) can_cancel = bool(self.entity.attributes.get('can_cancel'))
return [_capability('Alexa.SceneController', return [_AlexaSceneController(self.entity,
supports_deactivation=can_cancel)] supports_deactivation=can_cancel)]
class _GroupCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(group.DOMAIN)
class _GroupCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
return [_DisplayCategory.SCENE_TRIGGER] return [_DisplayCategory.SCENE_TRIGGER]
def capabilities(self): def interfaces(self):
return [_capability('Alexa.SceneController', return [_AlexaSceneController(self.entity,
supports_deactivation=True)] supports_deactivation=True)]
class _SensorCapabilities(_EntityCapabilities): @ENTITY_ADAPTERS.register(sensor.DOMAIN)
class _SensorCapabilities(_AlexaEntity):
def default_display_categories(self): def default_display_categories(self):
# although there are other kinds of sensors, all but temperature # although there are other kinds of sensors, all but temperature
# sensors are currently ignored. # sensors are currently ignored.
return [_DisplayCategory.TEMPERATURE_SENSOR] return [_DisplayCategory.TEMPERATURE_SENSOR]
def capabilities(self): def interfaces(self):
capabilities = []
attrs = self.entity.attributes attrs = self.entity.attributes
if attrs.get(CONF_UNIT_OF_MEASUREMENT) in ( if attrs.get(CONF_UNIT_OF_MEASUREMENT) in (
TEMP_FAHRENHEIT, TEMP_FAHRENHEIT,
TEMP_CELSIUS, TEMP_CELSIUS,
): ):
capabilities.append(_capability( yield _AlexaTemperatureSensor(self.entity)
'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,
}
class _Cause(object): 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') token = request[API_HEADER].get('correlationToken')
if token: if token:
response[API_EVENT][API_HEADER]['correlationToken'] = token response[API_EVENT][API_HEADER]['correlationToken'] = token
@ -511,36 +720,26 @@ def async_api_discovery(hass, config, request):
entity.entity_id) entity.entity_id)
continue continue
try: if entity.domain not in ENTITY_ADAPTERS:
entity_capabilities = _capabilities_for_entity(config, entity)
except _UnknownEntityDomainError:
continue continue
alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
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)
endpoint = { endpoint = {
'displayCategories': entity_capabilities.display_categories(), 'displayCategories': alexa_entity.display_categories(),
'additionalApplianceDetails': {}, 'additionalApplianceDetails': {},
'endpointId': entity.entity_id.replace('.', '#'), 'endpointId': alexa_entity.entity_id(),
'friendlyName': friendly_name, 'friendlyName': alexa_entity.friendly_name(),
'description': description, 'description': alexa_entity.description(),
'manufacturerName': 'Home Assistant', 'manufacturerName': 'Home Assistant',
} }
alexa_capabilities = entity_capabilities.capabilities() endpoint['capabilities'] = [
if not alexa_capabilities: i.serialize_discovery() for i in alexa_entity.interfaces()]
if not endpoint['capabilities']:
_LOGGER.debug("Not exposing %s because it has no capabilities", _LOGGER.debug("Not exposing %s because it has no capabilities",
entity.entity_id) entity.entity_id)
continue continue
endpoint['capabilities'] = alexa_capabilities
discovery_endpoints.append(endpoint) discovery_endpoints.append(endpoint)
return api_message( return api_message(
@ -624,7 +823,7 @@ def async_api_set_brightness(hass, config, request, entity):
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
def async_api_adjust_brightness(hass, config, request, entity): 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']) brightness_delta = int(request[API_PAYLOAD]['brightnessDelta'])
# read current state # read current state
@ -812,7 +1011,7 @@ def async_api_set_percentage(hass, config, request, entity):
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
def async_api_adjust_percentage(hass, config, request, entity): 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']) percentage_delta = int(request[API_PAYLOAD]['percentageDelta'])
service = None service = None
data = {ATTR_ENTITY_ID: entity.entity_id} data = {ATTR_ENTITY_ID: entity.entity_id}
@ -873,7 +1072,7 @@ def async_api_lock(hass, config, request, entity):
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
def async_api_unlock(hass, config, request, entity): 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, { yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id ATTR_ENTITY_ID: entity.entity_id
}, blocking=False) }, blocking=False)
@ -900,11 +1099,46 @@ def async_api_set_volume(hass, config, request, entity):
return api_message(request) 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')) @HANDLERS.register(('Alexa.Speaker', 'AdjustVolume'))
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
def async_api_adjust_volume(hass, config, request, entity): 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']) volume_delta = int(request[API_PAYLOAD]['volume'])
current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) 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) 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')) @HANDLERS.register(('Alexa.Speaker', 'SetMute'))
@extract_entity @extract_entity
@asyncio.coroutine @asyncio.coroutine
@ -1033,18 +1291,13 @@ def async_api_previous(hass, config, request, entity):
@asyncio.coroutine @asyncio.coroutine
def async_api_reportstate(hass, config, request, entity): def async_api_reportstate(hass, config, request, entity):
"""Process a ReportState request.""" """Process a ReportState request."""
unit = entity.attributes[CONF_UNIT_OF_MEASUREMENT] alexa_entity = ENTITY_ADAPTERS[entity.domain](config, entity)
temp_property = { properties = []
'namespace': 'Alexa.TemperatureSensor', for interface in alexa_entity.interfaces():
'name': 'temperature', properties.extend(interface.serialize_properties())
'value': {
'value': float(entity.state),
'scale': API_TEMP_UNITS[unit],
},
}
return api_message( return api_message(
request, request,
name='StateReport', name='StateReport',
context={'properties': [temp_property]} context={'properties': properties}
) )

View File

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

View File

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

View File

@ -47,7 +47,7 @@ def setup(hass, config):
return False return False
hass.data[DATA_ARLO] = arlo hass.data[DATA_ARLO] = arlo
except (ConnectTimeout, HTTPError) as ex: 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( hass.components.persistent_notification.create(
'Error: {}<br />' 'Error: {}<br />'
'You will need to restart hass after fixing.' 'You will need to restart hass after fixing.'

View File

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

View File

@ -50,11 +50,6 @@ class EcobeeBinarySensor(BinarySensorDevice):
"""Return the status of the sensor.""" """Return the status of the sensor."""
return self._state == 'true' 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 @property
def device_class(self): def device_class(self):
"""Return the class of this sensor, from DEVICE_CLASSES.""" """Return the class of this sensor, from DEVICE_CLASSES."""

View File

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

View File

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

View File

@ -59,5 +59,5 @@ class HiveBinarySensorEntity(BinarySensorDevice):
self.node_device_type) self.node_device_type)
def update(self): def update(self):
"""Update all Node data frome Hive.""" """Update all Node data from Hive."""
self.session.core.update_data(self.node_id) 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, 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.""" """Initialize the IHC binary sensor."""
super().__init__(ihc_controller, name, ihc_id, info, product) super().__init__(ihc_controller, name, ihc_id, info, product)
self._state = None self._state = None

View File

@ -83,5 +83,5 @@ class InsteonPLMBinarySensorDevice(BinarySensorDevice):
@callback @callback
def async_binarysensor_update(self, message): def async_binarysensor_update(self, message):
"""Receive notification from transport that new data exists.""" """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()) 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: elif subnode_id == 2:
parent_device.add_negative_node(node) parent_device.add_negative_node(node)
elif device_type == 'moisture': elif device_type == 'moisture':
# Moisure nodes have a subnode 2, but we ignore it because it's # Moisture nodes have a subnode 2, but we ignore it because
# just the inverse of the primary node. # it's just the inverse of the primary node.
if subnode_id == 4: if subnode_id == 4:
# Heartbeat node # Heartbeat node
device = ISYBinarySensorHeartbeat(node, parent_device) 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 ( from homeassistant.components.binary_sensor import (
BinarySensorDevice, DEVICE_CLASSES_SCHEMA) BinarySensorDevice, DEVICE_CLASSES_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, CONF_PAYLOAD_OFF, CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON,
CONF_DEVICE_CLASS) CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS)
from homeassistant.components.mqtt import ( from homeassistant.components.mqtt import (
CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE, CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE,
CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability) CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability)
@ -24,8 +24,10 @@ import homeassistant.helpers.config_validation as cv
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DEFAULT_NAME = 'MQTT Binary sensor' DEFAULT_NAME = 'MQTT Binary sensor'
DEFAULT_PAYLOAD_OFF = 'OFF' DEFAULT_PAYLOAD_OFF = 'OFF'
DEFAULT_PAYLOAD_ON = 'ON' DEFAULT_PAYLOAD_ON = 'ON'
DEFAULT_FORCE_UPDATE = False
DEPENDENCIES = ['mqtt'] 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_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string,
vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, 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) }).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_AVAILABILITY_TOPIC),
config.get(CONF_DEVICE_CLASS), config.get(CONF_DEVICE_CLASS),
config.get(CONF_QOS), config.get(CONF_QOS),
config.get(CONF_FORCE_UPDATE),
config.get(CONF_PAYLOAD_ON), config.get(CONF_PAYLOAD_ON),
config.get(CONF_PAYLOAD_OFF), config.get(CONF_PAYLOAD_OFF),
config.get(CONF_PAYLOAD_AVAILABLE), config.get(CONF_PAYLOAD_AVAILABLE),
@ -65,7 +69,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
"""Representation a binary sensor that is updated by MQTT.""" """Representation a binary sensor that is updated by MQTT."""
def __init__(self, name, state_topic, availability_topic, device_class, def __init__(self, name, state_topic, availability_topic, device_class,
qos, payload_on, payload_off, payload_available, qos, force_update, payload_on, payload_off, payload_available,
payload_not_available, value_template): payload_not_available, value_template):
"""Initialize the MQTT binary sensor.""" """Initialize the MQTT binary sensor."""
super().__init__(availability_topic, qos, payload_available, super().__init__(availability_topic, qos, payload_available,
@ -77,6 +81,7 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._payload_on = payload_on self._payload_on = payload_on
self._payload_off = payload_off self._payload_off = payload_off
self._qos = qos self._qos = qos
self._force_update = force_update
self._template = value_template self._template = value_template
@asyncio.coroutine @asyncio.coroutine
@ -94,6 +99,11 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
self._state = True self._state = True
elif payload == self._payload_off: elif payload == self._payload_off:
self._state = False 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() self.async_schedule_update_ha_state()
@ -119,3 +129,8 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice):
def device_class(self): def device_class(self):
"""Return the class of this sensor.""" """Return the class of this sensor."""
return self._device_class 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._name += ' / ' + module_name
self._sensor_name = sensor self._sensor_name = sensor
self._name += ' ' + sensor self._name += ' ' + sensor
camera_id = data.camera_data.cameraByName( self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id'] camera=camera_name, home=home)['id']
self._unique_id = "Netatmo_binary_sensor {0} - {1}".format(
self._name, camera_id)
self._cameratype = camera_type self._cameratype = camera_type
self._state = None self._state = None

View File

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

View File

@ -39,7 +39,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
sensor_type)) sensor_type))
else: 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: for zone in raincloud.controller.faucet.zones:
sensors.append(RainCloudBinarySensor(zone, sensor_type)) sensors.append(RainCloudBinarySensor(zone, sensor_type))

View File

@ -15,6 +15,7 @@ from homeassistant.components.binary_sensor import (
DEVICE_CLASSES_SCHEMA) DEVICE_CLASSES_SCHEMA)
from homeassistant.const import ( from homeassistant.const import (
ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE, ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE,
CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE,
CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START) CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START)
from homeassistant.exceptions import TemplateError from homeassistant.exceptions import TemplateError
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
@ -29,6 +30,8 @@ CONF_DELAY_OFF = 'delay_off'
SENSOR_SCHEMA = vol.Schema({ SENSOR_SCHEMA = vol.Schema({
vol.Required(CONF_VALUE_TEMPLATE): cv.template, 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_FRIENDLY_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA,
@ -38,11 +41,6 @@ SENSOR_SCHEMA = vol.Schema({
vol.All(cv.time_period, cv.positive_timedelta), vol.All(cv.time_period, cv.positive_timedelta),
}) })
SENSOR_SCHEMA = vol.All(
cv.deprecated(ATTR_ENTITY_ID),
SENSOR_SCHEMA,
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), 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(): for device, device_config in config[CONF_SENSORS].items():
value_template = device_config[CONF_VALUE_TEMPLATE] 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 entity_ids = (device_config.get(ATTR_ENTITY_ID) or
value_template.extract_entities()) value_template.extract_entities())
friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) 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: if value_template is not None:
value_template.hass = hass 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( sensors.append(
BinarySensorTemplate( BinarySensorTemplate(
hass, device, friendly_name, device_class, value_template, 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: if not sensors:
_LOGGER.error("No sensors added") _LOGGER.error("No sensors added")
@ -82,7 +90,8 @@ class BinarySensorTemplate(BinarySensorDevice):
"""A virtual binary sensor that triggers from another sensor.""" """A virtual binary sensor that triggers from another sensor."""
def __init__(self, hass, device, friendly_name, device_class, 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.""" """Initialize the Template binary sensor."""
self.hass = hass self.hass = hass
self.entity_id = async_generate_entity_id( self.entity_id = async_generate_entity_id(
@ -91,6 +100,10 @@ class BinarySensorTemplate(BinarySensorDevice):
self._device_class = device_class self._device_class = device_class
self._template = value_template self._template = value_template
self._state = None 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._entities = entity_ids
self._delay_on = delay_on self._delay_on = delay_on
self._delay_off = delay_off self._delay_off = delay_off
@ -119,6 +132,16 @@ class BinarySensorTemplate(BinarySensorDevice):
"""Return the name of the sensor.""" """Return the name of the sensor."""
return self._name 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 @property
def is_on(self): def is_on(self):
"""Return true if sensor is on.""" """Return true if sensor is on."""
@ -137,8 +160,9 @@ class BinarySensorTemplate(BinarySensorDevice):
@callback @callback
def _async_render(self): def _async_render(self):
"""Get the state of template.""" """Get the state of template."""
state = None
try: try:
return self._template.async_render().lower() == 'true' state = (self._template.async_render().lower() == 'true')
except TemplateError as ex: except TemplateError as ex:
if ex.args and ex.args[0].startswith( if ex.args and ex.args[0].startswith(
"UndefinedError: 'None' has no attribute"): "UndefinedError: 'None' has no attribute"):
@ -148,6 +172,29 @@ class BinarySensorTemplate(BinarySensorDevice):
return return
_LOGGER.error("Could not render template %s: %s", self._name, ex) _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 @callback
def async_check_state(self): def async_check_state(self):
"""Update the state from the template.""" """Update the state from the template."""

View File

@ -126,11 +126,12 @@ class ThresholdSensor(BinarySensorDevice):
@property @property
def threshold_type(self): def threshold_type(self):
"""Return the type of threshold this sensor represents.""" """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 return TYPE_RANGE
elif self._threshold_lower: elif self._threshold_lower is not None:
return TYPE_LOWER return TYPE_LOWER
elif self._threshold_upper: elif self._threshold_upper is not None:
return TYPE_UPPER return TYPE_UPPER
@property @property

View File

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

View File

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

View File

@ -174,7 +174,7 @@ class WebDavCalendarData(object):
@staticmethod @staticmethod
def is_matching(vevent, search): 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: if search is None:
return True return True

View File

@ -411,7 +411,7 @@ class TodoistProjectData(object):
The "best" event is determined by the following criteria: The "best" event is determined by the following criteria:
* A proposed event must not be completed * 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) the event at index 0, selected above)
* A proposed event must be on the same day or earlier as our * A proposed event must be on the same day or earlier as our
current event current event

View File

@ -91,13 +91,13 @@ def async_snapshot(hass, filename, entity_id=None):
@bind_hass @bind_hass
@asyncio.coroutine @asyncio.coroutine
def async_get_image(hass, entity_id, timeout=10): 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) websession = async_get_clientsession(hass)
state = hass.states.get(entity_id) state = hass.states.get(entity_id)
if state is None: if state is None:
raise HomeAssistantError( 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( url = "{0}{1}".format(
hass.config.api.base_url, hass.config.api.base_url,

View File

@ -22,7 +22,7 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90)
_LOGGER = logging.getLogger(__name__) _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.""" """Set up Abode camera devices."""
import abodepy.helpers.constants as CONST import abodepy.helpers.constants as CONST
import abodepy.helpers.timeline as TIMELINE 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 For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/camera.canary/ https://home-assistant.io/components/camera.canary/
""" """
import asyncio
import logging 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.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__) _LOGGER = logging.getLogger(__name__)
ATTR_MOTION_START_TIME = "motion_start_time" MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90)
ATTR_MOTION_END_TIME = "motion_end_time"
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string,
})
def setup_platform(hass, config, add_devices, discovery_info=None): 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 = [] devices = []
for location in data.locations: for location in data.locations:
entries = data.get_motion_entries(location.location_id) for device in location.devices:
if entries: if device.is_online:
devices.append(CanaryCamera(data, location.location_id, devices.append(
DEFAULT_TIMEOUT)) CanaryCamera(hass, data, location, device, DEFAULT_TIMEOUT,
config.get(CONF_FFMPEG_ARGUMENTS)))
add_devices(devices, True) add_devices(devices, True)
@ -36,60 +48,65 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
class CanaryCamera(Camera): class CanaryCamera(Camera):
"""An implementation of a Canary security 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.""" """Initialize a Canary security camera."""
super().__init__() super().__init__()
self._ffmpeg = hass.data[DATA_FFMPEG]
self._ffmpeg_arguments = ffmpeg_args
self._data = data self._data = data
self._location_id = location_id self._location = location
self._device = device
self._timeout = timeout self._timeout = timeout
self._live_stream_session = None
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
@property @property
def name(self): def name(self):
"""Return the name of this device.""" """Return the name of this device."""
return self._location.name return self._device.name
@property @property
def is_recording(self): def is_recording(self):
"""Return true if the device is recording.""" """Return true if the device is recording."""
return self._location.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 @property
def motion_detection_enabled(self): def motion_detection_enabled(self):
"""Return the camera motion detection status.""" """Return the camera motion detection status."""
return not self._location.is_recording return not self._location.is_recording
@asyncio.coroutine
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 self._name = home + ' / ' + camera_name
else: else:
self._name = camera_name 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( self._vpnurl, self._localurl = self._data.camera_data.cameraUrls(
camera=camera_name camera=camera_name
) )
self._unique_id = data.camera_data.cameraByName(
camera=camera_name, home=home)['id']
self._cameratype = camera_type self._cameratype = camera_type
def camera_image(self): def camera_image(self):
@ -117,5 +115,5 @@ class NetatmoCamera(Camera):
@property @property
def unique_id(self): def unique_id(self):
"""Return the unique ID for this sensor.""" """Return the unique ID for this camera."""
return self._unique_id return self._unique_id

View File

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

View File

@ -127,6 +127,9 @@ class UnifiVideoCamera(Camera):
else: else:
client_cls = uvc_camera.UVCCameraClient client_cls = uvc_camera.UVCCameraClient
if caminfo['username'] is None:
caminfo['username'] = 'ubnt'
camera = None camera = None
for addr in addrs: for addr in addrs:
try: 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) CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME)
from homeassistant.helpers import config_validation as cv from homeassistant.helpers import config_validation as cv
REQUIREMENTS = ['pyxeoma==1.2'] REQUIREMENTS = ['pyxeoma==1.3']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -22,6 +22,8 @@ CONF_CAMERAS = 'cameras'
CONF_HIDE = 'hide' CONF_HIDE = 'hide'
CONF_IMAGE_NAME = 'image_name' CONF_IMAGE_NAME = 'image_name'
CONF_NEW_VERSION = 'new_version' CONF_NEW_VERSION = 'new_version'
CONF_VIEWER_PASSWORD = 'viewer_password'
CONF_VIEWER_USERNAME = 'viewer_username'
CAMERAS_SCHEMA = vol.Schema({ CAMERAS_SCHEMA = vol.Schema({
vol.Required(CONF_IMAGE_NAME): cv.string, 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] host = config[CONF_HOST]
login = config.get(CONF_USERNAME) login = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD) password = config.get(CONF_PASSWORD)
new_version = config[CONF_NEW_VERSION]
xeoma = Xeoma(host, new_version, login, password) xeoma = Xeoma(host, login, password)
try: try:
yield from xeoma.async_test_connection() 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_IMAGE_NAME: image_name,
CONF_HIDE: False, 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]: 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)) cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras))
async_add_devices( async_add_devices(
[XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME]) [XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME],
for camera in cameras]) camera[CONF_VIEWER_USERNAME],
camera[CONF_VIEWER_PASSWORD]) for camera in cameras])
except XeomaError as err: except XeomaError as err:
_LOGGER.error("Error: %s", err.message) _LOGGER.error("Error: %s", err.message)
return return
@ -87,12 +92,14 @@ def async_setup_platform(hass, config, async_add_devices, discovery_info=None):
class XeomaCamera(Camera): class XeomaCamera(Camera):
"""Implementation of a Xeoma camera.""" """Implementation of a Xeoma camera."""
def __init__(self, xeoma, image, name): def __init__(self, xeoma, image, name, username, password):
"""Initialize a Xeoma camera.""" """Initialize a Xeoma camera."""
super().__init__() super().__init__()
self._xeoma = xeoma self._xeoma = xeoma
self._name = name self._name = name
self._image = image self._image = image
self._username = username
self._password = password
self._last_image = None self._last_image = None
@asyncio.coroutine @asyncio.coroutine
@ -100,7 +107,8 @@ class XeomaCamera(Camera):
"""Return a still image response from the camera.""" """Return a still image response from the camera."""
from pyxeoma.xeoma import XeomaError from pyxeoma.xeoma import XeomaError
try: 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 self._last_image = image
except XeomaError as err: except XeomaError as err:
_LOGGER.error("Error fetching image: %s", err.message) _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.helpers import discovery
from homeassistant.util import Throttle from homeassistant.util import Throttle
REQUIREMENTS = ['py-canary==0.2.3'] REQUIREMENTS = ['py-canary==0.4.0']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -111,7 +111,18 @@ class CanaryData(object):
"""Return a list of readings based on device_id.""" """Return a list of readings based on device_id."""
return self._readings_by_device_id.get(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): def set_location_mode(self, location_id, mode_name, is_private=False):
"""Set location mode.""" """Set location mode."""
self._api.set_location_mode(location_id, mode_name, is_private) self._api.set_location_mode(location_id, mode_name, is_private)
self.update(no_throttle=True) 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._force_refresh = True
self._api.device.set(values) 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 @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""

View File

@ -14,23 +14,19 @@ from homeassistant.components.climate import (
SUPPORT_ON_OFF) SUPPORT_ON_OFF)
from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE
SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_HUMIDITY | SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH
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)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Demo climate devices.""" """Set up the Demo climate devices."""
add_devices([ add_devices([
DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77, 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', DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High',
67, 54, 'Off', 'cool', False, None, None), 67, 54, 'Off', 'cool', False, None, None, None),
DemoClimate('Ecobee', None, TEMP_CELSIUS, None, None, 23, 'Auto Low', DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low',
None, None, 'Auto', 'auto', None, 24, 21) None, None, 'Auto', 'auto', None, 24, 21, None)
]) ])
@ -40,9 +36,37 @@ class DemoClimate(ClimateDevice):
def __init__(self, name, target_temperature, unit_of_measurement, def __init__(self, name, target_temperature, unit_of_measurement,
away, hold, current_temperature, current_fan_mode, away, hold, current_temperature, current_fan_mode,
target_humidity, current_humidity, current_swing_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.""" """Initialize the climate device."""
self._name = name 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_temperature = target_temperature
self._target_humidity = target_humidity self._target_humidity = target_humidity
self._unit_of_measurement = unit_of_measurement self._unit_of_measurement = unit_of_measurement
@ -59,12 +83,12 @@ class DemoClimate(ClimateDevice):
self._swing_list = ['Auto', '1', '2', '3', 'Off'] self._swing_list = ['Auto', '1', '2', '3', 'Off']
self._target_temperature_high = target_temp_high self._target_temperature_high = target_temp_high
self._target_temperature_low = target_temp_low self._target_temperature_low = target_temp_low
self._on = True self._on = is_on
@property @property
def supported_features(self): def supported_features(self):
"""Return the list of supported features.""" """Return the list of supported features."""
return SUPPORT_FLAGS return self._support_flags
@property @property
def should_poll(self): def should_poll(self):
@ -207,7 +231,7 @@ class DemoClimate(ClimateDevice):
self.schedule_update_ha_state() self.schedule_update_ha_state()
def turn_aux_heat_on(self): def turn_aux_heat_on(self):
"""Turn auxillary heater on.""" """Turn auxiliary heater on."""
self._aux = True self._aux = True
self.schedule_update_ha_state() 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, DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE, ClimateDevice,
ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE,
SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE, 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 ( from homeassistant.const import (
ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT) ATTR_ENTITY_ID, STATE_OFF, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT)
import homeassistant.helpers.config_validation as cv 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_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE |
SUPPORT_HOLD_MODE | SUPPORT_OPERATION_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): def setup_platform(hass, config, add_devices, discovery_info=None):

View File

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

View File

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

View File

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

View File

@ -46,7 +46,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
serport = connection.connection(ipaddress, port) serport = connection.connection(ipaddress, port)
serport.open() serport.open()
for thermostat, tstat in tstats.items(): for tstat in tstats.values():
add_devices([ add_devices([
HeatmiserV3Thermostat( HeatmiserV3Thermostat(
heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport) 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) entity.handle_update(self.data_updatesource)
def update(self): def update(self):
"""Update all Node data frome Hive.""" """Update all Node data from Hive."""
self.session.core.update_data(self.node_id) 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 @asyncio.coroutine
def async_turn_aux_heat_on(self): 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: if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC], mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_on, self._qos, self._retain) self._payload_on, self._qos, self._retain)
@ -576,7 +576,7 @@ class MqttClimate(MqttAvailability, ClimateDevice):
@asyncio.coroutine @asyncio.coroutine
def async_turn_aux_heat_off(self): 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: if self._topic[CONF_AUX_COMMAND_TOPIC] is not None:
mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC], mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC],
self._payload_off, self._qos, self._retain) self._payload_off, self._qos, self._retain)

View File

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

View File

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

View File

@ -24,7 +24,7 @@ CONF_RELAY = 'relay'
CONF_THERMOSTAT = 'thermostat' CONF_THERMOSTAT = 'thermostat'
DEFAULT_AWAY_TEMPERATURE = 14 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 DEFAULT_TIME_OFFSET = 7200
# # Return cached results if last scan was less then this time ago # # Return cached results if last scan was less then this time ago
# # NetAtmo Data is uploaded to server every hour # # 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): class ThermostatDevice(ClimateDevice):
"""Interface class for the oemthermostat modul.""" """Interface class for the oemthermostat module."""
def __init__(self, hass, thermostat, name, away_temp): def __init__(self, hass, thermostat, name, away_temp):
"""Initialize the device.""" """Initialize the device."""

View File

@ -249,7 +249,7 @@ class TadoClimate(ClimateDevice):
data = self._store.get_data(self._data_id) data = self._store.get_data(self._data_id)
if data is None: 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 return
if 'sensorDataPoints' in data: if 'sensorDataPoints' in data:
@ -294,7 +294,7 @@ class TadoClimate(ClimateDevice):
overlay = False overlay = False
overlay_data = None overlay_data = None
termination = self._current_operation termination = CONST_MODE_SMART_SCHEDULE
cooling = False cooling = False
fan_speed = CONST_MODE_OFF fan_speed = CONST_MODE_OFF
@ -317,7 +317,7 @@ class TadoClimate(ClimateDevice):
fan_speed = setting_data['fanSpeed'] fan_speed = setting_data['fanSpeed']
if self._device_is_active: 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" # and a termination, but we want to see the mode "OFF"
self._overlay_mode = termination self._overlay_mode = termination
self._current_operation = 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 from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
REQUIREMENTS = ['pytouchline==0.6'] REQUIREMENTS = ['pytouchline==0.7']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

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

View File

@ -16,8 +16,7 @@ import voluptuous as vol
from homeassistant.const import ( from homeassistant.const import (
EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE) EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME, CONF_TYPE)
from homeassistant.helpers import entityfilter from homeassistant.helpers import entityfilter, config_validation as cv
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.util import dt as dt_util from homeassistant.util import dt as dt_util
from homeassistant.components.alexa import smart_home as alexa_sh 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) cloud = hass.data[DOMAIN] = Cloud(hass, **kwargs)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, cloud.async_start)
success = yield from cloud.initialize()
if not success:
return False
yield from http_api.async_setup(hass) yield from http_api.async_setup(hass)
return True return True
@ -192,19 +186,6 @@ class Cloud:
return self._gactions_config 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): def path(self, *parts):
"""Get config path inside cloud dir. """Get config path inside cloud dir.
@ -234,8 +215,18 @@ class Cloud:
'refresh_token': self.refresh_token, 'refresh_token': self.refresh_token,
}, indent=4)) }, indent=4))
def _start_cloud(self, event): @asyncio.coroutine
def async_start(self, _):
"""Start the cloud component.""" """Start the cloud component."""
success = yield from self._fetch_jwt_keyset()
# Fetching keyset can fail if internet is not up yet.
if not success:
self.hass.helpers.async_call_later(5, self.async_start)
return
def load_config():
"""Load config."""
# Ensure config dir exists # Ensure config dir exists
path = self.hass.config.path(CONFIG_DIR) path = self.hass.config.path(CONFIG_DIR)
if not os.path.isdir(path): if not os.path.isdir(path):
@ -243,10 +234,15 @@ class Cloud:
user_info = self.user_info_path user_info = self.user_info_path
if not os.path.isfile(user_info): if not os.path.isfile(user_info):
return return None
with open(user_info, 'rt') as file: with open(user_info, 'rt') as file:
info = json.loads(file.read()) return json.loads(file.read())
info = yield from self.hass.async_add_job(load_config)
if info is None:
return
# Validate tokens # Validate tokens
try: 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/ https://home-assistant.io/components/coinbase/
""" """
from datetime import timedelta from datetime import timedelta
import logging import logging
import voluptuous as vol import voluptuous as vol
import homeassistant.helpers.config_validation as cv
from homeassistant.const import CONF_API_KEY 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.helpers.discovery import load_platform
from homeassistant.util import Throttle
REQUIREMENTS = ['coinbase==2.0.7']
REQUIREMENTS = ['coinbase==2.0.6']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
DOMAIN = 'coinbase' DOMAIN = 'coinbase'
@ -46,14 +47,13 @@ def setup(hass, config):
api_secret = config[DOMAIN].get(CONF_API_SECRET) api_secret = config[DOMAIN].get(CONF_API_SECRET)
exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES) exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES)
hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(api_key, hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(
api_secret) api_key, api_secret)
if not hasattr(coinbase_data, 'accounts'): if not hasattr(coinbase_data, 'accounts'):
return False return False
for account in coinbase_data.accounts.data: for account in coinbase_data.accounts.data:
load_platform(hass, 'sensor', DOMAIN, load_platform(hass, 'sensor', DOMAIN, {'account': account}, config)
{'account': account}, config)
for currency in exchange_currencies: for currency in exchange_currencies:
if currency not in coinbase_data.exchange_rates.rates: if currency not in coinbase_data.exchange_rates.rates:
_LOGGER.warning("Currency %s not found", currency) _LOGGER.warning("Currency %s not found", currency)

View File

@ -68,7 +68,7 @@ class HMCover(HMDevice, CoverDevice):
self._hmdevice.stop(self._channel) self._hmdevice.stop(self._channel)
def _init_data_struct(self): 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._state = "LEVEL"
self._data.update({self._state: STATE_UNKNOWN}) self._data.update({self._state: STATE_UNKNOWN})
if "LEVEL_2" in self._hmdevice.WRITENODE: if "LEVEL_2" in self._hmdevice.WRITENODE:

View File

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

View File

@ -74,7 +74,7 @@ def async_add_devices_discovery(hass, discovery_info, async_add_devices):
@callback @callback
def async_add_devices_config(hass, config, async_add_devices): 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 import xknx
cover = xknx.devices.Cover( cover = xknx.devices.Cover(
hass.data[DATA_KNX].xknx, hass.data[DATA_KNX].xknx,

View File

@ -63,7 +63,7 @@ class LutronCover(LutronDevice, CoverDevice):
def update(self): def update(self):
"""Call when forcing a refresh of the device.""" """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 level = self._lutron_device.level
_LOGGER.debug("Lutron ID: %d updated to %f", _LOGGER.debug("Lutron ID: %d updated to %f",
self._lutron_device.id, level) self._lutron_device.id, level)

View File

@ -214,16 +214,6 @@ class MqttCover(MqttAvailability, CoverDevice):
self.async_schedule_update_ha_state() 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: if self._state_topic is None:
# Force into optimistic mode. # Force into optimistic mode.
self._optimistic = True self._optimistic = True
@ -232,11 +222,6 @@ class MqttCover(MqttAvailability, CoverDevice):
self.hass, self._state_topic, self.hass, self._state_topic,
state_message_received, self._qos) 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: if self._tilt_status_topic is None:
self._tilt_optimistic = True self._tilt_optimistic = True
else: else:

View File

@ -89,11 +89,6 @@ class RPiGPIOCover(CoverDevice):
rpi_gpio.setup_input(self._state_pin, self._state_pull_mode) rpi_gpio.setup_input(self._state_pin, self._state_pull_mode)
rpi_gpio.write_output(self._relay_pin, not self._invert_relay) 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 @property
def name(self): def name(self):
"""Return the name of the cover if any.""" """Return the name of the cover if any."""

View File

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

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/ https://home-assistant.io/components/cover.tahoma/
""" """
import logging import logging
from datetime import timedelta
from homeassistant.components.cover import CoverDevice from homeassistant.components.cover import CoverDevice
from homeassistant.components.tahoma import ( from homeassistant.components.tahoma import (
@ -15,8 +14,6 @@ DEPENDENCIES = ['tahoma']
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(seconds=60)
def setup_platform(hass, config, add_devices, discovery_info=None): def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up Tahoma covers.""" """Set up Tahoma covers."""

View File

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

View File

@ -158,7 +158,7 @@ class ZwaveGarageDoorBarrier(ZwaveGarageDoorBase):
@property @property
def is_closing(self): 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" return self._state == "Closing"
@property @property

View File

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

View File

@ -42,7 +42,7 @@ Device = namedtuple('Device', ['mac', 'ip', 'last_update'])
class ActiontecDeviceScanner(DeviceScanner): 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): def __init__(self, config):
"""Initialize the scanner.""" """Initialize the scanner."""

View File

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

View File

@ -2,7 +2,7 @@
Support for HUAWEI routers. Support for HUAWEI routers.
For more details about this platform, please refer to the documentation at For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/device_tracker.huawei/ https://home-assistant.io/components/device_tracker.huawei_router/
""" """
import base64 import base64
import logging import logging
@ -119,7 +119,7 @@ class HuaweiDeviceScanner(DeviceScanner):
cnt = requests.post('http://{}/asp/GetRandCount.asp'.format(self.host)) cnt = requests.post('http://{}/asp/GetRandCount.asp'.format(self.host))
cnt_str = str(cnt.content, cnt.apparent_encoding, errors='replace') 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), cookie = requests.post('http://{}/login.cgi'.format(self.host),
data=[('UserName', self.username), data=[('UserName', self.username),
('PassWord', self.password), ('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 ( from homeassistant.const import (
CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT) CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT)
REQUIREMENTS = ['librouteros==1.0.4'] REQUIREMENTS = ['librouteros==1.0.5']
MTK_DEFAULT_API_PORT = '8728' 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] devices = config[CONF_DEVICES]
qos = config[CONF_QOS] qos = config[CONF_QOS]
dev_id_lookup = {} for dev_id, topic in devices.items():
@callback @callback
def async_tracker_message_received(topic, payload, qos): def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message.""" """Handle received MQTT message."""
hass.async_add_job( hass.async_add_job(
async_see(dev_id=dev_id_lookup[topic], location_name=payload)) async_see(dev_id=dev_id, location_name=payload))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
yield from mqtt.async_subscribe( yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos) hass, topic, async_message_received, qos)
return True return True

View File

@ -41,13 +41,10 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
devices = config[CONF_DEVICES] devices = config[CONF_DEVICES]
qos = config[CONF_QOS] qos = config[CONF_QOS]
dev_id_lookup = {} for dev_id, topic in devices.items():
@callback @callback
def async_tracker_message_received(topic, payload, qos): def async_message_received(topic, payload, qos, dev_id=dev_id):
"""Handle received MQTT message.""" """Handle received MQTT message."""
dev_id = dev_id_lookup[topic]
try: try:
data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload)) data = GPS_JSON_PAYLOAD_SCHEMA(json.loads(payload))
except vol.MultipleInvalid: except vol.MultipleInvalid:
@ -60,13 +57,10 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None):
return return
kwargs = _parse_see_args(dev_id, data) kwargs = _parse_see_args(dev_id, data)
hass.async_add_job( hass.async_add_job(async_see(**kwargs))
async_see(**kwargs))
for dev_id, topic in devices.items():
dev_id_lookup[topic] = dev_id
yield from mqtt.async_subscribe( yield from mqtt.async_subscribe(
hass, topic, async_tracker_message_received, qos) hass, topic, async_message_received, qos)
return True return True

View File

@ -143,6 +143,8 @@ def _parse_see_args(message, subscribe_topic):
kwargs['attributes']['tid'] = message['tid'] kwargs['attributes']['tid'] = message['tid']
if 'addr' in message: if 'addr' in message:
kwargs['attributes']['address'] = message['addr'] kwargs['attributes']['address'] = message['addr']
if 'cog' in message:
kwargs['attributes']['course'] = message['cog']
if 't' in message: if 't' in message:
if message['t'] == 'c': if message['t'] == 'c':
kwargs['attributes'][ATTR_SOURCE_TYPE] = SOURCE_TYPE_GPS 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 return scanner if scanner.success_init else None
def _refresh_on_acccess_denied(func): def _refresh_on_access_denied(func):
"""If remove rebooted, it lost our session so rebuld one and try again.""" """If remove rebooted, it lost our session so rebuild one and try again."""
def decorator(self, *args, **kwargs): def decorator(self, *args, **kwargs):
"""Wrap the function to refresh session_id on PermissionError.""" """Wrap the function to refresh session_id on PermissionError."""
try: try:
@ -95,16 +95,15 @@ class UbusDeviceScanner(DeviceScanner):
"""Must be implemented depending on the software.""" """Must be implemented depending on the software."""
raise NotImplementedError raise NotImplementedError
@_refresh_on_acccess_denied @_refresh_on_access_denied
def get_device_name(self, mac): def get_device_name(self, mac):
"""Return the name of the given device or None if we don't know.""" """Return the name of the given device or None if we don't know."""
if self.mac2name is None: if self.mac2name is None:
self._generate_mac2name() self._generate_mac2name()
name = self.mac2name.get(mac.upper(), None) name = self.mac2name.get(mac.upper(), None)
self.mac2name = None
return name return name
@_refresh_on_acccess_denied @_refresh_on_access_denied
def _update_info(self): def _update_info(self):
"""Ensure the information from the router is up to date. """Ensure the information from the router is up to date.
@ -122,13 +121,18 @@ class UbusDeviceScanner(DeviceScanner):
self.last_results = [] self.last_results = []
results = 0 results = 0
# for each access point
for hostapd in self.hostapd: for hostapd in self.hostapd:
result = _req_json_rpc( result = _req_json_rpc(
self.url, self.session_id, 'call', hostapd, 'get_clients') self.url, self.session_id, 'call', hostapd, 'get_clients')
if result: if result:
results = results + 1 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) return bool(results)

View File

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

View File

@ -1,7 +1,7 @@
""" """
Support for Dominos Pizza ordering. 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 from their menu
For more details about this platform, please refer to the documentation at 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): class DoorbirdRequestView(HomeAssistantView):
"""Provide a page for the device to call.""" """Provide a page for the device to call."""
requires_auth = False
url = API_URL url = API_URL
name = API_URL[1:].replace('/', ':') name = API_URL[1:].replace('/', ':')
extra_urls = [API_URL + '/{sensor}'] extra_urls = [API_URL + '/{sensor}']

View File

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

View File

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

View File

@ -59,7 +59,7 @@ def setup(hass, config):
payload, fullurl, req.status_code) payload, fullurl, req.status_code)
def update_emoncms(time): def update_emoncms(time):
"""Send whitelisted entities states reguarly to Emoncms.""" """Send whitelisted entities states regularly to Emoncms."""
payload_dict = {} payload_dict = {}
for entity_id in whitelist: 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_EXPOSE_BY_DEFAULT = 'expose_by_default'
CONF_EXPOSED_DOMAINS = 'exposed_domains' CONF_EXPOSED_DOMAINS = 'exposed_domains'
CONF_TYPE = 'type' CONF_TYPE = 'type'
CONF_ENTITIES = 'entities'
CONF_ENTITY_NAME = 'name'
CONF_ENTITY_HIDDEN = 'hidden'
TYPE_ALEXA = 'alexa' TYPE_ALEXA = 'alexa'
TYPE_GOOGLE = 'google_home' TYPE_GOOGLE = 'google_home'
@ -52,6 +55,11 @@ DEFAULT_EXPOSED_DOMAINS = [
] ]
DEFAULT_TYPE = TYPE_GOOGLE 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({ CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.Schema({ DOMAIN: vol.Schema({
vol.Optional(CONF_HOST_IP): cv.string, 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_EXPOSE_BY_DEFAULT): cv.boolean,
vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list, vol.Optional(CONF_EXPOSED_DOMAINS): cv.ensure_list,
vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): 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) }, extra=vol.ALLOW_EXTRA)
ATTR_EMULATED_HUE = 'emulated_hue' ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden' ATTR_EMULATED_HUE_HIDDEN = 'emulated_hue_hidden'
@ -183,6 +194,8 @@ class Config(object):
self.advertise_port = conf.get( self.advertise_port = conf.get(
CONF_ADVERTISE_PORT) or self.listen_port CONF_ADVERTISE_PORT) or self.listen_port
self.entities = conf.get(CONF_ENTITIES, {})
def entity_id_to_number(self, entity_id): def entity_id_to_number(self, entity_id):
"""Get a unique number for the entity id.""" """Get a unique number for the entity id."""
if self.type == TYPE_ALEXA: if self.type == TYPE_ALEXA:
@ -215,6 +228,14 @@ class Config(object):
assert isinstance(number, str) assert isinstance(number, str)
return self.numbers.get(number) 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): def is_entity_exposed(self, entity):
"""Determine if an entity should be exposed on the emulated bridge. """Determine if an entity should be exposed on the emulated bridge.
@ -227,6 +248,12 @@ class Config(object):
domain = entity.domain.lower() domain = entity.domain.lower()
explicit_expose = entity.attributes.get(ATTR_EMULATED_HUE, None) explicit_expose = entity.attributes.get(ATTR_EMULATED_HUE, None)
explicit_hidden = entity.attributes.get(ATTR_EMULATED_HUE_HIDDEN, 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: if explicit_expose is True or explicit_hidden is False:
expose = True expose = True
elif explicit_expose is False or explicit_hidden is 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__) _LOGGER = logging.getLogger(__name__)
ATTR_EMULATED_HUE = 'emulated_hue'
ATTR_EMULATED_HUE_NAME = 'emulated_hue_name'
HUE_API_STATE_ON = 'on' HUE_API_STATE_ON = 'on'
HUE_API_STATE_BRI = 'bri' HUE_API_STATE_BRI = 'bri'
@ -77,7 +74,7 @@ class HueAllLightsStateView(HomeAssistantView):
number = self.config.entity_id_to_number(entity.entity_id) number = self.config.entity_id_to_number(entity.entity_id)
json_response[number] = entity_to_json( json_response[number] = entity_to_json(
entity, state, brightness) self.config, entity, state, brightness)
return self.json(json_response) return self.json(json_response)
@ -110,7 +107,7 @@ class HueOneLightStateView(HomeAssistantView):
state, brightness = get_entity_state(self.config, entity) 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) return self.json(json_response)
@ -344,10 +341,8 @@ def get_entity_state(config, entity):
return (final_state, final_brightness) 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.""" """Convert an entity to its Hue bridge JSON representation."""
name = entity.attributes.get(ATTR_EMULATED_HUE_NAME, entity.name)
return { return {
'state': 'state':
{ {
@ -356,7 +351,7 @@ def entity_to_json(entity, is_on=None, brightness=None):
'reachable': True 'reachable': True
}, },
'type': 'Dimmable light', 'type': 'Dimmable light',
'name': name, 'name': config.get_entity_name(entity),
'modelid': 'HASS123', 'modelid': 'HASS123',
'uniqueid': entity.entity_id, 'uniqueid': entity.entity_id,
'swversion': '123' 'swversion': '123'

View File

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

View File

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

View File

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

View File

@ -29,7 +29,7 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, 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_TEMPERATURE = 'temperature'
ATTR_HUMIDITY = 'humidity' ATTR_HUMIDITY = 'humidity'
@ -200,7 +200,7 @@ class XiaomiAirPurifier(FanEntity):
@asyncio.coroutine @asyncio.coroutine
def _try_command(self, mask_error, func, *args, **kwargs): 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 from miio import DeviceException
try: try:
result = yield from self.hass.async_add_job( 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: with self._lock, open(self._data_file, 'rb') as myfile:
self._data = pickle.load(myfile) or {} self._data = pickle.load(myfile) or {}
self._cache_outdated = False self._cache_outdated = False
# pylint: disable=bare-except except: # noqa: E722 # pylint: disable=bare-except
except:
_LOGGER.error("Error loading data from pickled file %s", _LOGGER.error("Error loading data from pickled file %s",
self._data_file) self._data_file)
@ -172,8 +171,7 @@ class StoredData(object):
url, self._data_file) url, self._data_file)
try: try:
pickle.dump(self._data, myfile) pickle.dump(self._data, myfile)
# pylint: disable=bare-except except: # noqa: E722 # pylint: disable=bare-except
except:
_LOGGER.error( _LOGGER.error(
"Error saving pickled data to %s", self._data_file) "Error saving pickled data to %s", self._data_file)
self._cache_outdated = True 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.core import callback
from homeassistant.loader import bind_hass 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' DOMAIN = 'frontend'
DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log'] DEPENDENCIES = ['api', 'websocket_api', 'http', 'system_log']
@ -408,7 +408,7 @@ def async_setup_themes(hass, themes):
@callback @callback
def set_theme(call): def set_theme(call):
"""Set backend-prefered theme.""" """Set backend-preferred theme."""
data = call.data data = call.data
name = data[CONF_NAME] name = data[CONF_NAME]
if name == DEFAULT_THEME or name in hass.data[DATA_THEMES]: 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 return useragent.os.version[0] >= 12
family_min_version = { family_min_version = {
'Chrome': 50, # Probably can reduce this 'Chrome': 54, # Object.values
'Firefox': 43, # Array.protopype.includes added in 43 'Chrome Mobile': 54,
'Opera': 40, # Probably can reduce this 'Firefox': 47, # Object.values
'Edge': 14, # Array.protopype.includes added in 14 'Firefox Mobile': 47,
'Safari': 10, # many features not supported by 9 '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) version = family_min_version.get(useragent.browser.family)
return version and useragent.browser.version[0] >= version 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.""" """Keep trying to validate the user_code until it expires."""
if now >= dt.as_local(dev_flow.user_code_expiry): if now >= dt.as_local(dev_flow.user_code_expiry):
hass.components.persistent_notification.create( hass.components.persistent_notification.create(
'Authenication code expired, please restart ' 'Authentication code expired, please restart '
'Home-Assistant and try again', 'Home-Assistant and try again',
title=NOTIFICATION_TITLE, title=NOTIFICATION_TITLE,
notification_id=NOTIFICATION_ID) notification_id=NOTIFICATION_ID)

View File

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

View File

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

View File

@ -320,7 +320,7 @@ def setup(hass: HomeAssistant, base_config):
class CecDevice(Entity): class CecDevice(Entity):
"""Representation of a HDMI CEC device 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.""" """Initialize the device."""
self._device = device self._device = device
self.hass = hass self.hass = hass

View File

@ -20,7 +20,7 @@ from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv import homeassistant.helpers.config_validation as cv
from homeassistant.loader import bind_hass from homeassistant.loader import bind_hass
REQUIREMENTS = ['pyhomematic==0.1.38'] REQUIREMENTS = ['pyhomematic==0.1.39']
DOMAIN = 'homematic' DOMAIN = 'homematic'
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -218,7 +218,7 @@ SCHEMA_SERVICE_SET_INSTALL_MODE = vol.Schema({
@bind_hass @bind_hass
def virtualkey(hass, address, channel, param, interface=None): def virtualkey(hass, address, channel, param, interface=None):
"""Send virtual keypress to homematic controlller.""" """Send virtual keypress to homematic controller."""
data = { data = {
ATTR_ADDRESS: address, ATTR_ADDRESS: address,
ATTR_CHANNEL: channel, ATTR_CHANNEL: channel,
@ -256,7 +256,7 @@ def set_device_value(hass, address, channel, param, value, interface=None):
@bind_hass @bind_hass
def set_install_mode(hass, interface, mode=None, time=None, address=None): 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 = { data = {
key: value for key, value in ( key: value for key, value in (
(ATTR_INTERFACE, interface), (ATTR_INTERFACE, interface),
@ -466,7 +466,7 @@ def _system_callback_handler(hass, config, src, *args):
hass, discovery_type, addresses, interface) hass, discovery_type, addresses, interface)
# When devices of this type are found # 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: if found_devices:
discovery.load_platform(hass, component_name, DOMAIN, { discovery.load_platform(hass, component_name, DOMAIN, {
ATTR_DISCOVER_DEVICES: found_devices ATTR_DISCOVER_DEVICES: found_devices
@ -665,7 +665,7 @@ class HMHub(Entity):
self.schedule_update_ha_state() self.schedule_update_ha_state()
def _update_variables(self, now): 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) variables = self._homematic.getAllSystemVariables(self._name)
if variables is None: if variables is None:
return return

View File

@ -13,7 +13,7 @@ virtualkey:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT. description: Event to send i.e. PRESS_LONG, PRESS_SHORT.
example: PRESS_LONG example: PRESS_LONG
interface: interface:
description: (Optional) for set a interface value. description: (Optional) for set an interface value.
example: Interfaces name from config example: Interfaces name from config
set_variable_value: set_variable_value:
@ -42,7 +42,7 @@ set_device_value:
description: Event to send i.e. PRESS_LONG, PRESS_SHORT description: Event to send i.e. PRESS_LONG, PRESS_SHORT
example: PRESS_LONG example: PRESS_LONG
interface: interface:
description: (Optional) for set a interface value description: (Optional) for set an interface value
example: Interfaces name from config example: Interfaces name from config
value: value:
description: New 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 no password set, just always set authenticated=True
if request.app['hass'].http.api_password is None: if request.app['hass'].http.api_password is None:
request[KEY_AUTHENTICATED] = True request[KEY_AUTHENTICATED] = True
return handler(request) return (yield from handler(request))
# Check authentication # Check authentication
authenticated = False authenticated = False
@ -46,7 +46,7 @@ def auth_middleware(request, handler):
authenticated = True authenticated = True
request[KEY_AUTHENTICATED] = authenticated request[KEY_AUTHENTICATED] = authenticated
return handler(request) return (yield from handler(request))
def is_trusted_ip(request): def is_trusted_ip(request):

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