Merge remote-tracking branch 'refs/remotes/balloob/dev' into netatmo

This commit is contained in:
hydreliox 2016-01-13 09:03:44 +01:00
commit 4f13236008
58 changed files with 2633 additions and 886 deletions

View File

@ -36,6 +36,9 @@ omit =
homeassistant/components/rfxtrx.py
homeassistant/components/*/rfxtrx.py
homeassistant/components/mysensors.py
homeassistant/components/*/mysensors.py
homeassistant/components/binary_sensor/arest.py
homeassistant/components/binary_sensor/rest.py
homeassistant/components/browser.py
@ -73,6 +76,7 @@ omit =
homeassistant/components/media_player/plex.py
homeassistant/components/media_player/sonos.py
homeassistant/components/media_player/squeezebox.py
homeassistant/components/notify/free_mobile.py
homeassistant/components/notify/instapush.py
homeassistant/components/notify/nma.py
homeassistant/components/notify/pushbullet.py
@ -92,7 +96,6 @@ omit =
homeassistant/components/sensor/eliqonline.py
homeassistant/components/sensor/forecast.py
homeassistant/components/sensor/glances.py
homeassistant/components/sensor/mysensors.py
homeassistant/components/sensor/openweathermap.py
homeassistant/components/sensor/rest.py
homeassistant/components/sensor/rpi_gpio.py

View File

@ -87,13 +87,21 @@ def setup(hass, config):
lambda item: util.split_entity_id(item)[0])
for domain, ent_ids in by_domain:
# We want to block for all calls and only return when all calls
# have been processed. If a service does not exist it causes a 10
# second delay while we're blocking waiting for a response.
# But services can be registered on other HA instances that are
# listening to the bus too. So as a in between solution, we'll
# block only if the service is defined in the current HA instance.
blocking = hass.services.has_service(domain, service.service)
# Create a new dict for this call
data = dict(service.data)
# ent_ids is a generator, convert it to a list.
data[ATTR_ENTITY_ID] = list(ent_ids)
hass.services.call(domain, service.service, data, True)
hass.services.call(domain, service.service, data, blocking)
hass.services.register(ha.DOMAIN, SERVICE_TURN_OFF, handle_turn_service)
hass.services.register(ha.DOMAIN, SERVICE_TURN_ON, handle_turn_service)

View File

@ -68,7 +68,8 @@ class ManualAlarm(alarm.AlarmControlPanel):
@property
def state(self):
""" Returns the state of the device. """
if self._state in (STATE_ALARM_ARMED_HOME, STATE_ALARM_ARMED_AWAY) and \
if self._state in (STATE_ALARM_ARMED_HOME,
STATE_ALARM_ARMED_AWAY) and \
self._pending_time and self._state_ts + self._pending_time > \
dt_util.utcnow():
return STATE_ALARM_PENDING

View File

@ -67,7 +67,7 @@ class VerisureAlarm(alarm.AlarmControlPanel):
self._state = STATE_ALARM_DISARMED
elif verisure.ALARM_STATUS[self._id].status == 'armedhome':
self._state = STATE_ALARM_ARMED_HOME
elif verisure.ALARM_STATUS[self._id].status == 'armedaway':
elif verisure.ALARM_STATUS[self._id].status == 'armed':
self._state = STATE_ALARM_ARMED_AWAY
elif verisure.ALARM_STATUS[self._id].status != 'pending':
_LOGGER.error(

View File

@ -11,6 +11,7 @@ import logging
from homeassistant.const import HTTP_OK, HTTP_UNPROCESSABLE_ENTITY
from homeassistant.util import template
from homeassistant.helpers.service import call_from_config
DOMAIN = 'alexa'
DEPENDENCIES = ['http']
@ -23,6 +24,7 @@ API_ENDPOINT = '/api/alexa'
CONF_INTENTS = 'intents'
CONF_CARD = 'card'
CONF_SPEECH = 'speech'
CONF_ACTION = 'action'
def setup(hass, config):
@ -80,6 +82,7 @@ def _handle_alexa(handler, path_match, data):
speech = config.get(CONF_SPEECH)
card = config.get(CONF_CARD)
action = config.get(CONF_ACTION)
# pylint: disable=unsubscriptable-object
if speech is not None:
@ -89,6 +92,9 @@ def _handle_alexa(handler, path_match, data):
response.add_card(CardType[card['type']], card['title'],
card['content'])
if action is not None:
call_from_config(handler.server.hass, action, True)
handler.write_json(response.as_dict())

View File

@ -9,9 +9,9 @@ https://home-assistant.io/components/automation/
import logging
from homeassistant.bootstrap import prepare_setup_platform
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM
from homeassistant.const import CONF_PLATFORM
from homeassistant.components import logbook
from homeassistant.helpers.service import call_from_config
DOMAIN = 'automation'
@ -19,8 +19,6 @@ DEPENDENCIES = ['group']
CONF_ALIAS = 'alias'
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
CONF_CONDITION = 'condition'
CONF_ACTION = 'action'
@ -96,22 +94,7 @@ def _get_action(hass, config, name):
_LOGGER.info('Executing %s', name)
logbook.log_entry(hass, name, 'has been triggered', DOMAIN)
domain, service = split_entity_id(config[CONF_SERVICE])
service_data = config.get(CONF_SERVICE_DATA, {})
if not isinstance(service_data, dict):
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
if CONF_SERVICE_ENTITY_ID in config:
try:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID].split(",")
except AttributeError:
service_data[ATTR_ENTITY_ID] = \
config[CONF_SERVICE_ENTITY_ID]
hass.services.call(domain, service, service_data)
call_from_config(hass, config)
return action

View File

@ -6,6 +6,7 @@ Offers numeric state listening automation rules.
For more details about this automation rule, please refer to the documentation
at https://home-assistant.io/components/automation/#numeric-state-trigger
"""
from functools import partial
import logging
from homeassistant.const import CONF_VALUE_TEMPLATE
@ -20,6 +21,14 @@ CONF_ABOVE = "above"
_LOGGER = logging.getLogger(__name__)
def _renderer(hass, value_template, state):
"""Render state value."""
if value_template is None:
return state.state
return template.render(hass, value_template, {'state': state})
def trigger(hass, config, action):
""" Listen for state changes based on `config`. """
entity_id = config.get(CONF_ENTITY_ID)
@ -38,12 +47,7 @@ def trigger(hass, config, action):
CONF_BELOW, CONF_ABOVE)
return False
if value_template is not None:
renderer = lambda value: template.render(hass,
value_template,
{'state': value})
else:
renderer = lambda value: value.state
renderer = partial(_renderer, hass, value_template)
# pylint: disable=unused-argument
def state_automation_listener(entity, from_s, to_s):
@ -79,12 +83,7 @@ def if_action(hass, config):
CONF_BELOW, CONF_ABOVE)
return None
if value_template is not None:
renderer = lambda value: template.render(hass,
value_template,
{'state': value})
else:
renderer = lambda value: value.state
renderer = partial(_renderer, hass, value_template)
def if_numeric_state():
""" Test numeric state condition. """

View File

@ -80,18 +80,30 @@ def if_action(hass, config):
return None
if before is None:
before_func = lambda: None
def before_func():
"""Return no point in time."""
return None
elif before == EVENT_SUNRISE:
before_func = lambda: sun.next_rising(hass) + before_offset
def before_func():
"""Return time before sunrise."""
return sun.next_rising(hass) + before_offset
else:
before_func = lambda: sun.next_setting(hass) + before_offset
def before_func():
"""Return time before sunset."""
return sun.next_setting(hass) + before_offset
if after is None:
after_func = lambda: None
def after_func():
"""Return no point in time."""
return None
elif after == EVENT_SUNRISE:
after_func = lambda: sun.next_rising(hass) + after_offset
def after_func():
"""Return time after sunrise."""
return sun.next_rising(hass) + after_offset
else:
after_func = lambda: sun.next_setting(hass) + after_offset
def after_func():
"""Return time after sunset."""
return sun.next_setting(hass) + after_offset
def time_if():
""" Validate time based if-condition """

View File

@ -32,8 +32,8 @@ def trigger(hass, config, action):
_error_time(config[CONF_AFTER], CONF_AFTER)
return False
hours, minutes, seconds = after.hour, after.minute, after.second
elif (CONF_HOURS in config or CONF_MINUTES in config
or CONF_SECONDS in config):
elif (CONF_HOURS in config or CONF_MINUTES in config or
CONF_SECONDS in config):
hours = convert(config.get(CONF_HOURS), int)
minutes = convert(config.get(CONF_MINUTES), int)
seconds = convert(config.get(CONF_SECONDS), int)

View File

@ -58,8 +58,8 @@ class AsusWrtDeviceScanner(object):
def __init__(self, config):
self.host = config[CONF_HOST]
self.username = config[CONF_USERNAME]
self.password = config[CONF_PASSWORD]
self.username = str(config[CONF_USERNAME])
self.password = str(config[CONF_PASSWORD])
self.lock = threading.Lock()

View File

@ -71,7 +71,7 @@ def setup_scanner(hass, config, see):
location = ''
if data['event'] == 'enter':
if data['desc'] == 'home':
if data['desc'].lower() == 'home':
location = STATE_HOME
else:
location = data['desc']

View File

@ -105,8 +105,7 @@ class SnmpScanner(object):
return
if errstatus:
_LOGGER.error('SNMP error: %s at %s', errstatus.prettyPrint(),
errindex and restable[-1][int(errindex)-1]
or '?')
errindex and restable[-1][int(errindex)-1] or '?')
return
for resrow in restable:

View File

@ -242,8 +242,8 @@ class Tplink3DeviceScanner(TplinkDeviceScanner):
_LOGGER.info("Loading wireless clients...")
url = 'http://{}/cgi-bin/luci/;stok={}/admin/wireless?form=statistics' \
.format(self.host, self.stok)
url = ('http://{}/cgi-bin/luci/;stok={}/admin/wireless?'
'form=statistics').format(self.host, self.stok)
referer = 'http://{}/webpages/index.html'.format(self.host)
response = requests.post(url,

View File

@ -1,2 +1,2 @@
""" DO NOT MODIFY. Auto-generated by build_frontend script """
VERSION = "72a8220d0db0f7f3702228cd556b8c40"
VERSION = "63d38b69fc6582e75f892abc140a893a"

File diff suppressed because one or more lines are too long

@ -1 +1 @@
Subproject commit 78c348cb7b0a60ba015e3b652e538155d3e94a11
Subproject commit 7def0c85efbfe7a11a64560c21cb83059a5c7a3b

View File

@ -198,12 +198,12 @@ class RequestHandler(SimpleHTTPRequestHandler):
"Error parsing JSON", HTTP_UNPROCESSABLE_ENTITY)
return
self.authenticated = (self.server.api_password is None
or self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password
or data.get(DATA_API_PASSWORD) ==
self.server.api_password
or self.verify_session())
self.authenticated = (self.server.api_password is None or
self.headers.get(HTTP_HEADER_HA_AUTH) ==
self.server.api_password or
data.get(DATA_API_PASSWORD) ==
self.server.api_password or
self.verify_session())
if '_METHOD' in data:
method = data.pop('_METHOD')

View File

@ -13,8 +13,9 @@ from homeassistant.components.light import Light
from homeassistant.util import slugify
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.rfxtrx import ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, \
ATTR_NAME, EVENT_BUTTON_PRESSED
from homeassistant.components.rfxtrx import (
ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID,
ATTR_NAME, EVENT_BUTTON_PRESSED)
DEPENDENCIES = ['rfxtrx']

View File

@ -7,16 +7,15 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/light.vera/
"""
import logging
import time
from requests.exceptions import RequestException
from homeassistant.components.switch.vera import VeraSwitch
from homeassistant.components.light import ATTR_BRIGHTNESS
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.const import EVENT_HOMEASSISTANT_STOP, STATE_ON
REQUIREMENTS = ['pyvera==0.2.2']
REQUIREMENTS = ['pyvera==0.2.3']
_LOGGER = logging.getLogger(__name__)
@ -59,7 +58,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
lights = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
@ -86,5 +85,5 @@ class VeraLight(VeraSwitch):
else:
self.vera_device.switch_on()
self.last_command_send = time.time()
self.is_on_status = True
self._state = STATE_ON
self.update_ha_state()

