Merge pull request #11470 from home-assistant/release-0-60-1

0.60.1
pull/11476/head 0.60.1
Paulus Schoutsen 2018-01-05 12:55:13 -08:00 committed by GitHub
commit c526fcd40f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 319 additions and 140 deletions

View File

@ -245,7 +245,7 @@ def async_api_turn_on(hass, config, request, entity):
yield from hass.services.async_call(domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -261,7 +261,7 @@ def async_api_turn_off(hass, config, request, entity):
yield from hass.services.async_call(domain, SERVICE_TURN_OFF, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -276,7 +276,7 @@ def async_api_set_brightness(hass, config, request, entity):
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness,
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -300,7 +300,7 @@ def async_api_adjust_brightness(hass, config, request, entity):
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_BRIGHTNESS_PCT: brightness,
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -321,14 +321,14 @@ def async_api_set_color(hass, config, request, entity):
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_RGB_COLOR: rgb,
}, blocking=True)
}, blocking=False)
else:
xyz = color_util.color_RGB_to_xy(*rgb)
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_XY_COLOR: (xyz[0], xyz[1]),
light.ATTR_BRIGHTNESS: xyz[2],
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -343,7 +343,7 @@ def async_api_set_color_temperature(hass, config, request, entity):
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_KELVIN: kelvin,
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -361,7 +361,7 @@ def async_api_decrease_color_temp(hass, config, request, entity):
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value,
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -379,7 +379,7 @@ def async_api_increase_color_temp(hass, config, request, entity):
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id,
light.ATTR_COLOR_TEMP: value,
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -391,7 +391,7 @@ def async_api_activate(hass, config, request, entity):
"""Process a activate request."""
yield from hass.services.async_call(entity.domain, SERVICE_TURN_ON, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -421,8 +421,8 @@ def async_api_set_percentage(hass, config, request, entity):
service = SERVICE_SET_COVER_POSITION
data[cover.ATTR_POSITION] = percentage
yield from hass.services.async_call(entity.domain, service,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, service, data, blocking=False)
return api_message(request)
@ -469,8 +469,8 @@ def async_api_adjust_percentage(hass, config, request, entity):
data[cover.ATTR_POSITION] = max(0, percentage_delta + current)
yield from hass.services.async_call(entity.domain, service,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, service, data, blocking=False)
return api_message(request)
@ -482,7 +482,7 @@ def async_api_lock(hass, config, request, entity):
"""Process a lock request."""
yield from hass.services.async_call(entity.domain, SERVICE_LOCK, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -495,7 +495,7 @@ def async_api_unlock(hass, config, request, entity):
"""Process a unlock request."""
yield from hass.services.async_call(entity.domain, SERVICE_UNLOCK, {
ATTR_ENTITY_ID: entity.entity_id
}, blocking=True)
}, blocking=False)
return api_message(request)
@ -512,8 +512,9 @@ def async_api_set_volume(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
yield from hass.services.async_call(entity.domain, SERVICE_VOLUME_SET,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, SERVICE_VOLUME_SET,
data, blocking=False)
return api_message(request)
@ -540,9 +541,9 @@ def async_api_adjust_volume(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_LEVEL: volume,
}
yield from hass.services.async_call(entity.domain,
media_player.SERVICE_VOLUME_SET,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_SET,
data, blocking=False)
return api_message(request)
@ -559,9 +560,9 @@ def async_api_set_mute(hass, config, request, entity):
media_player.ATTR_MEDIA_VOLUME_MUTED: mute,
}
yield from hass.services.async_call(entity.domain,
media_player.SERVICE_VOLUME_MUTE,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, media_player.SERVICE_VOLUME_MUTE,
data, blocking=False)
return api_message(request)
@ -575,8 +576,9 @@ def async_api_play(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain, SERVICE_MEDIA_PLAY,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, SERVICE_MEDIA_PLAY,
data, blocking=False)
return api_message(request)
@ -590,8 +592,9 @@ def async_api_pause(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain, SERVICE_MEDIA_PAUSE,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, SERVICE_MEDIA_PAUSE,
data, blocking=False)
return api_message(request)
@ -605,8 +608,9 @@ def async_api_stop(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain, SERVICE_MEDIA_STOP,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, SERVICE_MEDIA_STOP,
data, blocking=False)
return api_message(request)
@ -620,9 +624,9 @@ def async_api_next(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain,
SERVICE_MEDIA_NEXT_TRACK,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, SERVICE_MEDIA_NEXT_TRACK,
data, blocking=False)
return api_message(request)
@ -636,8 +640,8 @@ def async_api_previous(hass, config, request, entity):
ATTR_ENTITY_ID: entity.entity_id
}
yield from hass.services.async_call(entity.domain,
SERVICE_MEDIA_PREVIOUS_TRACK,
data, blocking=True)
yield from hass.services.async_call(
entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK,
data, blocking=False)
return api_message(request)

