Group service / dynamic handling (#7971)
* Add Service to group
* Finish service
* Add service functions
* fix lint
* Address paulus comments
* fix lint & cleanup
* fix lint
* fix lint
* fix lint p3
* add test for check group
* add more tests
* fix lint
* Update service.yaml
* Fix order for tests
* Fix comment
* Fix test
* Fix tests
* Fix name in tests
* Fix view
* Fix default value
* Fix lint
* Fix key error
* add name
* migrate component entity
* fix tests
* fix import
* migrate device tracker
* fix lint
* fix bug
* fix logic
* fix lint
* fix tests
* fix generator
* fix group
* fix other tests.
* Not need to load group on first stage anymore.
* fix service
* add more group depency
* fix tests
* Revert "fix tests"
This reverts commit 35a922b3a8
.
* Real fix
* fix test p2
* fix test p3
* fix test p4
* fix test p5
* fix test p6
* fix lint
* fix test p7
* Rename attribute
* fix group test
* fix bug
* fix flagy tests
* fix service.yaml
* fix lint
pull/8037/merge
parent
46f3088a70
commit
814834512a
|
@ -29,6 +29,7 @@ import homeassistant.helpers.config_validation as cv
|
|||
from homeassistant.components.frontend import register_built_in_panel
|
||||
|
||||
DOMAIN = 'automation'
|
||||
DEPENDENCIES = ['group']
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
GROUP_NAME_ALL_AUTOMATIONS = 'all automations'
|
||||
|
|
|
@ -27,6 +27,7 @@ from homeassistant.const import (
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'cover'
|
||||
DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=15)
|
||||
|
||||
GROUP_NAME_ALL_COVERS = 'all covers'
|
||||
|
|
|
@ -27,6 +27,7 @@ from homeassistant.helpers.event import async_track_time_interval
|
|||
from homeassistant.helpers.restore_state import async_get_last_state
|
||||
from homeassistant.helpers.typing import GPSType, ConfigType, HomeAssistantType
|
||||
import homeassistant.helpers.config_validation as cv
|
||||
from homeassistant.loader import get_component
|
||||
import homeassistant.util as util
|
||||
from homeassistant.util.async import run_coroutine_threadsafe
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
@ -41,7 +42,7 @@ from homeassistant.const import (
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'device_tracker'
|
||||
DEPENDENCIES = ['zone']
|
||||
DEPENDENCIES = ['zone', 'group']
|
||||
|
||||
GROUP_NAME_ALL_DEVICES = 'all devices'
|
||||
ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices')
|
||||
|
@ -175,7 +176,7 @@ def async_setup(hass: HomeAssistantType, config: ConfigType):
|
|||
if setup_tasks:
|
||||
yield from asyncio.wait(setup_tasks, loop=hass.loop)
|
||||
|
||||
yield from tracker.async_setup_group()
|
||||
tracker.async_setup_group()
|
||||
|
||||
@callback
|
||||
def async_device_tracker_discovered(service, info):
|
||||
|
@ -228,7 +229,7 @@ class DeviceTracker(object):
|
|||
self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac}
|
||||
self.consider_home = consider_home
|
||||
self.track_new = track_new
|
||||
self.group = None # type: group.Group
|
||||
self.group = None
|
||||
self._is_updating = asyncio.Lock(loop=hass.loop)
|
||||
|
||||
for dev in devices:
|
||||
|
@ -302,9 +303,10 @@ class DeviceTracker(object):
|
|||
})
|
||||
|
||||
# During init, we ignore the group
|
||||
if self.group is not None:
|
||||
yield from self.group.async_update_tracked_entity_ids(
|
||||
list(self.group.tracking) + [device.entity_id])
|
||||
if self.group and self.track_new:
|
||||
self.group.async_set_group(
|
||||
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
|
||||
name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id])
|
||||
|
||||
# lookup mac vendor string to be stored in config
|
||||
yield from device.set_vendor_for_mac()
|
||||
|
@ -326,16 +328,19 @@ class DeviceTracker(object):
|
|||
update_config, self.hass.config.path(YAML_DEVICES),
|
||||
dev_id, device)
|
||||
|
||||
@asyncio.coroutine
|
||||
@callback
|
||||
def async_setup_group(self):
|
||||
"""Initialize group for all tracked devices.
|
||||
|
||||
This method is a coroutine.
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
entity_ids = (dev.entity_id for dev in self.devices.values()
|
||||
if dev.track)
|
||||
self.group = yield from group.Group.async_create_group(
|
||||
self.hass, GROUP_NAME_ALL_DEVICES, entity_ids, False)
|
||||
entity_ids = [dev.entity_id for dev in self.devices.values()
|
||||
if dev.track]
|
||||
|
||||
self.group = get_component('group')
|
||||
self.group.async_set_group(
|
||||
self.hass, util.slugify(GROUP_NAME_ALL_DEVICES), visible=False,
|
||||
name=GROUP_NAME_ALL_DEVICES, entity_ids=entity_ids)
|
||||
|
||||
@callback
|
||||
def async_update_stale(self, now: dt_util.dt.datetime):
|
||||
|
|
|
@ -25,7 +25,7 @@ import homeassistant.helpers.config_validation as cv
|
|||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'fan'
|
||||
|
||||
DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
GROUP_NAME_ALL_FANS = 'all fans'
|
||||
|
|
|
@ -30,13 +30,23 @@ CONF_ENTITIES = 'entities'
|
|||
CONF_VIEW = 'view'
|
||||
CONF_CONTROL = 'control'
|
||||
|
||||
ATTR_ADD_ENTITIES = 'add_entities'
|
||||
ATTR_AUTO = 'auto'
|
||||
ATTR_CONTROL = 'control'
|
||||
ATTR_ENTITIES = 'entities'
|
||||
ATTR_ICON = 'icon'
|
||||
ATTR_NAME = 'name'
|
||||
ATTR_OBJECT_ID = 'object_id'
|
||||
ATTR_ORDER = 'order'
|
||||
ATTR_VIEW = 'view'
|
||||
ATTR_VISIBLE = 'visible'
|
||||
ATTR_CONTROL = 'control'
|
||||
|
||||
SERVICE_SET_VISIBILITY = 'set_visibility'
|
||||
SERVICE_SET = 'set'
|
||||
SERVICE_REMOVE = 'remove'
|
||||
|
||||
CONTROL_TYPES = vol.In(['hidden', None])
|
||||
|
||||
SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Required(ATTR_VISIBLE): cv.boolean
|
||||
|
@ -44,6 +54,21 @@ SET_VISIBILITY_SERVICE_SCHEMA = vol.Schema({
|
|||
|
||||
RELOAD_SERVICE_SCHEMA = vol.Schema({})
|
||||
|
||||
SET_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_OBJECT_ID): cv.slug,
|
||||
vol.Optional(ATTR_NAME): cv.string,
|
||||
vol.Optional(ATTR_VIEW): cv.boolean,
|
||||
vol.Optional(ATTR_ICON): cv.string,
|
||||
vol.Optional(ATTR_CONTROL): CONTROL_TYPES,
|
||||
vol.Optional(ATTR_VISIBLE): cv.boolean,
|
||||
vol.Exclusive(ATTR_ENTITIES, 'entities'): cv.entity_ids,
|
||||
vol.Exclusive(ATTR_ADD_ENTITIES, 'entities'): cv.entity_ids,
|
||||
})
|
||||
|
||||
REMOVE_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Required(ATTR_OBJECT_ID): cv.slug,
|
||||
})
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -60,7 +85,7 @@ GROUP_SCHEMA = vol.Schema({
|
|||
CONF_VIEW: cv.boolean,
|
||||
CONF_NAME: cv.string,
|
||||
CONF_ICON: cv.icon,
|
||||
CONF_CONTROL: cv.string,
|
||||
CONF_CONTROL: CONTROL_TYPES,
|
||||
})
|
||||
|
||||
CONFIG_SCHEMA = vol.Schema({
|
||||
|
@ -99,10 +124,10 @@ def reload(hass):
|
|||
hass.add_job(async_reload, hass)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
@callback
|
||||
def async_reload(hass):
|
||||
"""Reload the automation from config."""
|
||||
yield from hass.services.async_call(DOMAIN, SERVICE_RELOAD)
|
||||
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_RELOAD))
|
||||
|
||||
|
||||
def set_visibility(hass, entity_id=None, visible=True):
|
||||
|
@ -111,6 +136,46 @@ def set_visibility(hass, entity_id=None, visible=True):
|
|||
hass.services.call(DOMAIN, SERVICE_SET_VISIBILITY, data)
|
||||
|
||||
|
||||
def set_group(hass, object_id, name=None, entity_ids=None, visible=None,
|
||||
icon=None, view=None, control=None, add=None):
|
||||
"""Create a new user group."""
|
||||
hass.add_job(
|
||||
async_set_group, hass, object_id, name, entity_ids, visible, icon,
|
||||
view, control, add)
|
||||
|
||||
|
||||
@callback
|
||||
def async_set_group(hass, object_id, name=None, entity_ids=None, visible=None,
|
||||
icon=None, view=None, control=None, add=None):
|
||||
"""Create a new user group."""
|
||||
data = {
|
||||
key: value for key, value in [
|
||||
(ATTR_OBJECT_ID, object_id),
|
||||
(ATTR_NAME, name),
|
||||
(ATTR_ENTITIES, entity_ids),
|
||||
(ATTR_VISIBLE, visible),
|
||||
(ATTR_ICON, icon),
|
||||
(ATTR_VIEW, view),
|
||||
(ATTR_CONTROL, control),
|
||||
(ATTR_ADD_ENTITIES, add),
|
||||
] if value is not None
|
||||
}
|
||||
|
||||
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SET, data))
|
||||
|
||||
|
||||
def remove(hass, name):
|
||||
"""Remove a user group."""
|
||||
hass.add_job(async_remove, hass, name)
|
||||
|
||||
|
||||
@callback
|
||||
def async_remove(hass, object_id):
|
||||
"""Remove a user group."""
|
||||
data = {ATTR_OBJECT_ID: object_id}
|
||||
hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_REMOVE, data))
|
||||
|
||||
|
||||
def expand_entity_ids(hass, entity_ids):
|
||||
"""Return entity_ids with group entity ids replaced by their members.
|
||||
|
||||
|
@ -170,6 +235,7 @@ def get_entity_ids(hass, entity_id, domain_filter=None):
|
|||
def async_setup(hass, config):
|
||||
"""Set up all groups found definded in the configuration."""
|
||||
component = EntityComponent(_LOGGER, DOMAIN, hass)
|
||||
service_groups = {}
|
||||
|
||||
yield from _async_process_config(hass, config, component)
|
||||
|
||||
|
@ -179,29 +245,116 @@ def async_setup(hass, config):
|
|||
)
|
||||
|
||||
@asyncio.coroutine
|
||||
def reload_service_handler(service_call):
|
||||
def reload_service_handler(service):
|
||||
"""Remove all groups and load new ones from config."""
|
||||
conf = yield from component.async_prepare_reload()
|
||||
if conf is None:
|
||||
return
|
||||
yield from _async_process_config(hass, conf, component)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_RELOAD, reload_service_handler,
|
||||
descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA)
|
||||
|
||||
@asyncio.coroutine
|
||||
def groups_service_handler(service):
|
||||
"""Handle dynamic group service functions."""
|
||||
object_id = service.data[ATTR_OBJECT_ID]
|
||||
|
||||
# new group
|
||||
if service.service == SERVICE_SET and object_id not in service_groups:
|
||||
entity_ids = service.data.get(ATTR_ENTITIES) or \
|
||||
service.data.get(ATTR_ADD_ENTITIES) or None
|
||||
|
||||
extra_arg = {attr: service.data[attr] for attr in (
|
||||
ATTR_VISIBLE, ATTR_ICON, ATTR_VIEW, ATTR_CONTROL
|
||||
) if service.data.get(attr) is not None}
|
||||
|
||||
new_group = yield from Group.async_create_group(
|
||||
hass, service.data.get(ATTR_NAME, object_id),
|
||||
object_id=object_id,
|
||||
entity_ids=entity_ids,
|
||||
user_defined=False,
|
||||
**extra_arg
|
||||
)
|
||||
|
||||
service_groups[object_id] = new_group
|
||||
return
|
||||
|
||||
# update group
|
||||
if service.service == SERVICE_SET:
|
||||
group = service_groups[object_id]
|
||||
need_update = False
|
||||
|
||||
if ATTR_ADD_ENTITIES in service.data:
|
||||
delta = service.data[ATTR_ADD_ENTITIES]
|
||||
entity_ids = set(group.tracking) | set(delta)
|
||||
yield from group.async_update_tracked_entity_ids(entity_ids)
|
||||
|
||||
if ATTR_ENTITIES in service.data:
|
||||
entity_ids = service.data[ATTR_ENTITIES]
|
||||
yield from group.async_update_tracked_entity_ids(entity_ids)
|
||||
|
||||
if ATTR_NAME in service.data:
|
||||
group.name = service.data[ATTR_NAME]
|
||||
need_update = True
|
||||
|
||||
if ATTR_VISIBLE in service.data:
|
||||
group.visible = service.data[ATTR_VISIBLE]
|
||||
need_update = True
|
||||
|
||||
if ATTR_ICON in service.data:
|
||||
group.icon = service.data[ATTR_ICON]
|
||||
need_update = True
|
||||
|
||||
if ATTR_CONTROL in service.data:
|
||||
group.control = service.data[ATTR_CONTROL]
|
||||
need_update = True
|
||||
|
||||
if ATTR_VIEW in service.data:
|
||||
group.view = service.data[ATTR_VIEW]
|
||||
need_update = True
|
||||
|
||||
if need_update:
|
||||
yield from group.async_update_ha_state()
|
||||
|
||||
return
|
||||
|
||||
# remove group
|
||||
if service.service == SERVICE_REMOVE:
|
||||
if object_id not in service_groups:
|
||||
_LOGGER.warning("Group '%s' not exists!", object_id)
|
||||
return
|
||||
|
||||
del_group = service_groups.pop(object_id)
|
||||
yield from del_group.async_stop()
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SET, groups_service_handler,
|
||||
descriptions[DOMAIN][SERVICE_SET], schema=SET_SERVICE_SCHEMA)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_REMOVE, groups_service_handler,
|
||||
descriptions[DOMAIN][SERVICE_REMOVE], schema=REMOVE_SERVICE_SCHEMA)
|
||||
|
||||
@asyncio.coroutine
|
||||
def visibility_service_handler(service):
|
||||
"""Change visibility of a group."""
|
||||
visible = service.data.get(ATTR_VISIBLE)
|
||||
tasks = [group.async_set_visible(visible) for group
|
||||
in component.async_extract_from_service(service,
|
||||
expand_group=False)]
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
tasks = []
|
||||
for group in component.async_extract_from_service(service,
|
||||
expand_group=False):
|
||||
group.visible = visible
|
||||
tasks.append(group.async_update_ha_state())
|
||||
|
||||
if tasks:
|
||||
yield from asyncio.wait(tasks, loop=hass.loop)
|
||||
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_SET_VISIBILITY, visibility_service_handler,
|
||||
descriptions[DOMAIN][SERVICE_SET_VISIBILITY],
|
||||
schema=SET_VISIBILITY_SERVICE_SCHEMA)
|
||||
hass.services.async_register(
|
||||
DOMAIN, SERVICE_RELOAD, reload_service_handler,
|
||||
descriptions[DOMAIN][SERVICE_RELOAD], schema=RELOAD_SERVICE_SCHEMA)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -231,8 +384,8 @@ def _async_process_config(hass, config, component):
|
|||
class Group(Entity):
|
||||
"""Track a group of entity ids."""
|
||||
|
||||
def __init__(self, hass, name, order=None, user_defined=True, icon=None,
|
||||
view=False, control=None):
|
||||
def __init__(self, hass, name, order=None, visible=True, icon=None,
|
||||
view=False, control=None, user_defined=True):
|
||||
"""Initialize a group.
|
||||
|
||||
This Object has factory function for creation.
|
||||
|
@ -240,31 +393,33 @@ class Group(Entity):
|
|||
self.hass = hass
|
||||
self._name = name
|
||||
self._state = STATE_UNKNOWN
|
||||
self._user_defined = user_defined
|
||||
self._order = order
|
||||
self._icon = icon
|
||||
self._view = view
|
||||
self.view = view
|
||||
self.tracking = []
|
||||
self.group_on = None
|
||||
self.group_off = None
|
||||
self.visible = visible
|
||||
self.control = control
|
||||
self._user_defined = user_defined
|
||||
self._order = order
|
||||
self._assumed_state = False
|
||||
self._async_unsub_state_changed = None
|
||||
self._visible = True
|
||||
self._control = control
|
||||
|
||||
@staticmethod
|
||||
def create_group(hass, name, entity_ids=None, user_defined=True,
|
||||
icon=None, view=False, control=None, object_id=None):
|
||||
visible=True, icon=None, view=False, control=None,
|
||||
object_id=None):
|
||||
"""Initialize a group."""
|
||||
return run_coroutine_threadsafe(
|
||||
Group.async_create_group(hass, name, entity_ids, user_defined,
|
||||
icon, view, control, object_id),
|
||||
Group.async_create_group(
|
||||
hass, name, entity_ids, user_defined, visible, icon, view,
|
||||
control, object_id),
|
||||
hass.loop).result()
|
||||
|
||||
@staticmethod
|
||||
@asyncio.coroutine
|
||||
def async_create_group(hass, name, entity_ids=None, user_defined=True,
|
||||
icon=None, view=False, control=None,
|
||||
visible=True, icon=None, view=False, control=None,
|
||||
object_id=None):
|
||||
"""Initialize a group.
|
||||
|
||||
|
@ -273,8 +428,9 @@ class Group(Entity):
|
|||
group = Group(
|
||||
hass, name,
|
||||
order=len(hass.states.async_entity_ids(DOMAIN)),
|
||||
user_defined=user_defined, icon=icon, view=view,
|
||||
control=control)
|
||||
visible=visible, icon=icon, view=view, control=control,
|
||||
user_defined=user_defined
|
||||
)
|
||||
|
||||
group.entity_id = async_generate_entity_id(
|
||||
ENTITY_ID_FORMAT, object_id or name, hass=hass)
|
||||
|
@ -297,6 +453,11 @@ class Group(Entity):
|
|||
"""Return the name of the group."""
|
||||
return self._name
|
||||
|
||||
@name.setter
|
||||
def name(self, value):
|
||||
"""Set Group name."""
|
||||
self._name = value
|
||||
|
||||
@property
|
||||
def state(self):
|
||||
"""Return the state of the group."""
|
||||
|
@ -307,19 +468,16 @@ class Group(Entity):
|
|||
"""Return the icon of the group."""
|
||||
return self._icon
|
||||
|
||||
@asyncio.coroutine
|
||||
def async_set_visible(self, visible):
|
||||
"""Change visibility of the group."""
|
||||
if self._visible != visible:
|
||||
self._visible = visible
|
||||
yield from self.async_update_ha_state()
|
||||
@icon.setter
|
||||
def icon(self, value):
|
||||
"""Set Icon for group."""
|
||||
self._icon = value
|
||||
|
||||
@property
|
||||
def hidden(self):
|
||||
"""If group should be hidden or not."""
|
||||
# Visibility from set_visibility service overrides
|
||||
if self._visible:
|
||||
return not self._user_defined or self._view
|
||||
if self.visible and not self.view:
|
||||
return False
|
||||
return True
|
||||
|
||||
@property
|
||||
|
@ -331,10 +489,10 @@ class Group(Entity):
|
|||
}
|
||||
if not self._user_defined:
|
||||
data[ATTR_AUTO] = True
|
||||
if self._view:
|
||||
if self.view:
|
||||
data[ATTR_VIEW] = True
|
||||
if self._control:
|
||||
data[ATTR_CONTROL] = self._control
|
||||
if self.control:
|
||||
data[ATTR_CONTROL] = self.control
|
||||
return data
|
||||
|
||||
@property
|
||||
|
|
|
@ -26,6 +26,7 @@ from homeassistant.helpers.restore_state import async_restore_state
|
|||
import homeassistant.util.color as color_util
|
||||
|
||||
DOMAIN = "light"
|
||||
DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
GROUP_NAME_ALL_LIGHTS = 'all lights'
|
||||
|
|
|
@ -25,6 +25,8 @@ from homeassistant.components import group
|
|||
ATTR_CHANGED_BY = 'changed_by'
|
||||
|
||||
DOMAIN = 'lock'
|
||||
DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
ENTITY_ID_ALL_LOCKS = group.ENTITY_ID_FORMAT.format('all_locks')
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
@ -33,8 +35,6 @@ GROUP_NAME_ALL_LOCKS = 'all locks'
|
|||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
LOCK_SERVICE_SCHEMA = vol.Schema({
|
||||
vol.Optional(ATTR_ENTITY_ID): cv.entity_ids,
|
||||
vol.Optional(ATTR_CODE): cv.string,
|
||||
|
|
|
@ -30,6 +30,8 @@ ATTR_NUM_REPEATS = 'num_repeats'
|
|||
ATTR_DELAY_SECS = 'delay_secs'
|
||||
|
||||
DOMAIN = 'remote'
|
||||
DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
ENTITY_ID_ALL_REMOTES = group.ENTITY_ID_FORMAT.format('all_remotes')
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
@ -38,7 +40,6 @@ GROUP_NAME_ALL_REMOTES = 'all remotes'
|
|||
|
||||
MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10)
|
||||
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
SERVICE_SEND_COMMAND = 'send_command'
|
||||
SERVICE_SYNC = 'sync'
|
||||
|
||||
|
|
|
@ -24,6 +24,9 @@ from homeassistant.helpers.script import Script
|
|||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
DOMAIN = 'script'
|
||||
DEPENDENCIES = ['group']
|
||||
|
||||
ATTR_CAN_CANCEL = 'can_cancel'
|
||||
ATTR_LAST_ACTION = 'last_action'
|
||||
ATTR_LAST_TRIGGERED = 'last_triggered'
|
||||
|
@ -31,8 +34,6 @@ ATTR_VARIABLES = 'variables'
|
|||
|
||||
CONF_SEQUENCE = 'sequence'
|
||||
|
||||
DOMAIN = 'script'
|
||||
|
||||
ENTITY_ID_FORMAT = DOMAIN + '.{}'
|
||||
|
||||
GROUP_NAME_ALL_SCRIPTS = 'all scripts'
|
||||
|
|
|
@ -42,7 +42,6 @@ foursquare:
|
|||
group:
|
||||
reload:
|
||||
description: "Reload group configuration."
|
||||
fields:
|
||||
|
||||
set_visibility:
|
||||
description: Hide or show a group
|
||||
|
@ -56,6 +55,50 @@ group:
|
|||
description: True if group should be shown or False if it should be hidden.
|
||||
example: True
|
||||
|
||||
set:
|
||||
description: Create/Update a user group
|
||||
|
||||
fields:
|
||||
object_id:
|
||||
description: Group id and part of entity id
|
||||
example: 'test_group'
|
||||
|
||||
name:
|
||||
description: Name of group
|
||||
example: 'My test group'
|
||||
|
||||
view:
|
||||
description: Boolean for if the group is a view
|
||||
example: True
|
||||
|
||||
icon:
|
||||
description: Name of icon for the group
|
||||
example: 'mdi:camera'
|
||||
|
||||
control:
|
||||
description: Value for control the group control
|
||||
example: 'hidden'
|
||||
|
||||
visible:
|
||||
description: If the group is visible on UI
|
||||
example: True
|
||||
|
||||
entities:
|
||||
description: List of all members in the group. Not compatible with 'delta'
|
||||
example: domain.entity_id1, domain.entity_id2
|
||||
|
||||
add_entities:
|
||||
description: List of members they will change on group listening.
|
||||
example: domain.entity_id1, domain.entity_id2
|
||||
|
||||
remove:
|
||||
description: Remove a user group
|
||||
|
||||
fields:
|
||||
object_id:
|
||||
description: Group id and part of entity id
|
||||
example: 'test_group'
|
||||
|
||||
persistent_notification:
|
||||
create:
|
||||
description: Show a notification in the frontend
|
||||
|
|
|
@ -23,6 +23,7 @@ from homeassistant.const import (
|
|||
from homeassistant.components import group
|
||||
|
||||
DOMAIN = 'switch'
|
||||
DEPENDENCIES = ['group']
|
||||
SCAN_INTERVAL = timedelta(seconds=30)
|
||||
|
||||
GROUP_NAME_ALL_SWITCHES = 'all switches'
|
||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.helpers import config_per_platform, discovery
|
|||
from homeassistant.helpers.entity import async_generate_entity_id
|
||||
from homeassistant.helpers.event import async_track_time_interval
|
||||
from homeassistant.helpers.service import extract_entity_ids
|
||||
from homeassistant.util import slugify
|
||||
from homeassistant.util.async import (
|
||||
run_callback_threadsafe, run_coroutine_threadsafe)
|
||||
|
||||
|
@ -37,8 +38,6 @@ class EntityComponent(object):
|
|||
self.group_name = group_name
|
||||
|
||||
self.entities = {}
|
||||
self.group = None
|
||||
|
||||
self.config = None
|
||||
|
||||
self._platforms = {
|
||||
|
@ -232,21 +231,19 @@ class EntityComponent(object):
|
|||
run_callback_threadsafe(
|
||||
self.hass.loop, self.async_update_group).result()
|
||||
|
||||
@asyncio.coroutine
|
||||
@callback
|
||||
def async_update_group(self):
|
||||
"""Set up and/or update component group.
|
||||
|
||||
This method must be run in the event loop.
|
||||
"""
|
||||
if self.group is None and self.group_name is not None:
|
||||
if self.group_name is not None:
|
||||
ids = sorted(self.entities, key=lambda x: self.entities[x].name)
|
||||
group = get_component('group')
|
||||
self.group = yield from group.Group.async_create_group(
|
||||
self.hass, self.group_name,
|
||||
sorted(self.entities, key=lambda x: self.entities[x].name),
|
||||
user_defined=False)
|
||||
elif self.group is not None:
|
||||
yield from self.group.async_update_tracked_entity_ids(
|
||||
sorted(self.entities, key=lambda x: self.entities[x].name))
|
||||
group.async_set_group(
|
||||
self.hass, slugify(self.group_name), name=self.group_name,
|
||||
visible=False, entity_ids=ids
|
||||
)
|
||||
|
||||
def reset(self):
|
||||
"""Remove entities and reset the entity component to initial values."""
|
||||
|
@ -270,9 +267,9 @@ class EntityComponent(object):
|
|||
self.entities = {}
|
||||
self.config = None
|
||||
|
||||
if self.group is not None:
|
||||
yield from self.group.async_stop()
|
||||
self.group = None
|
||||
if self.group_name is not None:
|
||||
group = get_component('group')
|
||||
group.async_remove(self.hass, slugify(self.group_name))
|
||||
|
||||
def prepare_reload(self):
|
||||
"""Prepare reloading this entity component."""
|
||||
|
@ -375,7 +372,7 @@ class EntityPlatform(object):
|
|||
tasks = [async_process_entity(entity) for entity in new_entities]
|
||||
|
||||
yield from asyncio.wait(tasks, loop=self.component.hass.loop)
|
||||
yield from self.component.async_update_group()
|
||||
self.component.async_update_group()
|
||||
|
||||
if self._async_unsub_polling is not None or \
|
||||
not any(entity.should_poll for entity
|
||||
|
|
|
@ -32,7 +32,7 @@ class TestAutomation(unittest.TestCase):
|
|||
|
||||
def test_service_data_not_a_dict(self):
|
||||
"""Test service data not dict."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, automation.DOMAIN):
|
||||
assert setup_component(self.hass, automation.DOMAIN, {
|
||||
automation.DOMAIN: {
|
||||
'trigger': {
|
||||
|
@ -390,23 +390,7 @@ class TestAutomation(unittest.TestCase):
|
|||
self.hass.block_till_done()
|
||||
assert automation.is_on(self.hass, entity_id)
|
||||
|
||||
@patch('homeassistant.config.load_yaml_config_file', autospec=True,
|
||||
return_value={
|
||||
automation.DOMAIN: {
|
||||
'alias': 'bye',
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event2',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'event': '{{ trigger.event.event_type }}'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
def test_reload_config_service(self, mock_load_yaml):
|
||||
def test_reload_config_service(self):
|
||||
"""Test the reload config service."""
|
||||
assert setup_component(self.hass, automation.DOMAIN, {
|
||||
automation.DOMAIN: {
|
||||
|
@ -435,10 +419,25 @@ class TestAutomation(unittest.TestCase):
|
|||
assert len(self.calls) == 1
|
||||
assert self.calls[0].data.get('event') == 'test_event'
|
||||
|
||||
automation.reload(self.hass)
|
||||
self.hass.block_till_done()
|
||||
# De-flake ?!
|
||||
self.hass.block_till_done()
|
||||
with patch('homeassistant.config.load_yaml_config_file', autospec=True,
|
||||
return_value={
|
||||
automation.DOMAIN: {
|
||||
'alias': 'bye',
|
||||
'trigger': {
|
||||
'platform': 'event',
|
||||
'event_type': 'test_event2',
|
||||
},
|
||||
'action': {
|
||||
'service': 'test.automation',
|
||||
'data_template': {
|
||||
'event': '{{ trigger.event.event_type }}'
|
||||
}
|
||||
}
|
||||
}}):
|
||||
automation.reload(self.hass)
|
||||
self.hass.block_till_done()
|
||||
# De-flake ?!
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert self.hass.states.get('automation.hello') is None
|
||||
assert self.hass.states.get('automation.bye') is not None
|
||||
|
@ -455,11 +454,9 @@ class TestAutomation(unittest.TestCase):
|
|||
assert len(self.calls) == 2
|
||||
assert self.calls[1].data.get('event') == 'test_event2'
|
||||
|
||||
@patch('homeassistant.config.load_yaml_config_file', autospec=True,
|
||||
return_value={automation.DOMAIN: 'not valid'})
|
||||
def test_reload_config_when_invalid_config(self, mock_load_yaml):
|
||||
def test_reload_config_when_invalid_config(self):
|
||||
"""Test the reload config service handling invalid config."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, automation.DOMAIN):
|
||||
assert setup_component(self.hass, automation.DOMAIN, {
|
||||
automation.DOMAIN: {
|
||||
'alias': 'hello',
|
||||
|
@ -483,8 +480,10 @@ class TestAutomation(unittest.TestCase):
|
|||
assert len(self.calls) == 1
|
||||
assert self.calls[0].data.get('event') == 'test_event'
|
||||
|
||||
automation.reload(self.hass)
|
||||
self.hass.block_till_done()
|
||||
with patch('homeassistant.config.load_yaml_config_file', autospec=True,
|
||||
return_value={automation.DOMAIN: 'not valid'}):
|
||||
automation.reload(self.hass)
|
||||
self.hass.block_till_done()
|
||||
|
||||
assert self.hass.states.get('automation.hello') is None
|
||||
|
||||
|
|
|
@ -57,7 +57,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase):
|
|||
def test_password_or_pub_key_required(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test creating an AsusWRT scanner without a pass or pubkey."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'asuswrt',
|
||||
|
@ -82,7 +82,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase):
|
|||
}
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(self.hass, DOMAIN, conf_dict)
|
||||
|
||||
conf_dict[DOMAIN][CONF_MODE] = 'router'
|
||||
|
@ -108,7 +108,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase):
|
|||
}
|
||||
}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(self.hass, DOMAIN, conf_dict)
|
||||
|
||||
conf_dict[DOMAIN][CONF_MODE] = 'router'
|
||||
|
@ -192,7 +192,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase):
|
|||
update_mock.start()
|
||||
self.addCleanup(update_mock.stop)
|
||||
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, DOMAIN):
|
||||
assert setup_component(self.hass, DOMAIN,
|
||||
{DOMAIN: conf_dict})
|
||||
ssh.login.assert_not_called()
|
||||
|
@ -264,7 +264,7 @@ class TestComponentsDeviceTrackerASUSWRT(unittest.TestCase):
|
|||
update_mock.start()
|
||||
self.addCleanup(update_mock.stop)
|
||||
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, DOMAIN):
|
||||
assert setup_component(self.hass, DOMAIN,
|
||||
{DOMAIN: conf_dict})
|
||||
telnet.login.assert_not_called()
|
||||
|
|
|
@ -57,7 +57,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
mock_request.register_uri(
|
||||
'GET', r'http://%s/Status_Wireless.live.asp' % TEST_HOST,
|
||||
status_code=401)
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -77,7 +77,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
mock_request.register_uri(
|
||||
'GET', r'http://%s/Status_Wireless.live.asp' % TEST_HOST,
|
||||
status_code=444)
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -95,7 +95,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
'ddwrt.DdWrtDeviceScanner.get_ddwrt_data', return_value=None)
|
||||
def test_no_response(self, data_mock, error_mock):
|
||||
"""Create a Ddwrt scanner with no response in init, should fail."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -112,7 +112,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
@mock.patch('homeassistant.components.device_tracker.ddwrt._LOGGER.error')
|
||||
def test_get_timeout(self, mock_error, mock_request):
|
||||
"""Test get Ddwrt data with request time out."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -140,7 +140,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
'GET', r'http://%s/Status_Lan.live.asp' % TEST_HOST,
|
||||
text=load_fixture('Ddwrt_Status_Lan.txt'))
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -169,7 +169,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
mock_request.register_uri(
|
||||
'GET', r'http://%s/Status_Lan.live.asp' % TEST_HOST, text=None)
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -198,7 +198,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
text=load_fixture('Ddwrt_Status_Lan.txt').
|
||||
replace('dhcp_leases', 'missing'))
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -229,7 +229,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
'GET', r'http://%s/Status_Lan.live.asp' % TEST_HOST,
|
||||
text=load_fixture('Ddwrt_Status_Lan.txt'))
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
@ -249,7 +249,7 @@ class TestDdwrt(unittest.TestCase):
|
|||
'GET', r'http://%s/Status_Lan.live.asp' % TEST_HOST,
|
||||
text=load_fixture('Ddwrt_Status_Lan.txt'))
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'ddwrt',
|
||||
|
|
|
@ -429,6 +429,7 @@ class TestComponentsDeviceTracker(unittest.TestCase):
|
|||
with assert_setup_component(1, device_tracker.DOMAIN):
|
||||
assert setup_component(self.hass, device_tracker.DOMAIN,
|
||||
TEST_PLATFORM)
|
||||
self.hass.block_till_done()
|
||||
|
||||
state = self.hass.states.get(device_tracker.ENTITY_ID_ALL_DEVICES)
|
||||
self.assertIsNotNone(state)
|
||||
|
|
|
@ -58,7 +58,7 @@ class TestUPCConnect(object):
|
|||
content=b'successful'
|
||||
)
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'upc_connect',
|
||||
|
@ -84,7 +84,7 @@ class TestUPCConnect(object):
|
|||
status=404
|
||||
)
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'upc_connect',
|
||||
|
@ -114,7 +114,7 @@ class TestUPCConnect(object):
|
|||
exc=asyncio.TimeoutError()
|
||||
)
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'upc_connect',
|
||||
|
@ -143,7 +143,7 @@ class TestUPCConnect(object):
|
|||
content=b'successful',
|
||||
)
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, DOMAIN):
|
||||
assert setup_component(
|
||||
self.hass, DOMAIN, {DOMAIN: {
|
||||
CONF_PLATFORM: 'upc_connect',
|
||||
|
|
|
@ -151,7 +151,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
|
||||
def test_fail_setup_if_no_command_topic(self):
|
||||
"""Test if command fails with command topic."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
|
@ -163,7 +163,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
def test_no_color_brightness_color_temp_white_xy_if_no_topics(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test if there is no color and brightness if no topic."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
|
@ -217,7 +217,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'payload_off': 0
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -301,7 +301,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
|
||||
def test_brightness_controlling_scale(self):
|
||||
"""Test the brightness controlling scale."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
|
@ -348,7 +348,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
|
||||
def test_white_value_controlling_scale(self):
|
||||
"""Test the white_value controlling scale."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt',
|
||||
|
@ -416,7 +416,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'xy_value_template': '{{ value_json.hello | join(",") }}',
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -467,7 +467,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'payload_off': 'off'
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -522,7 +522,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'state_topic': 'test_light_rgb/status',
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -546,7 +546,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'state_topic': 'test_light_rgb/status'
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -570,7 +570,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'state_topic': 'test_light_rgb/status'
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -594,7 +594,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'state_topic': 'test_light_rgb/status',
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
@ -618,7 +618,7 @@ class TestLightMQTT(unittest.TestCase):
|
|||
'state_topic': 'test_light_rgb/status',
|
||||
}}
|
||||
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, config)
|
||||
|
||||
state = self.hass.states.get('light.test')
|
||||
|
|
|
@ -104,7 +104,7 @@ class TestLightMQTTJSON(unittest.TestCase):
|
|||
def test_fail_setup_if_no_command_topic(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test if setup fails with no command topic."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_json',
|
||||
|
|
|
@ -51,7 +51,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
def test_setup_fails(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test that setup fails with missing required configuration items."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
@ -63,7 +63,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
def test_state_change_via_topic(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test state change via topic."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
@ -103,7 +103,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
def test_state_brightness_color_effect_temp_white_change_via_topic(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test state, bri, color, effect, color temp, white val change."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
@ -205,7 +205,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
def test_optimistic(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test optimistic mode."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
@ -278,7 +278,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
def test_flash(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test flash."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
@ -321,7 +321,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
|
||||
def test_transition(self):
|
||||
"""Test for transition time being sent when included."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
@ -364,7 +364,7 @@ class TestLightMQTTTemplate(unittest.TestCase):
|
|||
def test_invalid_values(self): \
|
||||
# pylint: disable=invalid-name
|
||||
"""Test that invalid values are ignored."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, light.DOMAIN):
|
||||
assert setup_component(self.hass, light.DOMAIN, {
|
||||
light.DOMAIN: {
|
||||
'platform': 'mqtt_template',
|
||||
|
|
|
@ -39,7 +39,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_template_state_text(self):
|
||||
""""Test the state text of a template."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -84,7 +84,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_template_state_boolean_on(self):
|
||||
"""Test the setting of the state with boolean on."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -119,7 +119,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_template_state_boolean_off(self):
|
||||
"""Test the setting of the state with off."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -154,7 +154,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_template_syntax_error(self):
|
||||
"""Test templating syntax error."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -188,7 +188,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_invalid_name_does_not_create(self):
|
||||
"""Test invalid name."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -222,7 +222,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_invalid_light_does_not_create(self):
|
||||
"""Test invalid light."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -239,7 +239,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_no_lights_does_not_create(self):
|
||||
"""Test if there are no lights no creation."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template'
|
||||
|
@ -253,7 +253,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_missing_template_does_create(self):
|
||||
"""Test missing template."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -286,7 +286,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_missing_on_does_not_create(self):
|
||||
"""Test missing on."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -316,7 +316,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_missing_off_does_not_create(self):
|
||||
"""Test missing off."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
@ -553,7 +553,7 @@ class TestTemplateLight:
|
|||
|
||||
def test_level_template(self):
|
||||
"""Test the template for the level."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'light'):
|
||||
assert setup.setup_component(self.hass, 'light', {
|
||||
'light': {
|
||||
'platform': 'template',
|
||||
|
|
|
@ -36,7 +36,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_template_state_text(self):
|
||||
""""Test the state text of a template."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -74,7 +74,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_template_state_boolean_on(self):
|
||||
"""Test the setting of the state with boolean on."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -103,7 +103,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_template_state_boolean_off(self):
|
||||
"""Test the setting of the state with off."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -132,7 +132,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_icon_template(self):
|
||||
"""Test icon template."""
|
||||
with assert_setup_component(1):
|
||||
with assert_setup_component(1, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -171,7 +171,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_template_syntax_error(self):
|
||||
"""Test templating syntax error."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -199,7 +199,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_invalid_name_does_not_create(self):
|
||||
"""Test invalid name."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -227,7 +227,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_invalid_switch_does_not_create(self):
|
||||
"""Test invalid switch."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -244,7 +244,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_no_switches_does_not_create(self):
|
||||
"""Test if there are no switches no creation."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template'
|
||||
|
@ -258,7 +258,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_missing_template_does_not_create(self):
|
||||
"""Test missing template."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -286,7 +286,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_missing_on_does_not_create(self):
|
||||
"""Test missing on."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
@ -314,7 +314,7 @@ class TestTemplateSwitch:
|
|||
|
||||
def test_missing_off_does_not_create(self):
|
||||
"""Test missing off."""
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'template',
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
"""The tests for the Group components."""
|
||||
# pylint: disable=protected-access
|
||||
import asyncio
|
||||
from collections import OrderedDict
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from homeassistant.setup import setup_component
|
||||
from homeassistant.setup import setup_component, async_setup_component
|
||||
from homeassistant.const import (
|
||||
STATE_ON, STATE_OFF, STATE_HOME, STATE_UNKNOWN, ATTR_ICON, ATTR_HIDDEN,
|
||||
ATTR_ASSUMED_STATE, STATE_NOT_HOME, )
|
||||
ATTR_ASSUMED_STATE, STATE_NOT_HOME)
|
||||
import homeassistant.components.group as group
|
||||
|
||||
from tests.common import get_test_home_assistant
|
||||
from tests.common import get_test_home_assistant, assert_setup_component
|
||||
|
||||
|
||||
class TestComponentsGroup(unittest.TestCase):
|
||||
|
@ -380,3 +381,67 @@ class TestComponentsGroup(unittest.TestCase):
|
|||
self.hass.block_till_done()
|
||||
group_state = self.hass.states.get(group_entity_id)
|
||||
self.assertIsNone(group_state.attributes.get(ATTR_HIDDEN))
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_service_group_services(hass):
|
||||
"""Check if service are available."""
|
||||
with assert_setup_component(0, 'group'):
|
||||
yield from async_setup_component(hass, 'group', {
|
||||
'group': {}
|
||||
})
|
||||
|
||||
assert hass.services.has_service('group', group.SERVICE_SET)
|
||||
assert hass.services.has_service('group', group.SERVICE_REMOVE)
|
||||
|
||||
|
||||
@asyncio.coroutine
|
||||
def test_service_group_set_group_remove_group(hass):
|
||||
"""Check if service are available."""
|
||||
with assert_setup_component(0, 'group'):
|
||||
yield from async_setup_component(hass, 'group', {
|
||||
'group': {}
|
||||
})
|
||||
|
||||
group.async_set_group(hass, 'user_test_group', name="Test")
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
group_state = hass.states.get('group.user_test_group')
|
||||
assert group_state
|
||||
assert group_state.attributes[group.ATTR_AUTO]
|
||||
assert group_state.attributes['friendly_name'] == "Test"
|
||||
|
||||
group.async_set_group(
|
||||
hass, 'user_test_group', view=True, visible=False,
|
||||
entity_ids=['test.entity_bla1'])
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
group_state = hass.states.get('group.user_test_group')
|
||||
assert group_state
|
||||
assert group_state.attributes[group.ATTR_VIEW]
|
||||
assert group_state.attributes[group.ATTR_AUTO]
|
||||
assert group_state.attributes['hidden']
|
||||
assert group_state.attributes['friendly_name'] == "Test"
|
||||
assert list(group_state.attributes['entity_id']) == ['test.entity_bla1']
|
||||
|
||||
group.async_set_group(
|
||||
hass, 'user_test_group', icon="mdi:camera", name="Test2",
|
||||
control="hidden", add=['test.entity_id2'])
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
group_state = hass.states.get('group.user_test_group')
|
||||
assert group_state
|
||||
assert group_state.attributes[group.ATTR_VIEW]
|
||||
assert group_state.attributes[group.ATTR_AUTO]
|
||||
assert group_state.attributes['hidden']
|
||||
assert group_state.attributes['friendly_name'] == "Test2"
|
||||
assert group_state.attributes['icon'] == "mdi:camera"
|
||||
assert group_state.attributes[group.ATTR_CONTROL] == "hidden"
|
||||
assert sorted(list(group_state.attributes['entity_id'])) == sorted([
|
||||
'test.entity_bla1', 'test.entity_id2'])
|
||||
|
||||
group.async_remove(hass, 'user_test_group')
|
||||
yield from hass.async_block_till_done()
|
||||
|
||||
group_state = hass.states.get('group.user_test_group')
|
||||
assert group_state is None
|
||||
|
|
|
@ -7,7 +7,7 @@ from homeassistant.core import callback
|
|||
from homeassistant.setup import setup_component
|
||||
from homeassistant.components import script
|
||||
|
||||
from tests.common import get_test_home_assistant, mock_component
|
||||
from tests.common import get_test_home_assistant
|
||||
|
||||
|
||||
ENTITY_ID = 'script.test'
|
||||
|
@ -20,7 +20,6 @@ class TestScriptComponent(unittest.TestCase):
|
|||
def setUp(self):
|
||||
"""Setup things to be run when tests are started."""
|
||||
self.hass = get_test_home_assistant()
|
||||
mock_component(self.hass, 'group')
|
||||
|
||||
# pylint: disable=invalid-name
|
||||
def tearDown(self):
|
||||
|
|
|
@ -14,6 +14,7 @@ from homeassistant.helpers.entity import Entity, generate_entity_id
|
|||
from homeassistant.helpers.entity_component import (
|
||||
EntityComponent, DEFAULT_SCAN_INTERVAL, SLOW_SETUP_WARNING)
|
||||
from homeassistant.helpers import entity_component
|
||||
from homeassistant.setup import setup_component
|
||||
|
||||
from homeassistant.helpers import discovery
|
||||
import homeassistant.util.dt as dt_util
|
||||
|
@ -76,6 +77,7 @@ class TestHelpersEntityComponent(unittest.TestCase):
|
|||
|
||||
def test_setting_up_group(self):
|
||||
"""Setup the setting of a group."""
|
||||
setup_component(self.hass, 'group', {'group': {}})
|
||||
component = EntityComponent(_LOGGER, DOMAIN, self.hass,
|
||||
group_name='everyone')
|
||||
|
||||
|
|
|
@ -68,7 +68,7 @@ class TestCheckConfig(unittest.TestCase):
|
|||
res = check_config.check(get_test_config_dir('light.yaml'))
|
||||
change_yaml_files(res)
|
||||
self.assertDictEqual({
|
||||
'components': {'light': [{'platform': 'demo'}]},
|
||||
'components': {'light': [{'platform': 'demo'}], 'group': None},
|
||||
'except': {},
|
||||
'secret_cache': {},
|
||||
'secrets': {},
|
||||
|
@ -110,7 +110,8 @@ class TestCheckConfig(unittest.TestCase):
|
|||
'discovery_prefix': 'homeassistant',
|
||||
'tls_version': 'auto',
|
||||
},
|
||||
'light': []},
|
||||
'light': [],
|
||||
'group': None},
|
||||
res['components']
|
||||
)
|
||||
self.assertDictEqual(
|
||||
|
@ -142,7 +143,7 @@ class TestCheckConfig(unittest.TestCase):
|
|||
|
||||
res = check_config.check(get_test_config_dir('badplatform.yaml'))
|
||||
change_yaml_files(res)
|
||||
assert res['components'] == {'light': []}
|
||||
assert res['components'] == {'light': [], 'group': None}
|
||||
assert res['except'] == {
|
||||
check_config.ERROR_STR: [
|
||||
'Platform not found: light.beer',
|
||||
|
|
|
@ -296,7 +296,7 @@ class TestSetup:
|
|||
MockPlatform(platform_schema=platform_schema,
|
||||
setup_platform=mock_setup))
|
||||
|
||||
with assert_setup_component(0):
|
||||
with assert_setup_component(0, 'switch'):
|
||||
assert setup.setup_component(self.hass, 'switch', {
|
||||
'switch': {
|
||||
'platform': 'platform_a',
|
||||
|
|
Loading…
Reference in New Issue