View File

@ -72,6 +72,7 @@ SUPPORT_YOUTUBE = 64
SUPPORT_TURN_ON = 128
SUPPORT_TURN_OFF = 256
SUPPORT_PLAY_MEDIA = 512
SUPPORT_VOLUME_STEP = 1024
YOUTUBE_COVER_URL_FORMAT = 'https://img.youtube.com/vi/{}/1.jpg'

View File

@ -20,7 +20,7 @@ from homeassistant.components.media_player import (
SUPPORT_PREVIOUS_TRACK, SUPPORT_NEXT_TRACK,
MEDIA_TYPE_MUSIC, MEDIA_TYPE_TVSHOW, MEDIA_TYPE_VIDEO)
REQUIREMENTS = ['pychromecast==0.6.13']
REQUIREMENTS = ['pychromecast==0.6.14']
CONF_IGNORE_CEC = 'ignore_cec'
CAST_SPLASH = 'https://home-assistant.io/images/cast/splash.png'
SUPPORT_CAST = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE | \

View File

@ -35,7 +35,7 @@ SUPPORT_PLEX = SUPPORT_PAUSE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK
def config_from_file(filename, config=None):
''' Small configuration file management function'''
""" Small configuration file management function. """
if config:
# We're writing configuration
try:
@ -85,7 +85,7 @@ def setup_platform(hass, config, add_devices_callback, discovery_info=None):
# pylint: disable=too-many-branches
def setup_plexserver(host, token, hass, add_devices_callback):
''' Setup a plexserver based on host parameter'''
""" Setup a plexserver based on host parameter. """
import plexapi.server
import plexapi.exceptions

View File