View File

@ -62,6 +62,13 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
_LOGGER.error("Unable to connect to Concord232: %s", str(ex))
return False
# The order of zones returned by client.list_zones() can vary.
# When the zones are not named, this can result in the same entity
# name mapping to different sensors in an unpredictable way. Sort
# the zones by zone number to prevent this.
client.zones.sort(key=lambda zone: zone['number'])
for zone in client.zones:
_LOGGER.info("Loading Zone found: %s", zone['name'])
if zone['number'] not in exclude:
@ -118,7 +125,7 @@ class Concord232ZoneSensor(BinarySensorDevice):
def is_on(self):
"""Return true if the binary sensor is on."""
# True means "faulted" or "open" or "abnormal state"
return bool(self._zone['state'] == 'Normal')
return bool(self._zone['state'] != 'Normal')
def update(self):
"""Get updated stats from API."""

View File

@ -62,10 +62,9 @@ def setup_platform(hass, config: ConfigType,
node.nid, node.parent_nid)
else:
device_type = _detect_device_type(node)
if device_type in ['moisture', 'opening']:
subnode_id = int(node.nid[-1])
# Leak and door/window sensors work the same way with negative
# nodes and heartbeat nodes
subnode_id = int(node.nid[-1])
if device_type == 'opening':
# Door/window sensors use an optional "negative" node
if subnode_id == 4:
# Subnode 4 is the heartbeat node, which we will represent
# as a separate binary_sensor
@ -74,6 +73,14 @@ def setup_platform(hass, config: ConfigType,
devices.append(device)
elif subnode_id == 2:
parent_device.add_negative_node(node)
elif device_type == 'moisture':
# Moisure nodes have a subnode 2, but we ignore it because it's
# just the inverse of the primary node.
if subnode_id == 4:
# Heartbeat node
device = ISYBinarySensorHeartbeat(node, parent_device)
parent_device.add_heartbeat_device(device)
devices.append(device)
else:
# We don't yet have any special logic for other sensor types,
# so add the nodes as individual devices
@ -165,7 +172,8 @@ class ISYBinarySensorDevice(isy.ISYDevice, BinarySensorDevice):
"""
self._negative_node = child
if not _is_val_unknown(self._negative_node):
# pylint: disable=protected-access
if not _is_val_unknown(self._negative_node.status._val):
# If the negative node has a value, it means the negative node is
# in use for this device. Therefore, we cannot determine the state
# of the sensor until we receive our first ON event.

View File

@ -25,11 +25,11 @@ CONF_DEVICE_ID = 'device_id'
CONF_CALENDARS = 'calendars'
CONF_CUSTOM_CALENDARS = 'custom_calendars'
CONF_CALENDAR = 'calendar'
CONF_ALL_DAY = 'all_day'
CONF_SEARCH = 'search'
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_URL): vol.Url,
# pylint: disable=no-value-for-parameter
vol.Required(CONF_URL): vol.Url(),
vol.Optional(CONF_CALENDARS, default=[]):
vol.All(cv.ensure_list, vol.Schema([
cv.string
@ -88,7 +88,7 @@ def setup_platform(hass, config, add_devices, disc_info=None):
WebDavCalendarEventDevice(hass,
device_data,
calendar,
cust_calendar.get(CONF_ALL_DAY),
True,
cust_calendar.get(CONF_SEARCH))
)

View File

@ -582,9 +582,10 @@ def _is_latest(js_option, request):
from user_agents import parse
useragent = parse(request.headers.get('User-Agent'))
# on iOS every browser is a Safari which we support from version 10.
# on iOS every browser is a Safari which we support from version 11.
if useragent.os.family == 'iOS':
return useragent.os.version[0] >= 10
# Was >= 10, temp setting it to 12 to work around issue #11387
return useragent.os.version[0] >= 12
family_min_version = {
'Chrome': 50, # Probably can reduce this

View File

@ -169,6 +169,8 @@ CONFIG_SCHEMA = vol.Schema({
vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string,
vol.Optional(CONF_RESOLVENAMES, default=DEFAULT_RESOLVENAMES):
vol.In(CONF_RESOLVENAMES_OPTIONS),
vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string,
vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string,
vol.Optional(CONF_CALLBACK_IP): cv.string,
vol.Optional(CONF_CALLBACK_PORT): cv.port,
}},
@ -747,10 +749,6 @@ class HMDevice(Entity):
"""Return device specific state attributes."""
attr = {}
# No data available
if not self.available:
return attr
# Generate a dictionary with attributes
for node, data in HM_ATTRIBUTE_SUPPORT.items():
# Is an attribute and exists for this object
@ -806,6 +804,9 @@ class HMDevice(Entity):
if attribute == 'UNREACH':
self._available = bool(value)
has_changed = True
elif not self.available:
self._available = False
has_changed = True
# If it has changed data point, update HASS
if has_changed:

View File

@ -160,6 +160,8 @@ class HueBridge(object):
self.allow_hue_groups = allow_hue_groups
self.bridge = None
self.lights = {}
self.lightgroups = {}
self.configured = False
self.config_request_id = None

View File

@ -31,10 +31,6 @@ DEPENDENCIES = ['hue']
_LOGGER = logging.getLogger(__name__)
DATA_KEY = 'hue_lights'
DATA_LIGHTS = 'lights'
DATA_LIGHTGROUPS = 'lightgroups'
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
MIN_TIME_BETWEEN_FORCED_SCANS = timedelta(milliseconds=100)
@ -93,8 +89,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
if discovery_info is None or 'bridge_id' not in discovery_info:
return
setup_data(hass)
if config is not None and len(config) > 0:
# Legacy configuration, will be removed in 0.60
config_str = yaml.dump([config])
@ -110,12 +104,6 @@ def setup_platform(hass, config, add_devices, discovery_info=None):
unthrottled_update_lights(hass, bridge, add_devices)
def setup_data(hass):
"""Initialize internal data. Useful from tests."""
if DATA_KEY not in hass.data:
hass.data[DATA_KEY] = {DATA_LIGHTS: {}, DATA_LIGHTGROUPS: {}}
@util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
def update_lights(hass, bridge, add_devices):
"""Update the Hue light objects with latest info from the bridge."""
@ -176,18 +164,17 @@ def process_lights(hass, api, bridge, bridge_type, update_lights_cb):
new_lights = []
lights = hass.data[DATA_KEY][DATA_LIGHTS]
for light_id, info in api_lights.items():
if light_id not in lights:
lights[light_id] = HueLight(
if light_id not in bridge.lights:
bridge.lights[light_id] = HueLight(
int(light_id), info, bridge,
update_lights_cb,
bridge_type, bridge.allow_unreachable,
bridge.allow_in_emulated_hue)
new_lights.append(lights[light_id])
new_lights.append(bridge.lights[light_id])
else:
lights[light_id].info = info
lights[light_id].schedule_update_ha_state()
bridge.lights[light_id].info = info
bridge.lights[light_id].schedule_update_ha_state()
return new_lights
@ -202,23 +189,22 @@ def process_groups(hass, api, bridge, bridge_type, update_lights_cb):
new_lights = []
groups = hass.data[DATA_KEY][DATA_LIGHTGROUPS]
for lightgroup_id, info in api_groups.items():
if 'state' not in info:
_LOGGER.warning('Group info does not contain state. '
'Please update your hub.')
return []
if lightgroup_id not in groups:
groups[lightgroup_id] = HueLight(
if lightgroup_id not in bridge.lightgroups:
bridge.lightgroups[lightgroup_id] = HueLight(
int(lightgroup_id), info, bridge,
update_lights_cb,
bridge_type, bridge.allow_unreachable,
bridge.allow_in_emulated_hue, True)
new_lights.append(groups[lightgroup_id])
new_lights.append(bridge.lightgroups[lightgroup_id])
else:
groups[lightgroup_id].info = info
groups[lightgroup_id].schedule_update_ha_state()
bridge.lightgroups[lightgroup_id].info = info
bridge.lightgroups[lightgroup_id].schedule_update_ha_state()
return new_lights

View File

@ -25,46 +25,53 @@ PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
# pylint: disable=unused-argument
def setup_platform(hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
def setup_platform(
hass, config: ConfigType,
add_devices: Callable[[list], None], discovery_info=None):
"""Set up the Sesame platform."""
import pysesame
email = config.get(CONF_EMAIL)
password = config.get(CONF_PASSWORD)
add_devices([SesameDevice(sesame) for
sesame in pysesame.get_sesames(email, password)])
add_devices([SesameDevice(sesame) for sesame in
pysesame.get_sesames(email, password)],
update_before_add=True)
class SesameDevice(LockDevice):
"""Representation of a Sesame device."""
_sesame = None
def __init__(self, sesame: object) -> None:
"""Initialize the Sesame device."""
self._sesame = sesame
# Cached properties from pysesame object.
self._device_id = None
self._nickname = None
self._is_unlocked = False
self._api_enabled = False
self._battery = -1
@property
def name(self) -> str:
"""Return the name of the device."""
return self._sesame.nickname
return self._nickname
@property
def available(self) -> bool:
"""Return True if entity is available."""
return self._sesame.api_enabled
return self._api_enabled
@property
def is_locked(self) -> bool:
"""Return True if the device is currently locked, else False."""
return not self._sesame.is_unlocked
return not self._is_unlocked
@property
def state(self) -> str:
"""Get the state of the device."""
if self._sesame.is_unlocked:
if self._is_unlocked:
return STATE_UNLOCKED
return STATE_LOCKED
@ -79,11 +86,16 @@ class SesameDevice(LockDevice):
def update(self) -> None:
"""Update the internal state of the device."""
self._sesame.update_state()
self._nickname = self._sesame.nickname
self._api_enabled = self._sesame.api_enabled
self._is_unlocked = self._sesame.is_unlocked
self._device_id = self._sesame.device_id
self._battery = self._sesame.battery
@property
def device_state_attributes(self) -> dict:
"""Return the state attributes."""
attributes = {}
attributes[ATTR_DEVICE_ID] = self._sesame.device_id
attributes[ATTR_BATTERY_LEVEL] = self._sesame.battery
attributes[ATTR_DEVICE_ID] = self._device_id
attributes[ATTR_BATTERY_LEVEL] = self._battery
return attributes

View File

@ -322,17 +322,17 @@ class LgWebOSDevice(MediaPlayerDevice):
def select_source(self, source):
"""Select input source."""
source = self._source_list.get(source)
if source is None:
source_dict = self._source_list.get(source)
if source_dict is None:
_LOGGER.warning("Source %s not found for %s", source, self.name)
return
self._current_source_id = self._source_list[source]['id']
if source.get('title'):
self._current_source = self._source_list[source]['title']
self._client.launch_app(self._source_list[source]['id'])
elif source.get('label'):
self._current_source = self._source_list[source]['label']
self._client.set_input(self._source_list[source]['id'])
self._current_source_id = source_dict['id']
if source_dict.get('title'):
self._current_source = source_dict['title']
self._client.launch_app(source_dict['id'])
elif source_dict.get('label'):
self._current_source = source_dict['label']
self._client.set_input(source_dict['id'])
def media_play(self):
"""Send play command."""

View File

@ -141,10 +141,17 @@ class ModbusRegisterSwitch(ModbusCoilSwitch):
self._verify_register = (
verify_register if verify_register else self._register)
self._register_type = register_type
self._state_on = (
state_on if state_on else self._command_on)
self._state_off = (
state_off if state_off else self._command_off)
if state_on is not None:
self._state_on = state_on
else:
self._state_on = self._command_on
if state_off is not None:
self._state_off = state_off
else:
self._state_off = self._command_off
self._is_on = None
def turn_on(self, **kwargs):

View File

@ -2,7 +2,7 @@
"""Constants used by Home Assistant components."""
MAJOR_VERSION = 0
MINOR_VERSION = 60
PATCH_VERSION = '0'
PATCH_VERSION = '1'
__short_version__ = '{}.{}'.format(MAJOR_VERSION, MINOR_VERSION)
__version__ = '{}.{}'.format(__short_version__, PATCH_VERSION)
REQUIRED_PYTHON_VER = (3, 4, 2)

View File

@ -346,6 +346,7 @@ def test_exclude_filters(hass):
))
msg = yield from smart_home.async_handle_message(hass, config, request)
yield from hass.async_block_till_done()
msg = msg['event']
@ -378,6 +379,7 @@ def test_include_filters(hass):
))
msg = yield from smart_home.async_handle_message(hass, config, request)
yield from hass.async_block_till_done()
msg = msg['event']
@ -393,6 +395,7 @@ def test_api_entity_not_exists(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -442,6 +445,7 @@ def test_api_turn_on(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -475,6 +479,7 @@ def test_api_turn_off(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -501,6 +506,7 @@ def test_api_set_brightness(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -532,6 +538,7 @@ def test_api_adjust_brightness(hass, result, adjust):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -566,6 +573,7 @@ def test_api_set_color_rgb(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -600,6 +608,7 @@ def test_api_set_color_xy(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -629,6 +638,7 @@ def test_api_set_color_temperature(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -658,6 +668,7 @@ def test_api_decrease_color_temp(hass, result, initial):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -687,6 +698,7 @@ def test_api_increase_color_temp(hass, result, initial):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -714,6 +726,7 @@ def test_api_activate(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -740,6 +753,7 @@ def test_api_set_percentage_fan(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -769,6 +783,7 @@ def test_api_set_percentage_cover(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -800,6 +815,7 @@ def test_api_adjust_percentage_fan(hass, result, adjust):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -832,6 +848,7 @@ def test_api_adjust_percentage_cover(hass, result, adjust):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -859,6 +876,7 @@ def test_api_lock(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -885,6 +903,7 @@ def test_api_play(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -911,6 +930,7 @@ def test_api_pause(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -937,6 +957,7 @@ def test_api_stop(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -963,6 +984,7 @@ def test_api_next(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -989,6 +1011,7 @@ def test_api_previous(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -1017,6 +1040,7 @@ def test_api_set_volume(hass):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -1048,6 +1072,7 @@ def test_api_adjust_volume(hass, result, adjust):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']
@ -1077,6 +1102,7 @@ def test_api_mute(hass, domain):
msg = yield from smart_home.async_handle_message(
hass, DEFAULT_CONFIG, request)
yield from hass.async_block_till_done()
assert 'event' in msg
msg = msg['event']

View File

@ -121,8 +121,10 @@ class TestComponentsWebDavCalendar(unittest.TestCase):
assert len(devices) == 2
assert devices[0].name == "First"
assert devices[0].dev_id == "First"
self.assertFalse(devices[0].data.include_all_day)
assert devices[1].name == "Second"
assert devices[1].dev_id == "Second"
self.assertFalse(devices[1].data.include_all_day)
caldav.setup_platform(self.hass,
{
@ -167,6 +169,7 @@ class TestComponentsWebDavCalendar(unittest.TestCase):
assert len(devices) == 1
assert devices[0].name == "HomeOffice"
assert devices[0].dev_id == "Second HomeOffice"
self.assertTrue(devices[0].data.include_all_day)
caldav.setup_platform(self.hass,
{

View File

@ -36,27 +36,45 @@ class TestSetup(unittest.TestCase):
self.mock_lights = []
self.mock_groups = []
self.mock_add_devices = MagicMock()
hue_light.setup_data(self.hass)
def setup_mocks_for_process_lights(self):
"""Set up all mocks for process_lights tests."""
self.mock_bridge = MagicMock()
self.mock_bridge = self.create_mock_bridge('host')
self.mock_api = MagicMock()
self.mock_api.get.return_value = {}
self.mock_bridge.get_api.return_value = self.mock_api
self.mock_bridge_type = MagicMock()
hue_light.setup_data(self.hass)
def setup_mocks_for_process_groups(self):
"""Set up all mocks for process_groups tests."""
self.mock_bridge = MagicMock()
self.mock_bridge = self.create_mock_bridge('host')
self.mock_bridge.get_group.return_value = {
'name': 'Group 0', 'state': {'any_on': True}}
self.mock_api = MagicMock()
self.mock_api.get.return_value = {}
self.mock_bridge.get_api.return_value = self.mock_api
self.mock_bridge_type = MagicMock()
hue_light.setup_data(self.hass)
def create_mock_bridge(self, host, allow_hue_groups=True):
"""Return a mock HueBridge with reasonable defaults."""
mock_bridge = MagicMock()
mock_bridge.host = host
mock_bridge.allow_hue_groups = allow_hue_groups
mock_bridge.lights = {}
mock_bridge.lightgroups = {}
return mock_bridge
def create_mock_lights(self, lights):
"""Return a dict suitable for mocking api.get('lights')."""
mock_bridge_lights = lights
for light_id, info in mock_bridge_lights.items():
if 'state' not in info:
info['state'] = {'on': False}
return mock_bridge_lights
def test_setup_platform_no_discovery_info(self):
"""Test setup_platform without discovery info."""
@ -211,6 +229,70 @@ class TestSetup(unittest.TestCase):
self.mock_add_devices.assert_called_once_with(
self.mock_lights)
@MockDependency('phue')
def test_update_lights_with_two_bridges(self, mock_phue):
"""Test the update_lights function with two bridges."""
self.setup_mocks_for_update_lights()
mock_bridge_one = self.create_mock_bridge('one', False)
mock_bridge_one_lights = self.create_mock_lights(
{1: {'name': 'b1l1'}, 2: {'name': 'b1l2'}})
mock_bridge_two = self.create_mock_bridge('two', False)
mock_bridge_two_lights = self.create_mock_lights(
{1: {'name': 'b2l1'}, 3: {'name': 'b2l3'}})
with patch('homeassistant.components.light.hue.get_bridge_type',
return_value=self.mock_bridge_type):
with patch('homeassistant.components.light.hue.HueLight.'
'schedule_update_ha_state'):
mock_api = MagicMock()
mock_api.get.return_value = mock_bridge_one_lights
with patch.object(mock_bridge_one, 'get_api',
return_value=mock_api):
hue_light.unthrottled_update_lights(
self.hass, mock_bridge_one, self.mock_add_devices)
mock_api = MagicMock()
mock_api.get.return_value = mock_bridge_two_lights
with patch.object(mock_bridge_two, 'get_api',
return_value=mock_api):
hue_light.unthrottled_update_lights(
self.hass, mock_bridge_two, self.mock_add_devices)
self.assertEquals(sorted(mock_bridge_one.lights.keys()), [1, 2])
self.assertEquals(sorted(mock_bridge_two.lights.keys()), [1, 3])
self.assertEquals(len(self.mock_add_devices.mock_calls), 2)
# first call
name, args, kwargs = self.mock_add_devices.mock_calls[0]
self.assertEquals(len(args), 1)
self.assertEquals(len(kwargs), 0)
# one argument, a list of lights in bridge one; each of them is an
# object of type HueLight so we can't straight up compare them
lights = args[0]
self.assertEquals(
lights[0].unique_id,
'{}.b1l1.Light.1'.format(hue_light.HueLight))
self.assertEquals(
lights[1].unique_id,
'{}.b1l2.Light.2'.format(hue_light.HueLight))
# second call works the same
name, args, kwargs = self.mock_add_devices.mock_calls[1]
self.assertEquals(len(args), 1)
self.assertEquals(len(kwargs), 0)
lights = args[0]
self.assertEquals(
lights[0].unique_id,
'{}.b2l1.Light.1'.format(hue_light.HueLight))
self.assertEquals(
lights[1].unique_id,
'{}.b2l3.Light.3'.format(hue_light.HueLight))
def test_process_lights_api_error(self):
"""Test the process_lights function when the bridge errors out."""
self.setup_mocks_for_process_lights()
@ -221,9 +303,7 @@ class TestSetup(unittest.TestCase):
None)
self.assertEquals([], ret)
self.assertEquals(
{},
self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS])
self.assertEquals(self.mock_bridge.lights, {})
def test_process_lights_no_lights(self):
"""Test the process_lights function when bridge returns no lights."""
@ -234,9 +314,7 @@ class TestSetup(unittest.TestCase):
None)
self.assertEquals([], ret)
self.assertEquals(
{},
self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS])
self.assertEquals(self.mock_bridge.lights, {})
@patch('homeassistant.components.light.hue.HueLight')
def test_process_lights_some_lights(self, mock_hue_light):
@ -260,9 +338,7 @@ class TestSetup(unittest.TestCase):
self.mock_bridge_type, self.mock_bridge.allow_unreachable,
self.mock_bridge.allow_in_emulated_hue),
])
self.assertEquals(
len(self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS]),
2)
self.assertEquals(len(self.mock_bridge.lights), 2)
@patch('homeassistant.components.light.hue.HueLight')
def test_process_lights_new_light(self, mock_hue_light):
@ -274,8 +350,7 @@ class TestSetup(unittest.TestCase):
self.setup_mocks_for_process_lights()
self.mock_api.get.return_value = {
1: {'state': 'on'}, 2: {'state': 'off'}}
self.hass.data[
hue_light.DATA_KEY][hue_light.DATA_LIGHTS][1] = MagicMock()
self.mock_bridge.lights = {1: MagicMock()}
ret = hue_light.process_lights(
self.hass, self.mock_api, self.mock_bridge, self.mock_bridge_type,
@ -288,11 +363,9 @@ class TestSetup(unittest.TestCase):
self.mock_bridge_type, self.mock_bridge.allow_unreachable,
self.mock_bridge.allow_in_emulated_hue),
])
self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS][
1].schedule_update_ha_state.assert_called_once_with()
self.assertEquals(
len(self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTS]),
2)
self.assertEquals(len(self.mock_bridge.lights), 2)
self.mock_bridge.lights[1]\
.schedule_update_ha_state.assert_called_once_with()
def test_process_groups_api_error(self):
"""Test the process_groups function when the bridge errors out."""
@ -304,9 +377,7 @@ class TestSetup(unittest.TestCase):
None)
self.assertEquals([], ret)
self.assertEquals(
{},
self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS])
self.assertEquals(self.mock_bridge.lightgroups, {})
def test_process_groups_no_state(self):
"""Test the process_groups function when bridge returns no status."""
@ -318,9 +389,7 @@ class TestSetup(unittest.TestCase):
None)
self.assertEquals([], ret)
self.assertEquals(
{},
self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS])
self.assertEquals(self.mock_bridge.lightgroups, {})
@patch('homeassistant.components.light.hue.HueLight')
def test_process_groups_some_groups(self, mock_hue_light):
@ -344,10 +413,7 @@ class TestSetup(unittest.TestCase):
self.mock_bridge_type, self.mock_bridge.allow_unreachable,
self.mock_bridge.allow_in_emulated_hue, True),
])
self.assertEquals(
len(self.hass.data[
hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS]),
2)
self.assertEquals(len(self.mock_bridge.lightgroups), 2)
@patch('homeassistant.components.light.hue.HueLight')
def test_process_groups_new_group(self, mock_hue_light):
@ -359,8 +425,7 @@ class TestSetup(unittest.TestCase):
self.setup_mocks_for_process_groups()
self.mock_api.get.return_value = {
1: {'state': 'on'}, 2: {'state': 'off'}}
self.hass.data[
hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS][1] = MagicMock()
self.mock_bridge.lightgroups = {1: MagicMock()}
ret = hue_light.process_groups(
self.hass, self.mock_api, self.mock_bridge, self.mock_bridge_type,
@ -373,12 +438,9 @@ class TestSetup(unittest.TestCase):
self.mock_bridge_type, self.mock_bridge.allow_unreachable,
self.mock_bridge.allow_in_emulated_hue, True),
])
self.hass.data[hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS][
1].schedule_update_ha_state.assert_called_once_with()
self.assertEquals(
len(self.hass.data[
hue_light.DATA_KEY][hue_light.DATA_LIGHTGROUPS]),
2)
self.assertEquals(len(self.mock_bridge.lightgroups), 2)
self.mock_bridge.lightgroups[1]\
.schedule_update_ha_state.assert_called_once_with()
class TestHueLight(unittest.TestCase):

View File

@ -0,0 +1,60 @@
"""The tests for the LG webOS media player platform."""
import unittest
from unittest import mock
from homeassistant.components.media_player import webostv
class FakeLgWebOSDevice(webostv.LgWebOSDevice):
"""A fake device without the client setup required for the real one."""
def __init__(self, *args, **kwargs):
"""Initialise parameters needed for tests with fake values."""
self._source_list = {}
self._client = mock.MagicMock()
self._name = 'fake_device'
self._current_source = None
class TestLgWebOSDevice(unittest.TestCase):
"""Test the LgWebOSDevice class."""
def setUp(self):
"""Configure a fake device for each test."""
self.device = FakeLgWebOSDevice()
def test_select_source_with_empty_source_list(self):
"""Ensure we don't call client methods when we don't have sources."""
self.device.select_source('nonexistent')
assert 0 == self.device._client.launch_app.call_count
assert 0 == self.device._client.set_input.call_count
def test_select_source_with_titled_entry(self):
"""Test that a titled source is treated as an app."""
self.device._source_list = {
'existent': {
'id': 'existent_id',
'title': 'existent_title',
},
}
self.device.select_source('existent')
assert 'existent_title' == self.device._current_source
assert [mock.call('existent_id')] == (
self.device._client.launch_app.call_args_list)
def test_select_source_with_labelled_entry(self):
"""Test that a labelled source is treated as an input source."""
self.device._source_list = {
'existent': {
'id': 'existent_id',
'label': 'existent_label',
},
}
self.device.select_source('existent')
assert 'existent_label' == self.device._current_source
assert [mock.call('existent_id')] == (
self.device._client.set_input.call_args_list)