@ -22,9 +22,9 @@ from homeassistant.const import (
_LOGGER = logging.getLogger(__name__)
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | SUPPORT_VOLUME_MUTE |\
SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | SUPPORT_SEEK |\
SUPPORT_TURN_ON | SUPPORT_TURN_OFF
SUPPORT_SQUEEZEBOX = SUPPORT_PAUSE | SUPPORT_VOLUME_SET | \
SUPPORT_VOLUME_MUTE | SUPPORT_PREVIOUS_TRACK | SUPPORT_NEXT_TRACK | \
SUPPORT_SEEK | SUPPORT_TURN_ON | SUPPORT_TURN_OFF
# pylint: disable=unused-argument
@ -202,9 +202,8 @@ class SqueezeBoxDevice(MediaPlayerDevice):
""" Image url of current playing media. """
if 'artwork_url' in self._status:
return self._status['artwork_url']
return 'http://{server}:{port}/music/current/cover.jpg?player={player}'\
.format(
server=self._lms.host,
return ('http://{server}:{port}/music/current/cover.jpg?'
'player={player}').format(server=self._lms.host,
port=self._lms.http_port,
player=self._id)

View File

@ -0,0 +1,438 @@
"""
homeassistant.components.media_player.universal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Combines multiple media players into one for a universal controller.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/media_player.universal/
"""
# pylint: disable=import-error
from copy import copy
import logging
from homeassistant.helpers.event import track_state_change
from homeassistant.helpers.service import call_from_config
from homeassistant.const import (
STATE_IDLE, STATE_ON, STATE_OFF, CONF_NAME,
ATTR_ENTITY_ID, ATTR_ENTITY_PICTURE,
SERVICE_TURN_OFF, SERVICE_TURN_ON,
SERVICE_VOLUME_UP, SERVICE_VOLUME_DOWN, SERVICE_VOLUME_SET,
SERVICE_VOLUME_MUTE,
SERVICE_MEDIA_PLAY_PAUSE, SERVICE_MEDIA_PLAY, SERVICE_MEDIA_PAUSE,
SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_SEEK)
from homeassistant.components.media_player import (
MediaPlayerDevice, DOMAIN,
SUPPORT_VOLUME_STEP, SUPPORT_VOLUME_SET, SUPPORT_VOLUME_MUTE,
SUPPORT_TURN_ON, SUPPORT_TURN_OFF,
SERVICE_PLAY_MEDIA, SERVICE_YOUTUBE_VIDEO,
ATTR_SUPPORTED_MEDIA_COMMANDS, ATTR_MEDIA_VOLUME_MUTED,
ATTR_MEDIA_CONTENT_ID, ATTR_MEDIA_CONTENT_TYPE, ATTR_MEDIA_DURATION,
ATTR_MEDIA_TITLE, ATTR_MEDIA_ARTIST, ATTR_MEDIA_ALBUM_NAME,
ATTR_MEDIA_TRACK, ATTR_MEDIA_SERIES_TITLE, ATTR_MEDIA_ALBUM_ARTIST,
ATTR_MEDIA_SEASON, ATTR_MEDIA_EPISODE, ATTR_MEDIA_CHANNEL,
ATTR_MEDIA_PLAYLIST, ATTR_APP_ID, ATTR_APP_NAME, ATTR_MEDIA_VOLUME_LEVEL,
ATTR_MEDIA_SEEK_POSITION)
ATTR_ACTIVE_CHILD = 'active_child'
CONF_ATTRS = 'attributes'
CONF_CHILDREN = 'children'
CONF_COMMANDS = 'commands'
CONF_PLATFORM = 'platform'
CONF_SERVICE = 'service'
CONF_SERVICE_DATA = 'service_data'
CONF_STATE = 'state'
OFF_STATES = [STATE_IDLE, STATE_OFF]
REQUIREMENTS = []
_LOGGER = logging.getLogger(__name__)
# pylint: disable=unused-argument
def setup_platform(hass, config, add_devices, discovery_info=None):
""" sets up the universal media players """
if not validate_config(config):
return
player = UniversalMediaPlayer(hass,
config[CONF_NAME],
config[CONF_CHILDREN],
config[CONF_COMMANDS],
config[CONF_ATTRS])
add_devices([player])
def validate_config(config):
""" validate universal media player configuration """
del config[CONF_PLATFORM]
# validate name
if CONF_NAME not in config:
_LOGGER.error('Universal Media Player configuration requires name')
return False
validate_children(config)
validate_commands(config)
validate_attributes(config)
del_keys = []
for key in config:
if key not in [CONF_NAME, CONF_CHILDREN, CONF_COMMANDS, CONF_ATTRS]:
_LOGGER.warning(
'Universal Media Player (%s) unrecognized parameter %s',
config[CONF_NAME], key)
del_keys.append(key)
for key in del_keys:
del config[key]
return True
def validate_children(config):
""" validate children """
if CONF_CHILDREN not in config:
_LOGGER.info(
'No children under Universal Media Player (%s)', config[CONF_NAME])
config[CONF_CHILDREN] = []
elif not isinstance(config[CONF_CHILDREN], list):
_LOGGER.warning(
'Universal Media Player (%s) children not list in config. '
'They will be ignored.',
config[CONF_NAME])
config[CONF_CHILDREN] = []
def validate_commands(config):
""" validate commands """
if CONF_COMMANDS not in config:
config[CONF_COMMANDS] = {}
elif not isinstance(config[CONF_COMMANDS], dict):
_LOGGER.warning(
'Universal Media Player (%s) specified commands not dict in '
'config. They will be ignored.',
config[CONF_NAME])
config[CONF_COMMANDS] = {}
def validate_attributes(config):
""" validate attributes """
if CONF_ATTRS not in config:
config[CONF_ATTRS] = {}
elif not isinstance(config[CONF_ATTRS], dict):
_LOGGER.warning(
'Universal Media Player (%s) specified attributes '
'not dict in config. They will be ignored.',
config[CONF_NAME])
config[CONF_ATTRS] = {}
for key, val in config[CONF_ATTRS].items():
attr = val.split('|', 1)
if len(attr) == 1:
attr.append(None)
config[CONF_ATTRS][key] = attr
class UniversalMediaPlayer(MediaPlayerDevice):
""" Represents a universal media player in HA """
# pylint: disable=too-many-public-methods
def __init__(self, hass, name, children, commands, attributes):
# pylint: disable=too-many-arguments
self.hass = hass
self._name = name
self._children = children
self._cmds = commands
self._attrs = attributes
self._child_state = None
def on_dependency_update(*_):
""" update ha state when dependencies update """
self.update_ha_state(True)
depend = copy(children)
for entity in attributes.values():
depend.append(entity[0])
track_state_change(hass, depend, on_dependency_update)
def _entity_lkp(self, entity_id, state_attr=None):
""" Looks up an entity state from hass """
state_obj = self.hass.states.get(entity_id)
if state_obj is None:
return
if state_attr:
return state_obj.attributes.get(state_attr)
return state_obj.state
def _override_or_child_attr(self, attr_name):
""" returns either the override or the active child for attr_name """
if attr_name in self._attrs:
return self._entity_lkp(self._attrs[attr_name][0],
self._attrs[attr_name][1])
return self._child_attr(attr_name)
def _child_attr(self, attr_name):
""" returns the active child's attr """
active_child = self._child_state
return active_child.attributes.get(attr_name) if active_child else None
def _call_service(self, service_name, service_data=None,
allow_override=False):
""" calls either a specified or active child's service """
if allow_override and service_name in self._cmds:
call_from_config(
self.hass, self._cmds[service_name], blocking=True)
return
if service_data is None:
service_data = {}
active_child = self._child_state
service_data[ATTR_ENTITY_ID] = active_child.entity_id
self.hass.services.call(DOMAIN, service_name, service_data,
blocking=True)
@property
def should_poll(self):
""" Indicates whether HA should poll for updates """
return False
@property
def master_state(self):
""" gets the master state from entity or none """
if CONF_STATE in self._attrs:
master_state = self._entity_lkp(self._attrs[CONF_STATE][0],
self._attrs[CONF_STATE][1])
return master_state if master_state else STATE_OFF
else:
return None
def _cache_active_child_state(self):
""" The state of the active child or None """
for child_name in self._children:
child_state = self.hass.states.get(child_name)
if child_state and child_state.state not in OFF_STATES:
self._child_state = child_state
return
self._child_state = None
@property
def name(self):
""" name of universal player """
return self._name
@property
def state(self):
"""
Current state of media player
Off if master state is off
ELSE Status of first active child
ELSE master state or off
"""
master_state = self.master_state # avoid multiple lookups
if master_state == STATE_OFF:
return STATE_OFF
active_child = self._child_state
if active_child:
return active_child.state
return master_state if master_state else STATE_OFF
@property
def volume_level(self):
""" Volume level of entity specified in attributes or active child """
return self._child_attr(ATTR_MEDIA_VOLUME_LEVEL)
@property
def is_volume_muted(self):
""" boolean if volume is muted """
return self._override_or_child_attr(ATTR_MEDIA_VOLUME_MUTED) \
in [True, STATE_ON]
@property
def media_content_id(self):
""" Content ID of current playing media. """
return self._child_attr(ATTR_MEDIA_CONTENT_ID)
@property
def media_content_type(self):
""" Content type of current playing media. """
return self._child_attr(ATTR_MEDIA_CONTENT_TYPE)
@property
def media_duration(self):
""" Duration of current playing media in seconds. """
return self._child_attr(ATTR_MEDIA_DURATION)
@property
def media_image_url(self):
""" Image url of current playing media. """
return self._child_attr(ATTR_ENTITY_PICTURE)
@property
def media_title(self):
""" Title of current playing media. """
return self._child_attr(ATTR_MEDIA_TITLE)
@property
def media_artist(self):
""" Artist of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ARTIST)
@property
def media_album_name(self):
""" Album name of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ALBUM_NAME)
@property
def media_album_artist(self):
""" Album arist of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_ALBUM_ARTIST)
@property
def media_track(self):
""" Track number of current playing media. (Music track only) """
return self._child_attr(ATTR_MEDIA_TRACK)
@property
def media_series_title(self):
""" Series title of current playing media. (TV Show only)"""
return self._child_attr(ATTR_MEDIA_SERIES_TITLE)
@property
def media_season(self):
""" Season of current playing media. (TV Show only) """
return self._child_attr(ATTR_MEDIA_SEASON)
@property
def media_episode(self):
""" Episode of current playing media. (TV Show only) """
return self._child_attr(ATTR_MEDIA_EPISODE)
@property
def media_channel(self):
""" Channel currently playing. """
return self._child_attr(ATTR_MEDIA_CHANNEL)
@property
def media_playlist(self):
""" Title of Playlist currently playing. """
return self._child_attr(ATTR_MEDIA_PLAYLIST)
@property
def app_id(self):
""" ID of the current running app. """
return self._child_attr(ATTR_APP_ID)
@property
def app_name(self):
""" Name of the current running app. """
return self._child_attr(ATTR_APP_NAME)
@property
def supported_media_commands(self):
""" Flags of media commands that are supported. """
flags = self._child_attr(ATTR_SUPPORTED_MEDIA_COMMANDS) or 0
if SERVICE_TURN_ON in self._cmds:
flags |= SUPPORT_TURN_ON
if SERVICE_TURN_OFF in self._cmds:
flags |= SUPPORT_TURN_OFF
if any([cmd in self._cmds for cmd in [SERVICE_VOLUME_UP,
SERVICE_VOLUME_DOWN]]):
flags |= SUPPORT_VOLUME_STEP
flags &= ~SUPPORT_VOLUME_SET
if SERVICE_VOLUME_MUTE in self._cmds and \
ATTR_MEDIA_VOLUME_MUTED in self._attrs:
flags |= SUPPORT_VOLUME_MUTE
return flags
@property
def device_state_attributes(self):
""" Extra attributes a device wants to expose. """
active_child = self._child_state
return {ATTR_ACTIVE_CHILD: active_child.entity_id} \
if active_child else {}
def turn_on(self):
""" turn the media player on. """
self._call_service(SERVICE_TURN_ON, allow_override=True)
def turn_off(self):
""" turn the media player off. """
self._call_service(SERVICE_TURN_OFF, allow_override=True)
def mute_volume(self, is_volume_muted):
""" mute the volume. """
data = {ATTR_MEDIA_VOLUME_MUTED: is_volume_muted}
self._call_service(SERVICE_VOLUME_MUTE, data, allow_override=True)
def set_volume_level(self, volume_level):
""" set volume level, range 0..1. """
data = {ATTR_MEDIA_VOLUME_LEVEL: volume_level}
self._call_service(SERVICE_VOLUME_SET, data)
def media_play(self):
""" Send play commmand. """
self._call_service(SERVICE_MEDIA_PLAY)
def media_pause(self):
""" Send pause command. """
self._call_service(SERVICE_MEDIA_PAUSE)
def media_previous_track(self):
""" Send previous track command. """
self._call_service(SERVICE_MEDIA_PREVIOUS_TRACK)
def media_next_track(self):
""" Send next track command. """
self._call_service(SERVICE_MEDIA_NEXT_TRACK)
def media_seek(self, position):
""" Send seek command. """
data = {ATTR_MEDIA_SEEK_POSITION: position}
self._call_service(SERVICE_MEDIA_SEEK, data)
def play_youtube(self, media_id):
""" Plays a YouTube media. """
data = {'media_id': media_id}
self._call_service(SERVICE_YOUTUBE_VIDEO, data)
def play_media(self, media_type, media_id):
""" Plays a piece of media. """
data = {'media_type': media_type, 'media_id': media_id}
self._call_service(SERVICE_PLAY_MEDIA, data)
def volume_up(self):
""" volume_up media player. """
self._call_service(SERVICE_VOLUME_UP, allow_override=True)
def volume_down(self):
""" volume_down media player. """
self._call_service(SERVICE_VOLUME_DOWN, allow_override=True)
def media_play_pause(self):
""" media_play_pause media player. """
self._call_service(SERVICE_MEDIA_PLAY_PAUSE)
def update(self):
""" event to trigger a state update in HA """
for child_name in self._children:
child_state = self.hass.states.get(child_name)
if child_state and child_state.state not in OFF_STATES:
self._child_state = child_state
return
self._child_state = None

View File

@ -149,9 +149,9 @@ class MQTT(object):
}
if client_id is None:
self._mqttc = mqtt.Client()
self._mqttc = mqtt.Client(protocol=mqtt.MQTTv311)
else:
self._mqttc = mqtt.Client(client_id)
self._mqttc = mqtt.Client(client_id, protocol=mqtt.MQTTv311)
self._mqttc.user_data_set(self.userdata)

View File

@ -0,0 +1,114 @@
"""
homeassistant.components.mqtt_eventstream
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Connect two Home Assistant instances via mqtt.
Configuration:
To use the mqtt_eventstream component you will need to add the following to
your configuration.yaml file.
If you do not specify a publish_topic you will not forward events to the queue.
If you do not specify a subscribe_topic then you will not receive events from
the remote server.
mqtt_eventstream:
publish_topic: MyServerName
subscribe_topic: OtherHaServerName
"""
import json
from homeassistant.core import EventOrigin, State
from homeassistant.components.mqtt import DOMAIN as MQTT_DOMAIN
from homeassistant.components.mqtt import SERVICE_PUBLISH as MQTT_SVC_PUBLISH
from homeassistant.const import (
MATCH_ALL,
EVENT_TIME_CHANGED,
EVENT_CALL_SERVICE,
EVENT_SERVICE_EXECUTED,
EVENT_STATE_CHANGED,
)
import homeassistant.loader as loader
from homeassistant.remote import JSONEncoder
# The domain of your component. Should be equal to the name of your component
DOMAIN = "mqtt_eventstream"
# List of component names (string) your component depends upon
DEPENDENCIES = ['mqtt']
def setup(hass, config):
""" Setup our mqtt_eventstream component. """
mqtt = loader.get_component('mqtt')
pub_topic = config[DOMAIN].get('publish_topic', None)
sub_topic = config[DOMAIN].get('subscribe_topic', None)
def _event_publisher(event):
""" Handle events by publishing them on the mqtt queue. """
if event.origin != EventOrigin.local:
return
if event.event_type == EVENT_TIME_CHANGED:
return
# Filter out the events that were triggered by publishing
# to the MQTT topic, or you will end up in an infinite loop.
if event.event_type == EVENT_CALL_SERVICE:
if (
event.data.get('domain') == MQTT_DOMAIN and
event.data.get('service') == MQTT_SVC_PUBLISH and
event.data.get('topic') == pub_topic
):
return
# Filter out all the "event service executed" events because they
# are only used internally by core as callbacks for blocking
# during the interval while a service is being executed.
# They will serve no purpose to the external system,
# and thus are unnecessary traffic.
# And at any rate it would cause an infinite loop to publish them
# because publishing to an MQTT topic itself triggers one.
if event.event_type == EVENT_SERVICE_EXECUTED:
return
event_info = {'event_type': event.event_type, 'event_data': event.data}
msg = json.dumps(event_info, cls=JSONEncoder)
mqtt.publish(hass, pub_topic, msg)
# Only listen for local events if you are going to publish them
if pub_topic:
hass.bus.listen(MATCH_ALL, _event_publisher)
# Process events from a remote server that are received on a queue
def _event_receiver(topic, payload, qos):
"""
Receive events published by the other HA instance and fire
them on this hass instance.
"""
event = json.loads(payload)
event_type = event.get('event_type')
event_data = event.get('event_data')
# Special case handling for event STATE_CHANGED
# We will try to convert state dicts back to State objects
# Copied over from the _handle_api_post_events_event method
# of the api component.
if event_type == EVENT_STATE_CHANGED and event_data:
for key in ('old_state', 'new_state'):
state = State.from_dict(event_data.get(key))
if state:
event_data[key] = state
hass.bus.fire(
event_type,
event_data=event_data,
origin=EventOrigin.remote
)
# Only subscribe if you specified a topic
if sub_topic:
mqtt.subscribe(hass, sub_topic, _event_receiver)
hass.states.set('{domain}.initialized'.format(domain=DOMAIN), True)
# return boolean to indicate that initialization was successful
return True

View File

@ -0,0 +1,230 @@
"""
homeassistant.components.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MySensors component that connects to a MySensors gateway via pymysensors
API.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors.html
New features:
New MySensors component.
Updated MySensors Sensor platform.
New MySensors Switch platform. Currently only in optimistic mode (compare
with MQTT).
Multiple gateways are now supported.
Configuration.yaml:
mysensors:
gateways:
- port: '/dev/ttyUSB0'
persistence_file: 'path/mysensors.json'
- port: '/dev/ttyACM1'
persistence_file: 'path/mysensors2.json'
debug: true
persistence: true
version: '1.5'
"""
import logging
from homeassistant.helpers import validate_config
import homeassistant.bootstrap as bootstrap
from homeassistant.const import (
EVENT_HOMEASSISTANT_START,
EVENT_HOMEASSISTANT_STOP,
EVENT_PLATFORM_DISCOVERED, ATTR_SERVICE, ATTR_DISCOVERED,
TEMP_CELCIUS,)
CONF_GATEWAYS = 'gateways'
CONF_PORT = 'port'
CONF_DEBUG = 'debug'
CONF_PERSISTENCE = 'persistence'
CONF_PERSISTENCE_FILE = 'persistence_file'
CONF_VERSION = 'version'
DEFAULT_VERSION = '1.4'
DOMAIN = 'mysensors'
DEPENDENCIES = []
REQUIREMENTS = [
'https://github.com/theolind/pymysensors/archive/'
'005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4']
_LOGGER = logging.getLogger(__name__)
ATTR_NODE_ID = 'node_id'
ATTR_CHILD_ID = 'child_id'
ATTR_PORT = 'port'
GATEWAYS = None
SCAN_INTERVAL = 30
DISCOVER_SENSORS = "mysensors.sensors"
DISCOVER_SWITCHES = "mysensors.switches"
# Maps discovered services to their platforms
DISCOVERY_COMPONENTS = [
('sensor', DISCOVER_SENSORS),
('switch', DISCOVER_SWITCHES),
]
def setup(hass, config):
"""Setup the MySensors component."""
# pylint: disable=too-many-locals
if not validate_config(config,
{DOMAIN: [CONF_GATEWAYS]},
_LOGGER):
return False
import mysensors.mysensors as mysensors
version = str(config[DOMAIN].get(CONF_VERSION, DEFAULT_VERSION))
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
def setup_gateway(port, persistence, persistence_file, version):
"""Return gateway after setup of the gateway."""
gateway = mysensors.SerialGateway(port, event_callback=None,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
gateway.metric = is_metric
gateway.debug = config[DOMAIN].get(CONF_DEBUG, False)
gateway = GatewayWrapper(gateway, version)
# pylint: disable=attribute-defined-outside-init
gateway.event_callback = gateway.callback_factory()
def gw_start(event):
"""Callback to trigger start of gateway and any persistence."""
gateway.start()
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: gateway.stop())
if persistence:
for node_id in gateway.sensors:
gateway.event_callback('persistence', node_id)
hass.bus.listen_once(EVENT_HOMEASSISTANT_START, gw_start)
return gateway
# Setup all ports from config
global GATEWAYS
GATEWAYS = {}
conf_gateways = config[DOMAIN][CONF_GATEWAYS]
if isinstance(conf_gateways, dict):
conf_gateways = [conf_gateways]
persistence = config[DOMAIN].get(CONF_PERSISTENCE, True)
for index, gway in enumerate(conf_gateways):
port = gway[CONF_PORT]
persistence_file = gway.get(
CONF_PERSISTENCE_FILE,
hass.config.path('mysensors{}.pickle'.format(index + 1)))
GATEWAYS[port] = setup_gateway(
port, persistence, persistence_file, version)
for (component, discovery_service) in DISCOVERY_COMPONENTS:
# Ensure component is loaded
if not bootstrap.setup_component(hass, component, config):
return False
# Fire discovery event
hass.bus.fire(EVENT_PLATFORM_DISCOVERED, {
ATTR_SERVICE: discovery_service,
ATTR_DISCOVERED: {}})
return True
def pf_callback_factory(
s_types, v_types, devices, add_devices, entity_class):
"""Return a new callback for the platform."""
def mysensors_callback(gateway, node_id):
"""Callback for mysensors platform."""
if gateway.sensors[node_id].sketch_name is None:
_LOGGER.info('No sketch_name: node %s', node_id)
return
# previously discovered, just update state with latest info
if node_id in devices:
for entity in devices[node_id]:
entity.update_ha_state(True)
return
# First time we see this node, detect sensors
for child in gateway.sensors[node_id].children.values():
name = '{} {}.{}'.format(
gateway.sensors[node_id].sketch_name, node_id, child.id)
for value_type in child.values.keys():
if child.type not in s_types or value_type not in v_types:
continue
devices[node_id].append(
entity_class(gateway, node_id, child.id, name, value_type))
if devices[node_id]:
_LOGGER.info('adding new devices: %s', devices[node_id])
add_devices(devices[node_id])
for entity in devices[node_id]:
entity.update_ha_state(True)
return mysensors_callback
class GatewayWrapper(object):
"""Gateway wrapper class, by subclassing serial gateway."""
def __init__(self, gateway, version):
"""Setup class attributes on instantiation.
Args:
gateway (mysensors.SerialGateway): Gateway to wrap.
version (str): Version of mysensors API.
Attributes:
_wrapped_gateway (mysensors.SerialGateway): Wrapped gateway.
version (str): Version of mysensors API.
platform_callbacks (list): Callback functions, one per platform.
const (module): Mysensors API constants.
__initialised (bool): True if GatewayWrapper is initialised.
"""
self._wrapped_gateway = gateway
self.version = version
self.platform_callbacks = []
self.const = self.get_const()
self.__initialised = True
def __getattr__(self, name):
"""See if this object has attribute name."""
# Do not use hasattr, it goes into infinite recurrsion
if name in self.__dict__:
# this object has it
return getattr(self, name)
# proxy to the wrapped object
return getattr(self._wrapped_gateway, name)
def __setattr__(self, name, value):
"""See if this object has attribute name then set to value."""
if '_GatewayWrapper__initialised' not in self.__dict__:
return object.__setattr__(self, name, value)
elif name in self.__dict__:
object.__setattr__(self, name, value)
else:
object.__setattr__(self._wrapped_gateway, name, value)
def get_const(self):
"""Get mysensors API constants."""
if self.version == '1.5':
import mysensors.const_15 as const
else:
import mysensors.const_14 as const
return const
def callback_factory(self):
"""Return a new callback function."""
def node_update(update_type, node_id):
"""Callback for node updates from the MySensors gateway."""
_LOGGER.info('update %s: node %s', update_type, node_id)
for callback in self.platform_callbacks:
callback(self, node_id)
return node_update

View File

@ -0,0 +1,51 @@
"""
homeassistant.components.notify.free_mobile
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Free Mobile SMS platform for notify component.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.free_mobile/
"""
import logging
from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, BaseNotificationService)
from homeassistant.const import CONF_USERNAME, CONF_ACCESS_TOKEN
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['freesms==0.1.0']
def get_service(hass, config):
""" Get the Free Mobile SMS notification service. """
if not validate_config({DOMAIN: config},
{DOMAIN: [CONF_USERNAME,
CONF_ACCESS_TOKEN]},
_LOGGER):
return None
return FreeSMSNotificationService(config[CONF_USERNAME],
config[CONF_ACCESS_TOKEN])
# pylint: disable=too-few-public-methods
class FreeSMSNotificationService(BaseNotificationService):
""" Implements notification service for the Free Mobile SMS service. """
def __init__(self, username, access_token):
from freesms import FreeClient
self.free_client = FreeClient(username, access_token)
def send_message(self, message="", **kwargs):
""" Send a message to the Free Mobile user cell. """
resp = self.free_client.send_sms(message)
if resp.status_code == 400:
_LOGGER.error("At least one parameter is missing")
elif resp.status_code == 402:
_LOGGER.error("Too much SMS send in a few time")
elif resp.status_code == 403:
_LOGGER.error("Wrong Username/Password")
elif resp.status_code == 500:
_LOGGER.error("Server error, try later")

View File

@ -7,14 +7,25 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/notify.syslog/
"""
import logging
import syslog
from homeassistant.helpers import validate_config
from homeassistant.components.notify import (
DOMAIN, ATTR_TITLE, BaseNotificationService)
_LOGGER = logging.getLogger(__name__)
FACILITIES = {'kernel': syslog.LOG_KERN,
def get_service(hass, config):
"""Get the syslog notification service."""
if not validate_config({DOMAIN: config},
{DOMAIN: ['facility', 'option', 'priority']},
_LOGGER):
return None
import syslog
_facility = {
'kernel': syslog.LOG_KERN,
'user': syslog.LOG_USER,
'mail': syslog.LOG_MAIL,
'daemon': syslog.LOG_DAEMON,
@ -31,35 +42,27 @@ FACILITIES = {'kernel': syslog.LOG_KERN,
'local4': syslog.LOG_LOCAL4,
'local5': syslog.LOG_LOCAL5,
'local6': syslog.LOG_LOCAL6,
'local7': syslog.LOG_LOCAL7}
'local7': syslog.LOG_LOCAL7,
}.get(config['facility'], 40)
OPTIONS = {'pid': syslog.LOG_PID,
_option = {
'pid': syslog.LOG_PID,
'cons': syslog.LOG_CONS,
'ndelay': syslog.LOG_NDELAY,
'nowait': syslog.LOG_NOWAIT,
'perror': syslog.LOG_PERROR}
'perror': syslog.LOG_PERROR
}.get(config['option'], 10)
PRIORITIES = {5: syslog.LOG_EMERG,
_priority = {
5: syslog.LOG_EMERG,
4: syslog.LOG_ALERT,
3: syslog.LOG_CRIT,
2: syslog.LOG_ERR,
1: syslog.LOG_WARNING,
0: syslog.LOG_NOTICE,
-1: syslog.LOG_INFO,
-2: syslog.LOG_DEBUG}
def get_service(hass, config):
""" Get the mail notification service. """
if not validate_config({DOMAIN: config},
{DOMAIN: ['facility', 'option', 'priority']},
_LOGGER):
return None
_facility = FACILITIES.get(config['facility'], 40)
_option = OPTIONS.get(config['option'], 10)
_priority = PRIORITIES.get(config['priority'], -1)
-2: syslog.LOG_DEBUG
}.get(config['priority'], -1)
return SyslogNotificationService(_facility, _option, _priority)
@ -76,6 +79,7 @@ class SyslogNotificationService(BaseNotificationService):
def send_message(self, message="", **kwargs):
""" Send a message to a user. """
import syslog
title = kwargs.get(ATTR_TITLE)

View File

@ -9,8 +9,8 @@ https://home-assistant.io/components/sensor/
import logging
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.components import (wink, zwave, isy994,
verisure, ecobee, tellduslive)
from homeassistant.components import (
wink, zwave, isy994, verisure, ecobee, tellduslive, mysensors)
DOMAIN = 'sensor'
SCAN_INTERVAL = 30
@ -25,6 +25,7 @@ DISCOVERY_PLATFORMS = {
verisure.DISCOVER_SENSORS: 'verisure',
ecobee.DISCOVER_SENSORS: 'ecobee',
tellduslive.DISCOVER_SENSORS: 'tellduslive',
mysensors.DISCOVER_SENSORS: 'mysensors',
}

View File

@ -11,8 +11,8 @@ import logging
import requests
from homeassistant.const import ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE, \
DEVICE_DEFAULT_NAME
from homeassistant.const import (ATTR_UNIT_OF_MEASUREMENT, CONF_VALUE_TEMPLATE,
DEVICE_DEFAULT_NAME)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers.entity import Entity
from homeassistant.util import template, Throttle

View File

@ -70,5 +70,8 @@ class EliqSensor(Entity):
def update(self):
""" Gets the latest data. """
try:
response = self.api.get_data_now(channelid=self.channel_id)
self._state = int(response.power)
except TypeError: # raised by eliqonline library on any HTTP error
pass

View File

@ -7,150 +7,177 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors/
"""
import logging
from collections import defaultdict
from homeassistant.helpers.entity import Entity
from homeassistant.const import (
ATTR_BATTERY_LEVEL, EVENT_HOMEASSISTANT_STOP,
ATTR_BATTERY_LEVEL,
TEMP_CELCIUS, TEMP_FAHRENHEIT,
STATE_ON, STATE_OFF)
CONF_PORT = "port"
CONF_DEBUG = "debug"
CONF_PERSISTENCE = "persistence"
CONF_PERSISTENCE_FILE = "persistence_file"
CONF_VERSION = "version"
ATTR_NODE_ID = "node_id"
ATTR_CHILD_ID = "child_id"
import homeassistant.components.mysensors as mysensors
_LOGGER = logging.getLogger(__name__)
REQUIREMENTS = ['https://github.com/theolind/pymysensors/archive/'
'd4b809c2167650691058d1e29bfd2c4b1792b4b0.zip'
'#pymysensors==0.3']
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
""" Setup the mysensors platform. """
import mysensors.mysensors as mysensors
import mysensors.const_14 as const
devices = {} # keep track of devices added to HA
# Just assume celcius means that the user wants metric for now.
# It may make more sense to make this a global config option in the future.
is_metric = (hass.config.temperature_unit == TEMP_CELCIUS)
def sensor_update(update_type, nid):
""" Callback for sensor updates from the MySensors gateway. """
_LOGGER.info("sensor_update %s: node %s", update_type, nid)
sensor = gateway.sensors[nid]
if sensor.sketch_name is None:
"""Setup the mysensors platform for sensors."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
if nid not in devices:
devices[nid] = {}
node = devices[nid]
new_devices = []
for child_id, child in sensor.children.items():
if child_id not in node:
node[child_id] = {}
for value_type, value in child.values.items():
if value_type not in node[child_id]:
name = '{} {}.{}'.format(sensor.sketch_name, nid, child.id)
node[child_id][value_type] = \
MySensorsNodeValue(
nid, child_id, name, value_type, is_metric, const)
new_devices.append(node[child_id][value_type])
else:
node[child_id][value_type].update_sensor(
value, sensor.battery_level)
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states.
s_types = [
gateway.const.Presentation.S_TEMP,
gateway.const.Presentation.S_HUM,
gateway.const.Presentation.S_BARO,
gateway.const.Presentation.S_WIND,
gateway.const.Presentation.S_RAIN,
gateway.const.Presentation.S_UV,
gateway.const.Presentation.S_WEIGHT,
gateway.const.Presentation.S_POWER,
gateway.const.Presentation.S_DISTANCE,
gateway.const.Presentation.S_LIGHT_LEVEL,
gateway.const.Presentation.S_IR,
gateway.const.Presentation.S_WATER,
gateway.const.Presentation.S_AIR_QUALITY,
gateway.const.Presentation.S_CUSTOM,
gateway.const.Presentation.S_DUST,
gateway.const.Presentation.S_SCENE_CONTROLLER,
]
not_v_types = [
gateway.const.SetReq.V_ARMED,
gateway.const.SetReq.V_LIGHT,
gateway.const.SetReq.V_LOCK_STATUS,
]
if float(gateway.version) >= 1.5:
s_types.extend([
gateway.const.Presentation.S_COLOR_SENSOR,
gateway.const.Presentation.S_MULTIMETER,
])
not_v_types.extend([gateway.const.SetReq.V_STATUS, ])
v_types = [member for member in gateway.const.SetReq
if member.value not in not_v_types]
if new_devices:
_LOGGER.info("adding new devices: %s", new_devices)
add_devices(new_devices)
port = config.get(CONF_PORT)
if port is None:
_LOGGER.error("Missing required key 'port'")
return False
persistence = config.get(CONF_PERSISTENCE, True)
persistence_file = config.get(CONF_PERSISTENCE_FILE,
hass.config.path('mysensors.pickle'))
version = config.get(CONF_VERSION, '1.4')
gateway = mysensors.SerialGateway(port, sensor_update,
persistence=persistence,
persistence_file=persistence_file,
protocol_version=version)
gateway.metric = is_metric
gateway.debug = config.get(CONF_DEBUG, False)
gateway.start()
if persistence:
for nid in gateway.sensors:
sensor_update('sensor_update', nid)
hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP,
lambda event: gateway.stop())
devices = defaultdict(list)
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
s_types, v_types, devices, add_devices, MySensorsSensor))
class MySensorsNodeValue(Entity):
""" Represents the value of a MySensors child node. """
# pylint: disable=too-many-arguments, too-many-instance-attributes
def __init__(self, node_id, child_id, name, value_type, metric, const):
self._name = name
class MySensorsSensor(Entity):
"""Represent the value of a MySensors child node."""
# pylint: disable=too-many-arguments
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Setup class attributes on instantiation.
Args:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
Attributes:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
_name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
battery_level (int): Node battery level.
_values (dict): Child values. Non state values set as state attributes.
"""
self.gateway = gateway
self.node_id = node_id
self.child_id = child_id
self.battery_level = 0
self._name = name
self.value_type = value_type
self.metric = metric
self._value = ''
self.const = const
self.battery_level = 0
self._values = {}
@property
def should_poll(self):
""" MySensor gateway pushes its state to HA. """
"""MySensor gateway pushes its state to HA."""
return False
@property
def name(self):
""" The name of this sensor. """
"""The name of this entity."""
return self._name
@property
def state(self):
""" Returns the state of the device. """
return self._value
"""Return the state of the device."""
if not self._values:
return ''
return self._values[self.value_type]
@property
def unit_of_measurement(self):
""" Unit of measurement of this entity. """
if self.value_type == self.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.metric else TEMP_FAHRENHEIT
elif self.value_type == self.const.SetReq.V_HUM or \
self.value_type == self.const.SetReq.V_DIMMER or \
self.value_type == self.const.SetReq.V_LIGHT_LEVEL:
"""Unit of measurement of this entity."""
# pylint:disable=too-many-return-statements
if self.value_type == self.gateway.const.SetReq.V_TEMP:
return TEMP_CELCIUS if self.gateway.metric else TEMP_FAHRENHEIT
elif self.value_type == self.gateway.const.SetReq.V_HUM or \
self.value_type == self.gateway.const.SetReq.V_DIMMER or \
self.value_type == self.gateway.const.SetReq.V_PERCENTAGE or \
self.value_type == self.gateway.const.SetReq.V_LIGHT_LEVEL:
return '%'
elif self.value_type == self.gateway.const.SetReq.V_WATT:
return 'W'
elif self.value_type == self.gateway.const.SetReq.V_KWH:
return 'kWh'
elif self.value_type == self.gateway.const.SetReq.V_VOLTAGE:
return 'V'
elif self.value_type == self.gateway.const.SetReq.V_CURRENT:
return 'A'
elif self.value_type == self.gateway.const.SetReq.V_IMPEDANCE:
return 'ohm'
elif self.gateway.const.SetReq.V_UNIT_PREFIX in self._values:
return self._values[self.gateway.const.SetReq.V_UNIT_PREFIX]
return None
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = dict(self._values)
device_attr.pop(self.value_type, None)
return device_attr
@property
def state_attributes(self):
""" Returns the state attributes. """
return {
ATTR_NODE_ID: self.node_id,
ATTR_CHILD_ID: self.child_id,
"""Return the state attributes."""
data = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
def update_sensor(self, value, battery_level):
""" Update a sensor with the latest value from the controller. """
_LOGGER.info("%s value = %s", self._name, value)
if self.value_type == self.const.SetReq.V_TRIPPED or \
self.value_type == self.const.SetReq.V_ARMED:
self._value = STATE_ON if int(value) == 1 else STATE_OFF
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
def update(self):
"""Update the controller with the latest values from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.info(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_TRIPPED:
self._values[value_type] = STATE_ON if int(
value) == 1 else STATE_OFF
else:
self._value = value
self.battery_level = battery_level
self.update_ha_state()
self._values[value_type] = value
self.battery_level = node.battery_level

View File

@ -13,14 +13,14 @@ from homeassistant.util import Throttle
from homeassistant.const import (CONF_API_KEY, TEMP_CELCIUS, TEMP_FAHRENHEIT)
from homeassistant.helpers.entity import Entity
REQUIREMENTS = ['pyowm==2.2.1']
REQUIREMENTS = ['pyowm==2.3.0']
_LOGGER = logging.getLogger(__name__)
SENSOR_TYPES = {
'weather': ['Condition', ''],
'temperature': ['Temperature', ''],
'wind_speed': ['Wind speed', 'm/s'],
'humidity': ['Humidity', '%'],
'pressure': ['Pressure', 'hPa'],
'pressure': ['Pressure', 'mbar'],
'clouds': ['Cloud coverage', '%'],
'rain': ['Rain', 'mm'],
'snow': ['Snow', 'mm']

View File

@ -15,7 +15,7 @@ from homeassistant.const import (
ATTR_BATTERY_LEVEL, ATTR_TRIPPED, ATTR_ARMED, ATTR_LAST_TRIP_TIME,
TEMP_CELCIUS, TEMP_FAHRENHEIT, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['pyvera==0.2.2']
REQUIREMENTS = ['pyvera==0.2.3']
_LOGGER = logging.getLogger(__name__)
@ -56,7 +56,7 @@ def get_devices(hass, config):
vera_sensors = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
@ -85,18 +85,14 @@ class VeraSensor(Entity):
self.current_value = ''
self._temperature_units = None
self.controller.register(vera_device)
self.controller.on(
vera_device, self._update_callback)
self.controller.register(vera_device, self._update_callback)
def _update_callback(self, _device):
""" Called by the vera device callback to update state. """
_LOGGER.info(
'Subscription update for %s', self.name)
self.update_ha_state(True)
def __str__(self):
return "%s %s %s" % (self.name, self.vera_device.deviceId, self.state)
return "%s %s %s" % (self.name, self.vera_device.device_id, self.state)
@property
def state(self):
@ -119,18 +115,18 @@ class VeraSensor(Entity):
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.refresh_value('Armed')
armed = self.vera_device.get_value('Armed')
attr[ATTR_ARMED] = 'True' if armed == '1' else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.refresh_value('LastTrip')
last_tripped = self.vera_device.get_value('LastTrip')
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.refresh_value('Tripped')
tripped = self.vera_device.get_value('Tripped')
attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
@ -143,7 +139,6 @@ class VeraSensor(Entity):
def update(self):
if self.vera_device.category == "Temperature Sensor":
self.vera_device.refresh_value('CurrentTemperature')
current_temp = self.vera_device.get_value('CurrentTemperature')
vera_temp_units = self.vera_device.veraController.temperature_units
@ -161,10 +156,9 @@ class VeraSensor(Entity):
self.current_value = current_temp
elif self.vera_device.category == "Light Sensor":
self.vera_device.refresh_value('CurrentLevel')
self.current_value = self.vera_device.get_value('CurrentLevel')
elif self.vera_device.category == "Sensor":
tripped = self.vera_device.refresh_value('Tripped')
tripped = self.vera_device.get_value('Tripped')
self.current_value = 'Tripped' if tripped == '1' else 'Not Tripped'
else:
self.current_value = 'Unknown'

View File

@ -25,7 +25,7 @@ SENSOR_TYPES = {
'precipitation': ['Condition', 'mm'],
'temperature': ['Temperature', '°C'],
'windSpeed': ['Wind speed', 'm/s'],
'pressure': ['Pressure', 'hPa'],
'pressure': ['Pressure', 'mbar'],
'windDirection': ['Wind direction', '°'],
'humidity': ['Humidity', '%'],
'fog': ['Fog', '%'],

View File

@ -74,6 +74,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
value.type == zwave.TYPE_DECIMAL):
add_devices([ZWaveMultilevelSensor(value)])
elif value.command_class == zwave.COMMAND_CLASS_ALARM:
add_devices([ZWaveAlarmSensor(value)])
class ZWaveSensor(Entity):
""" Represents a Z-Wave sensor. """
@ -216,3 +219,19 @@ class ZWaveMultilevelSensor(ZWaveSensor):
return TEMP_FAHRENHEIT
else:
return unit
class ZWaveAlarmSensor(ZWaveSensor):
""" A Z-wave sensor that sends Alarm alerts
Examples include certain Multisensors that have motion and
vibration capabilities. Z-Wave defines various alarm types
such as Smoke, Flood, Burglar, CarbonMonoxide, etc.
This wraps these alarms and allows you to use them to
trigger things, etc.
COMMAND_CLASS_ALARM is what we get here.
"""
# Empty subclass for now. Allows for later customizations
pass

View File

@ -17,7 +17,7 @@ from homeassistant.helpers.entity import ToggleEntity
from homeassistant.const import (
STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, ATTR_ENTITY_ID)
from homeassistant.components import (
group, discovery, wink, isy994, verisure, zwave, tellduslive)
group, discovery, wink, isy994, verisure, zwave, tellduslive, mysensors)
DOMAIN = 'switch'
SCAN_INTERVAL = 30
@ -41,6 +41,7 @@ DISCOVERY_PLATFORMS = {
verisure.DISCOVER_SWITCHES: 'verisure',
zwave.DISCOVER_SWITCHES: 'zwave',
tellduslive.DISCOVER_SWITCHES: 'tellduslive',
mysensors.DISCOVER_SWITCHES: 'mysensors',
}
PROP_TO_ATTR = {

View File

@ -0,0 +1,164 @@
"""
homeassistant.components.switch.mysensors
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Support for MySensors switches.
For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/sensor.mysensors.html
"""
import logging
from collections import defaultdict
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
STATE_ON, STATE_OFF)
import homeassistant.components.mysensors as mysensors
_LOGGER = logging.getLogger(__name__)
DEPENDENCIES = []
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Setup the mysensors platform for switches."""
# Only act if loaded via mysensors by discovery event.
# Otherwise gateway is not setup.
if discovery_info is None:
return
for gateway in mysensors.GATEWAYS.values():
# Define the S_TYPES and V_TYPES that the platform should handle as
# states.
s_types = [
gateway.const.Presentation.S_DOOR,
gateway.const.Presentation.S_MOTION,
gateway.const.Presentation.S_SMOKE,
gateway.const.Presentation.S_LIGHT,
gateway.const.Presentation.S_LOCK,
]
v_types = [
gateway.const.SetReq.V_ARMED,
gateway.const.SetReq.V_LIGHT,
gateway.const.SetReq.V_LOCK_STATUS,
]
if float(gateway.version) >= 1.5:
s_types.extend([
gateway.const.Presentation.S_BINARY,
gateway.const.Presentation.S_SPRINKLER,
gateway.const.Presentation.S_WATER_LEAK,
gateway.const.Presentation.S_SOUND,
gateway.const.Presentation.S_VIBRATION,
gateway.const.Presentation.S_MOISTURE,
])
v_types.extend([gateway.const.SetReq.V_STATUS, ])
devices = defaultdict(list)
gateway.platform_callbacks.append(mysensors.pf_callback_factory(
s_types, v_types, devices, add_devices, MySensorsSwitch))
class MySensorsSwitch(SwitchDevice):
"""Represent the value of a MySensors child node."""
# pylint: disable=too-many-arguments
def __init__(self, gateway, node_id, child_id, name, value_type):
"""Setup class attributes on instantiation.
Args:
gateway (GatewayWrapper): Gateway object.
node_id (str): Id of node.
child_id (str): Id of child.
name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
Attributes:
gateway (GatewayWrapper): Gateway object
node_id (str): Id of node.
child_id (str): Id of child.
_name (str): Entity name.
value_type (str): Value type of child. Value is entity state.
battery_level (int): Node battery level.
_values (dict): Child values. Non state values set as state attributes.
"""
self.gateway = gateway
self.node_id = node_id
self.child_id = child_id
self._name = name
self.value_type = value_type
self.battery_level = 0
self._values = {}
@property
def should_poll(self):
"""MySensor gateway pushes its state to HA."""
return False
@property
def name(self):
"""The name of this entity."""
return self._name
@property
def device_state_attributes(self):
"""Return device specific state attributes."""
device_attr = dict(self._values)
device_attr.pop(self.value_type, None)
return device_attr
@property
def state_attributes(self):
"""Return the state attributes."""
data = {
mysensors.ATTR_PORT: self.gateway.port,
mysensors.ATTR_NODE_ID: self.node_id,
mysensors.ATTR_CHILD_ID: self.child_id,
ATTR_BATTERY_LEVEL: self.battery_level,
}
device_attr = self.device_state_attributes
if device_attr is not None:
data.update(device_attr)
return data
@property
def is_on(self):
"""Return True if switch is on."""
if self.value_type in self._values:
return self._values[self.value_type] == STATE_ON
return False
def turn_on(self):
"""Turn the switch on."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, 1)
self._values[self.value_type] = STATE_ON
self.update_ha_state()
def turn_off(self):
"""Turn the switch off."""
self.gateway.set_child_value(
self.node_id, self.child_id, self.value_type, 0)
self._values[self.value_type] = STATE_OFF
self.update_ha_state()
def update(self):
"""Update the controller with the latest value from a sensor."""
node = self.gateway.sensors[self.node_id]
child = node.children[self.child_id]
for value_type, value in child.values.items():
_LOGGER.info(
"%s: value_type %s, value = %s", self._name, value_type, value)
if value_type == self.gateway.const.SetReq.V_ARMED or \
value_type == self.gateway.const.SetReq.V_STATUS or \
value_type == self.gateway.const.SetReq.V_LIGHT or \
value_type == self.gateway.const.SetReq.V_LOCK_STATUS:
self._values[value_type] = (
STATE_ON if int(value) == 1 else STATE_OFF)
else:
self._values[value_type] = value
self.battery_level = node.battery_level

View File

@ -13,8 +13,9 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.util import slugify
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.components.rfxtrx import ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID, \
ATTR_NAME, EVENT_BUTTON_PRESSED
from homeassistant.components.rfxtrx import (
ATTR_STATE, ATTR_FIREEVENT, ATTR_PACKETID,
ATTR_NAME, EVENT_BUTTON_PRESSED)
DEPENDENCIES = ['rfxtrx']

View File

@ -7,19 +7,21 @@ For more details about this platform, please refer to the documentation at
https://home-assistant.io/components/switch.vera/
"""
import logging
import time
from requests.exceptions import RequestException
import homeassistant.util.dt as dt_util
from homeassistant.helpers.entity import ToggleEntity
from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
ATTR_BATTERY_LEVEL,
ATTR_TRIPPED,
ATTR_ARMED,
ATTR_LAST_TRIP_TIME,
EVENT_HOMEASSISTANT_STOP)
EVENT_HOMEASSISTANT_STOP,
STATE_ON,
STATE_OFF)
REQUIREMENTS = ['pyvera==0.2.2']
REQUIREMENTS = ['pyvera==0.2.3']
_LOGGER = logging.getLogger(__name__)
@ -60,7 +62,7 @@ def get_devices(hass, config):
vera_switches = []
for device in devices:
extra_data = device_data.get(device.deviceId, {})
extra_data = device_data.get(device.device_id, {})
exclude = extra_data.get('exclude', False)
if exclude is not True:
@ -75,7 +77,7 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
add_devices(get_devices(hass, config))
class VeraSwitch(ToggleEntity):
class VeraSwitch(SwitchDevice):
""" Represents a Vera Switch. """
def __init__(self, vera_device, controller, extra_data=None):
@ -86,19 +88,17 @@ class VeraSwitch(ToggleEntity):
self._name = self.extra_data.get('name')
else:
self._name = self.vera_device.name
self.is_on_status = False
# for debouncing status check after command is sent
self.last_command_send = 0
self._state = STATE_OFF
self.controller.register(vera_device)
self.controller.on(
vera_device, self._update_callback)
self.controller.register(vera_device, self._update_callback)
def _update_callback(self, _device):
""" Called by the vera device callback to update state. """
_LOGGER.info(
'Subscription update for %s', self.name)
self.update_ha_state(True)
if self.vera_device.is_switched_on():
self._state = STATE_ON
else:
self._state = STATE_OFF
self.update_ha_state()
@property
def name(self):
@ -113,18 +113,18 @@ class VeraSwitch(ToggleEntity):
attr[ATTR_BATTERY_LEVEL] = self.vera_device.battery_level + '%'
if self.vera_device.is_armable:
armed = self.vera_device.refresh_value('Armed')
armed = self.vera_device.get_value('Armed')
attr[ATTR_ARMED] = 'True' if armed == '1' else 'False'
if self.vera_device.is_trippable:
last_tripped = self.vera_device.refresh_value('LastTrip')
last_tripped = self.vera_device.get_value('LastTrip')
if last_tripped is not None:
utc_time = dt_util.utc_from_timestamp(int(last_tripped))
attr[ATTR_LAST_TRIP_TIME] = dt_util.datetime_to_str(
utc_time)
else:
attr[ATTR_LAST_TRIP_TIME] = None
tripped = self.vera_device.refresh_value('Tripped')
tripped = self.vera_device.get_value('Tripped')
attr[ATTR_TRIPPED] = 'True' if tripped == '1' else 'False'
attr['Vera Device Id'] = self.vera_device.vera_device_id
@ -132,14 +132,14 @@ class VeraSwitch(ToggleEntity):
return attr
def turn_on(self, **kwargs):
self.last_command_send = time.time()
self.vera_device.switch_on()
self.is_on_status = True
self._state = STATE_ON
self.update_ha_state()
def turn_off(self, **kwargs):
self.last_command_send = time.time()
self.vera_device.switch_off()
self.is_on_status = False
self._state = STATE_OFF
self.update_ha_state()
@property
def should_poll(self):
@ -149,13 +149,4 @@ class VeraSwitch(ToggleEntity):
@property
def is_on(self):
""" True if device is on. """
return self.is_on_status
def update(self):
# We need to debounce the status call after turning switch on or off
# because the vera has some lag in updating the device status
try:
if (self.last_command_send + 5) < time.time():
self.is_on_status = self.vera_device.is_switched_on()
except RequestException:
_LOGGER.warning('Could not update status for %s', self.name)
return self._state == STATE_ON

View File

@ -12,7 +12,7 @@ from homeassistant.components.switch import SwitchDevice
from homeassistant.const import (
STATE_ON, STATE_OFF, STATE_STANDBY, EVENT_HOMEASSISTANT_STOP)
REQUIREMENTS = ['pywemo==0.3.7']
REQUIREMENTS = ['pywemo==0.3.8']
_LOGGER = logging.getLogger(__name__)
_WEMO_SUBSCRIPTION_REGISTRY = None
@ -69,15 +69,14 @@ class WemoSwitch(SwitchDevice):
def _update_callback(self, _device, _params):
""" Called by the wemo device callback to update state. """
_LOGGER.info(
'Subscription update for %s, sevice=%s',
self.name, _device)
'Subscription update for %s',
_device)
self.update_ha_state(True)
@property
def should_poll(self):
""" No polling should be needed with subscriptions """
# but leave in for initial version in case of issues.
return True
""" No polling needed with subscriptions """
return False
@property
def unique_id(self):

View File

@ -46,8 +46,9 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
return
data = ecobee.NETWORK
hold_temp = discovery_info['hold_temp']
_LOGGER.info("Loading ecobee thermostat component with hold_temp set to "
+ str(hold_temp))
_LOGGER.info(
"Loading ecobee thermostat component with hold_temp set to %s",
hold_temp)
add_devices(Thermostat(data, index, hold_temp)
for index in range(len(data.ecobee.thermostats)))

View File

@ -28,7 +28,7 @@ DISCOVER_SWITCHES = 'verisure.switches'
DISCOVER_ALARMS = 'verisure.alarm_control_panel'
DEPENDENCIES = ['alarm_control_panel']
REQUIREMENTS = ['vsure==0.4.3']
REQUIREMENTS = ['vsure==0.4.5']
_LOGGER = logging.getLogger(__name__)

View File

@ -37,6 +37,7 @@ COMMAND_CLASS_SENSOR_BINARY = 48
COMMAND_CLASS_SENSOR_MULTILEVEL = 49
COMMAND_CLASS_METER = 50
COMMAND_CLASS_BATTERY = 128
COMMAND_CLASS_ALARM = 113 # 0x71
GENRE_WHATEVER = None
GENRE_USER = "User"
@ -53,7 +54,8 @@ DISCOVERY_COMPONENTS = [
DISCOVER_SENSORS,
[COMMAND_CLASS_SENSOR_BINARY,
COMMAND_CLASS_SENSOR_MULTILEVEL,
COMMAND_CLASS_METER],
COMMAND_CLASS_METER,
COMMAND_CLASS_ALARM],
TYPE_WHATEVER,
GENRE_USER),
('light',

View File

@ -36,7 +36,7 @@ def extract_entity_ids(hass, service):
service_ent_id = service.data[ATTR_ENTITY_ID]
if isinstance(service_ent_id, str):
return group.expand_entity_ids(hass, [service_ent_id.lower()])
return group.expand_entity_ids(hass, [service_ent_id])
return [ent_id for ent_id in group.expand_entity_ids(hass, service_ent_id)]

View File

@ -0,0 +1,43 @@
"""Service calling related helpers."""
import logging
from homeassistant.util import split_entity_id
from homeassistant.const import ATTR_ENTITY_ID
CONF_SERVICE = 'service'
CONF_SERVICE_ENTITY_ID = 'entity_id'
CONF_SERVICE_DATA = 'data'
_LOGGER = logging.getLogger(__name__)
def call_from_config(hass, config, blocking=False):
"""Call a service based on a config hash."""
if not isinstance(config, dict) or CONF_SERVICE not in config:
_LOGGER.error('Missing key %s: %s', CONF_SERVICE, config)
return
try:
domain, service = split_entity_id(config[CONF_SERVICE])
except ValueError:
_LOGGER.error('Invalid service specified: %s', config[CONF_SERVICE])
return
service_data = config.get(CONF_SERVICE_DATA)
if service_data is None:
service_data = {}
elif isinstance(service_data, dict):
service_data = dict(service_data)
else:
_LOGGER.error("%s should be a dictionary", CONF_SERVICE_DATA)
service_data = {}
entity_id = config.get(CONF_SERVICE_ENTITY_ID)
if isinstance(entity_id, str):
service_data[ATTR_ENTITY_ID] = [ent.strip() for ent in
entity_id.split(",")]
elif entity_id is not None:
service_data[ATTR_ENTITY_ID] = entity_id
hass.services.call(domain, service, service_data, blocking)

View File

@ -59,7 +59,7 @@ tellcore-py==1.1.2
# homeassistant.components.light.vera
# homeassistant.components.sensor.vera
# homeassistant.components.switch.vera
pyvera==0.2.2
pyvera==0.2.3
# homeassistant.components.wink
# homeassistant.components.light.wink
@ -69,7 +69,7 @@ pyvera==0.2.2
python-wink==0.3.1
# homeassistant.components.media_player.cast
pychromecast==0.6.13
pychromecast==0.6.14
# homeassistant.components.media_player.kodi
jsonrpc-requests==0.1
@ -89,6 +89,12 @@ https://github.com/bashwork/pymodbus/archive/d7fc4f1cc975631e0a9011390e8017f64b6
# homeassistant.components.mqtt
paho-mqtt==1.1
# homeassistant.components.mysensors
https://github.com/theolind/pymysensors/archive/005bff4c5ca7a56acd30e816bc3bcdb5cb2d46fd.zip#pymysensors==0.4
# homeassistant.components.notify.free_mobile
freesms==0.1.0
# homeassistant.components.notify.pushbullet
pushbullet.py==0.9.0
@ -131,11 +137,8 @@ eliqonline==1.0.11
# homeassistant.components.sensor.forecast
python-forecastio==1.3.3
# homeassistant.components.sensor.mysensors
https://github.com/theolind/pymysensors/archive/d4b809c2167650691058d1e29bfd2c4b1792b4b0.zip#pymysensors==0.3
# homeassistant.components.sensor.openweathermap
pyowm==2.2.1
pyowm==2.3.0
# homeassistant.components.sensor.rpi_gpio
# homeassistant.components.switch.rpi_gpio
@ -173,7 +176,7 @@ hikvision==0.4
orvibo==1.1.0
# homeassistant.components.switch.wemo
pywemo==0.3.7
pywemo==0.3.8
# homeassistant.components.tellduslive
tellive-py==0.5.2
@ -191,7 +194,7 @@ python-nest==2.6.0
radiotherm==1.2
# homeassistant.components.verisure
vsure==0.4.3
vsure==0.4.5
# homeassistant.components.zwave
pydispatcher==2.0.5

View File

@ -1,5 +1,5 @@
flake8>=2.5.0
pylint>=1.5.1
flake8>=2.5.1
pylint>=1.5.3
coveralls>=1.1
pytest>=2.6.4
pytest-cov>=2.2.0

View File

@ -11,10 +11,10 @@ from unittest.mock import patch
import requests
from homeassistant import bootstrap, const
import homeassistant.core as ha
import homeassistant.components.device_tracker as device_tracker
import homeassistant.components.http as http
import homeassistant.components.zone as zone
from tests.common import get_test_home_assistant
SERVER_PORT = 8126
HTTP_BASE_URL = "http://127.0.0.1:{}".format(SERVER_PORT)
@ -34,7 +34,7 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server. """
global hass
hass = ha.HomeAssistant()
hass = get_test_home_assistant()
# Set up server
bootstrap.setup_component(hass, http.DOMAIN, {

View File

@ -0,0 +1,342 @@
"""
tests.component.media_player.test_universal
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests universal media_player component.
"""
from copy import copy
import unittest
import homeassistant.core as ha
from homeassistant.const import (
STATE_OFF, STATE_ON, STATE_UNKNOWN, STATE_PLAYING, STATE_PAUSED)
import homeassistant.components.switch as switch
import homeassistant.components.media_player as media_player
import homeassistant.components.media_player.universal as universal
class MockMediaPlayer(media_player.MediaPlayerDevice):
""" Mock media player for testing """
def __init__(self, hass, name):
self.hass = hass
self._name = name
self.entity_id = media_player.ENTITY_ID_FORMAT.format(name)
self._state = STATE_OFF
self._volume_level = 0
self._is_volume_muted = False
self._media_title = None
self._supported_media_commands = 0
@property
def name(self):
""" name of player """
return self._name
@property
def state(self):
""" state of the player """
return self._state
@property
def volume_level(self):
""" volume level of player """
return self._volume_level
@property
def is_volume_muted(self):
""" if the media player is muted """
return self._is_volume_muted
@property
def supported_media_commands(self):
""" supported media commands flag """
return self._supported_media_commands
def turn_on(self):
""" mock turn_on function """
self._state = STATE_UNKNOWN
def turn_off(self):
""" mock turn_off function """
self._state = STATE_OFF
def mute_volume(self):
""" mock mute function """
self._is_volume_muted = ~self._is_volume_muted
def set_volume_level(self, volume):
""" mock set volume level """
self._volume_level = volume
def media_play(self):
""" mock play """
self._state = STATE_PLAYING
def media_pause(self):
""" mock pause """
self._state = STATE_PAUSED
class TestMediaPlayer(unittest.TestCase):
""" Test the media_player module. """
def setUp(self): # pylint: disable=invalid-name
self.hass = ha.HomeAssistant()
self.mock_mp_1 = MockMediaPlayer(self.hass, 'mock1')
self.mock_mp_1.update_ha_state()
self.mock_mp_2 = MockMediaPlayer(self.hass, 'mock2')
self.mock_mp_2.update_ha_state()
self.mock_mute_switch_id = switch.ENTITY_ID_FORMAT.format('mute')
self.hass.states.set(self.mock_mute_switch_id, STATE_OFF)
self.mock_state_switch_id = switch.ENTITY_ID_FORMAT.format('state')
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
self.config_children_only = \
{'name': 'test', 'platform': 'universal',
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
media_player.ENTITY_ID_FORMAT.format('mock2')]}
self.config_children_and_attr = \
{'name': 'test', 'platform': 'universal',
'children': [media_player.ENTITY_ID_FORMAT.format('mock1'),
media_player.ENTITY_ID_FORMAT.format('mock2')],
'attributes': {
'is_volume_muted': self.mock_mute_switch_id,
'state': self.mock_state_switch_id}}
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_check_config_children_only(self):
""" Check config with only children """
config_start = copy(self.config_children_only)
del config_start['platform']
config_start['commands'] = {}
config_start['attributes'] = {}
response = universal.validate_config(self.config_children_only)
self.assertTrue(response)
self.assertEqual(config_start, self.config_children_only)
def test_check_config_children_and_attr(self):
""" Check config with children and attributes """
config_start = copy(self.config_children_and_attr)
del config_start['platform']
config_start['commands'] = {}
response = universal.validate_config(self.config_children_and_attr)
self.assertTrue(response)
self.assertEqual(config_start, self.config_children_and_attr)
def test_master_state(self):
""" test master state property """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
self.assertEqual(None, ump.master_state)
def test_master_state_with_attrs(self):
""" test master state property """
config = self.config_children_and_attr
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
self.assertEqual(STATE_OFF, ump.master_state)
self.hass.states.set(self.mock_state_switch_id, STATE_ON)
self.assertEqual(STATE_ON, ump.master_state)
def test_master_state_with_bad_attrs(self):
""" test master state property """
config = self.config_children_and_attr
config['attributes']['state'] = 'bad.entity_id'
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
self.assertEqual(STATE_OFF, ump.master_state)
def test_active_child_state(self):
""" test active child state property """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.assertEqual(None, ump._child_state)
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(self.mock_mp_1.entity_id,
ump._child_state.entity_id)
self.mock_mp_2._state = STATE_PLAYING
self.mock_mp_2.update_ha_state()
ump.update()
self.assertEqual(self.mock_mp_1.entity_id,
ump._child_state.entity_id)
self.mock_mp_1._state = STATE_OFF
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(self.mock_mp_2.entity_id,
ump._child_state.entity_id)
def test_name(self):
""" test name property """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
self.assertEqual(config['name'], ump.name)
def test_state_children_only(self):
""" test media player state with only children """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.assertTrue(ump.state, STATE_OFF)
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(STATE_PLAYING, ump.state)
def test_state_with_children_and_attrs(self):
""" test media player with children and master state """
config = self.config_children_and_attr
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.assertEqual(ump.state, STATE_OFF)
self.hass.states.set(self.mock_state_switch_id, STATE_ON)
ump.update()
self.assertEqual(ump.state, STATE_ON)
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(ump.state, STATE_PLAYING)
self.hass.states.set(self.mock_state_switch_id, STATE_OFF)
ump.update()
self.assertEqual(ump.state, STATE_OFF)
def test_volume_level(self):
""" test volume level property """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.assertEqual(None, ump.volume_level)
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(0, ump.volume_level)
self.mock_mp_1._volume_level = 1
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(1, ump.volume_level)
def test_is_volume_muted_children_only(self):
""" test is volume muted property w/ children only """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.assertFalse(ump.is_volume_muted)
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
self.assertFalse(ump.is_volume_muted)
self.mock_mp_1._is_volume_muted = True
self.mock_mp_1.update_ha_state()
ump.update()
self.assertTrue(ump.is_volume_muted)
def test_is_volume_muted_children_and_attr(self):
""" test is volume muted property w/ children and attrs """
config = self.config_children_and_attr
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
self.assertFalse(ump.is_volume_muted)
self.hass.states.set(self.mock_mute_switch_id, STATE_ON)
self.assertTrue(ump.is_volume_muted)
def test_supported_media_commands_children_only(self):
""" test supported media commands with only children """
config = self.config_children_only
universal.validate_config(config)
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.assertEqual(0, ump.supported_media_commands)
self.mock_mp_1._supported_media_commands = 512
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
self.assertEqual(512, ump.supported_media_commands)
def test_supported_media_commands_children_and_cmds(self):
""" test supported media commands with children and attrs """
config = self.config_children_and_attr
universal.validate_config(config)
config['commands']['turn_on'] = 'test'
config['commands']['turn_off'] = 'test'
config['commands']['volume_up'] = 'test'
config['commands']['volume_down'] = 'test'
config['commands']['volume_mute'] = 'test'
ump = universal.UniversalMediaPlayer(self.hass, **config)
ump.entity_id = media_player.ENTITY_ID_FORMAT.format(config['name'])
ump.update()
self.mock_mp_1._supported_media_commands = \
universal.SUPPORT_VOLUME_SET
self.mock_mp_1._state = STATE_PLAYING
self.mock_mp_1.update_ha_state()
ump.update()
check_flags = universal.SUPPORT_TURN_ON | universal.SUPPORT_TURN_OFF \
| universal.SUPPORT_VOLUME_STEP | universal.SUPPORT_VOLUME_MUTE
self.assertEqual(check_flags, ump.supported_media_commands)

View File

@ -4,12 +4,14 @@ tests.components.sensor.test_yr
Tests Yr sensor.
"""
from datetime import datetime
from unittest.mock import patch
import pytest
import homeassistant.core as ha
import homeassistant.components.sensor as sensor
import homeassistant.util.dt as dt_util
@pytest.mark.usefixtures('betamax_session')
@ -26,8 +28,12 @@ class TestSensorYr:
self.hass.stop()
def test_default_setup(self, betamax_session):
now = datetime(2016, 1, 5, 1, tzinfo=dt_util.UTC)
with patch('homeassistant.components.sensor.yr.requests.Session',
return_value=betamax_session):
with patch('homeassistant.components.sensor.yr.dt_util.utcnow',
return_value=now):
assert sensor.setup(self.hass, {
'sensor': {
'platform': 'yr',

View File

@ -27,12 +27,13 @@ API_URL = "http://127.0.0.1:{}{}".format(SERVER_PORT, alexa.API_ENDPOINT)
HA_HEADERS = {const.HTTP_HEADER_HA_AUTH: API_PASSWORD}
hass = None
calls = []
@patch('homeassistant.components.http.util.get_local_ip',
return_value='127.0.0.1')
def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
""" Initalizes a Home Assistant server. """
"""Initalize a Home Assistant server for testing this module."""
global hass
hass = ha.HomeAssistant()
@ -42,6 +43,8 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
{http.DOMAIN: {http.CONF_API_PASSWORD: API_PASSWORD,
http.CONF_SERVER_PORT: SERVER_PORT}})
hass.services.register('test', 'alexa', lambda call: calls.append(call))
bootstrap.setup_component(hass, alexa.DOMAIN, {
'alexa': {
'intents': {
@ -61,7 +64,20 @@ def setUpModule(mock_get_local_ip): # pylint: disable=invalid-name
'GetZodiacHoroscopeIntent': {
'speech': {
'type': 'plaintext',
'text': 'You told us your sign is {{ ZodiacSign }}.'
'text': 'You told us your sign is {{ ZodiacSign }}.',
}
},
'CallServiceIntent': {
'speech': {
'type': 'plaintext',
'text': 'Service called',
},
'action': {
'service': 'test.alexa',
'data': {
'hello': 1
},
'entity_id': 'switch.test',
}
}
}
@ -231,6 +247,39 @@ class TestAlexa(unittest.TestCase):
text = req.json().get('response', {}).get('outputSpeech', {}).get('text')
self.assertEqual('You are both home, you silly', text)
def test_intent_request_calling_service(self):
data = {
'version': '1.0',
'session': {
'new': False,
'sessionId': 'amzn1.echo-api.session.0000000-0000-0000-0000-00000000000',
'application': {
'applicationId': 'amzn1.echo-sdk-ams.app.000000-d0ed-0000-ad00-000000d00ebe'
},
'attributes': {},
'user': {
'userId': 'amzn1.account.AM3B00000000000000000000000'
}
},
'request': {
'type': 'IntentRequest',
'requestId': ' amzn1.echo-api.request.0000000-0000-0000-0000-00000000000',
'timestamp': '2015-05-13T12:34:56Z',
'intent': {
'name': 'CallServiceIntent',
}
}
}
call_count = len(calls)
req = _req(data)
self.assertEqual(200, req.status_code)
self.assertEqual(call_count + 1, len(calls))
call = calls[-1]
self.assertEqual('test', call.domain)
self.assertEqual('alexa', call.service)
self.assertEqual(['switch.test'], call.data.get('entity_id'))
self.assertEqual(1, call.data.get('hello'))
def test_session_ended_request(self):
data = {
'version': '1.0',

View File

@ -6,20 +6,22 @@ Tests core compoments.
"""
# pylint: disable=protected-access,too-many-public-methods
import unittest
from unittest.mock import patch
import homeassistant.core as ha
import homeassistant.loader as loader
from homeassistant.const import (
STATE_ON, STATE_OFF, SERVICE_TURN_ON, SERVICE_TURN_OFF)
import homeassistant.components as comps
from tests.common import get_test_home_assistant
class TestComponentsCore(unittest.TestCase):
""" Tests homeassistant.components module. """
def setUp(self): # pylint: disable=invalid-name
""" Init needed objects. """
self.hass = ha.HomeAssistant()
self.hass = get_test_home_assistant()
self.assertTrue(comps.setup(self.hass, {}))
self.hass.states.set('light.Bowl', STATE_ON)
@ -58,3 +60,24 @@ class TestComponentsCore(unittest.TestCase):
self.hass.pool.block_till_done()
self.assertEqual(1, len(runs))
@patch('homeassistant.core.ServiceRegistry.call')
def test_turn_on_to_not_block_for_domains_without_service(self, mock_call):
self.hass.services.register('light', SERVICE_TURN_ON, lambda x: x)
# We can't test if our service call results in services being called
# because by mocking out the call service method, we mock out all
# So we mimick how the service registry calls services
service_call = ha.ServiceCall('homeassistant', 'turn_on', {
'entity_id': ['light.test', 'sensor.bla', 'light.bla']
})
self.hass.services._services['homeassistant']['turn_on'](service_call)
self.assertEqual(2, mock_call.call_count)
self.assertEqual(
('light', 'turn_on', {'entity_id': ['light.bla', 'light.test']},
True),
mock_call.call_args_list[0][0])
self.assertEqual(
('sensor', 'turn_on', {'entity_id': ['sensor.bla']}, False),
mock_call.call_args_list[1][0])

View File

@ -0,0 +1,139 @@
"""
tests.test_component_mqtt_eventstream
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Tests MQTT eventstream component.
"""
import json
import unittest
from unittest.mock import ANY, patch
import homeassistant.components.mqtt_eventstream as eventstream
from homeassistant.const import EVENT_STATE_CHANGED
from homeassistant.core import State
from homeassistant.remote import JSONEncoder
import homeassistant.util.dt as dt_util
from tests.common import (
get_test_home_assistant,
mock_mqtt_component,
fire_mqtt_message,
mock_state_change_event,
fire_time_changed
)
class TestMqttEventStream(unittest.TestCase):
""" Test the MQTT eventstream module. """
def setUp(self): # pylint: disable=invalid-name
super(TestMqttEventStream, self).setUp()
self.hass = get_test_home_assistant()
self.mock_mqtt = mock_mqtt_component(self.hass)
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def add_eventstream(self, sub_topic=None, pub_topic=None):
""" Add a mqtt_eventstream component to the hass. """
config = {}
if sub_topic:
config['subscribe_topic'] = sub_topic
if pub_topic:
config['publish_topic'] = pub_topic
return eventstream.setup(self.hass, {eventstream.DOMAIN: config})
def test_setup_succeeds(self):
self.assertTrue(self.add_eventstream())
def test_setup_with_pub(self):
# Should start off with no listeners for all events
self.assertEqual(self.hass.bus.listeners.get('*'), None)
self.assertTrue(self.add_eventstream(pub_topic='bar'))
self.hass.pool.block_till_done()
# Verify that the event handler has been added as a listener
self.assertEqual(self.hass.bus.listeners.get('*'), 1)
@patch('homeassistant.components.mqtt.subscribe')
def test_subscribe(self, mock_sub):
sub_topic = 'foo'
self.assertTrue(self.add_eventstream(sub_topic=sub_topic))
self.hass.pool.block_till_done()
# Verify that the this entity was subscribed to the topic
mock_sub.assert_called_with(self.hass, sub_topic, ANY)
@patch('homeassistant.components.mqtt.publish')
@patch('homeassistant.core.dt_util.datetime_to_str')
def test_state_changed_event_sends_message(self, mock_datetime, mock_pub):
now = '00:19:19 11-01-2016'
e_id = 'fake.entity'
pub_topic = 'bar'
mock_datetime.return_value = now
# Add the eventstream component for publishing events
self.assertTrue(self.add_eventstream(pub_topic=pub_topic))
self.hass.pool.block_till_done()
# Reset the mock because it will have already gotten calls for the
# mqtt_eventstream state change on initialization, etc.
mock_pub.reset_mock()
# Set a state of an entity
mock_state_change_event(self.hass, State(e_id, 'on'))
self.hass.pool.block_till_done()
# The order of the JSON is indeterminate,
# so first just check that publish was called
mock_pub.assert_called_with(self.hass, pub_topic, ANY)
self.assertTrue(mock_pub.called)
# Get the actual call to publish and make sure it was the one
# we were looking for
msg = mock_pub.call_args[0][2]
event = {}
event['event_type'] = EVENT_STATE_CHANGED
new_state = {
"last_updated": now,
"state": "on",
"entity_id": e_id,
"attributes": {},
"last_changed": now
}
event['event_data'] = {"new_state": new_state, "entity_id": e_id}
# Verify that the message received was that expected
self.assertEqual(json.loads(msg), event)
@patch('homeassistant.components.mqtt.publish')
def test_time_event_does_not_send_message(self, mock_pub):
self.assertTrue(self.add_eventstream(pub_topic='bar'))
self.hass.pool.block_till_done()
# Reset the mock because it will have already gotten calls for the
# mqtt_eventstream state change on initialization, etc.
mock_pub.reset_mock()
fire_time_changed(self.hass, dt_util.utcnow())
self.assertFalse(mock_pub.called)
def test_receiving_remote_event_fires_hass_event(self):
sub_topic = 'foo'
self.assertTrue(self.add_eventstream(sub_topic=sub_topic))
self.hass.pool.block_till_done()
calls = []
self.hass.bus.listen_once('test_event', lambda _: calls.append(1))
self.hass.pool.block_till_done()
payload = json.dumps(
{'event_type': 'test_event', 'event_data': {}},
cls=JSONEncoder
)
fire_mqtt_message(self.hass, sub_topic, payload)
self.hass.pool.block_till_done()
self.assertEqual(1, len(calls))

View File

@ -0,0 +1,68 @@
"""
tests.helpers.test_service
~~~~~~~~~~~~~~~~~~~~~~~~~~
Test service helpers.
"""
import unittest
from unittest.mock import patch
from homeassistant.const import SERVICE_TURN_ON
from homeassistant.helpers import service
from tests.common import get_test_home_assistant, mock_service
class TestServiceHelpers(unittest.TestCase):
"""
Tests the Home Assistant service helpers.
"""
def setUp(self): # pylint: disable=invalid-name
""" things to be run when tests are started. """
self.hass = get_test_home_assistant()
self.calls = mock_service(self.hass, 'test_domain', 'test_service')
def tearDown(self): # pylint: disable=invalid-name
""" Stop down stuff we started. """
self.hass.stop()
def test_split_entity_string(self):
service.call_from_config(self.hass, {
'service': 'test_domain.test_service',
'entity_id': 'hello.world, sensor.beer'
})
self.hass.pool.block_till_done()
self.assertEqual(['hello.world', 'sensor.beer'],
self.calls[-1].data.get('entity_id'))
def test_not_mutate_input(self):
orig = {
'service': 'test_domain.test_service',
'entity_id': 'hello.world, sensor.beer',
'data': {
'hello': 1,
},
}
service.call_from_config(self.hass, orig)
self.hass.pool.block_till_done()
self.assertEqual({
'service': 'test_domain.test_service',
'entity_id': 'hello.world, sensor.beer',
'data': {
'hello': 1,
},
}, orig)
@patch('homeassistant.helpers.service._LOGGER.error')
def test_fail_silently_if_no_service(self, mock_log):
service.call_from_config(self.hass, None)
self.assertEqual(1, mock_log.call_count)
service.call_from_config(self.hass, {})
self.assertEqual(2, mock_log.call_count)
service.call_from_config(self.hass, {
'service': 'invalid'
})
self.assertEqual(3, mock_log.call_count)