Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
7a360be
split entity into base and entity
dmulcahey Jan 25, 2020
13b7d4e
add initial light group support
dmulcahey Jan 25, 2020
276961b
add dispatching of groups to light
dmulcahey Jan 25, 2020
00ef011
added zha group object
dmulcahey Jan 26, 2020
34baa3d
add group event listener
dmulcahey Jan 26, 2020
cd1ed66
add and remove group members
dmulcahey Jan 26, 2020
c42bc5d
get group by name
dmulcahey Jan 26, 2020
515e12d
fix rebase
dmulcahey Feb 1, 2020
69f59f9
fix rebase
dmulcahey Feb 1, 2020
448d85c
use group_id for unique_id
dmulcahey Feb 1, 2020
fbf54b1
get entities from registry
dmulcahey Feb 1, 2020
f7e7574
use group name
dmulcahey Feb 1, 2020
fa5480c
update entity domain
dmulcahey Feb 3, 2020
5f21885
update zha storage to handle groups
dmulcahey Feb 3, 2020
015f19d
dispatch group entities
dmulcahey Mar 15, 2020
7fa86bc
update light group
dmulcahey Mar 15, 2020
908abb9
fix group remove and dispatch light group entities
dmulcahey Mar 15, 2020
692d756
allow picking the domain for group entities
dmulcahey Mar 15, 2020
f405087
beginning - auto determine entity domain
dmulcahey Mar 16, 2020
61d6c06
move methods to helpers so they can be shared
dmulcahey Mar 18, 2020
2f10e48
fix rebase
dmulcahey Mar 21, 2020
71ffb8c
remove double init groups... again
dmulcahey Mar 21, 2020
6defe8f
cleanup startup
dmulcahey Mar 22, 2020
980d5b6
use asyncio create task
dmulcahey Mar 22, 2020
694b0f2
group entity discovery
dmulcahey Mar 22, 2020
07547e4
add logging and fix group name
dmulcahey Mar 23, 2020
900b5be
add logging and update group after probe if needed
dmulcahey Mar 23, 2020
9a60544
test add group via gateway
dmulcahey Mar 23, 2020
4345c5b
add method to get group entity ids
dmulcahey Mar 23, 2020
0a8945d
update storage
dmulcahey Mar 23, 2020
94ee48e
test get group by name
dmulcahey Mar 23, 2020
b4036e7
update storage on remove
dmulcahey Mar 23, 2020
8f5a7fe
test group with single member
dmulcahey Mar 23, 2020
5fafdf1
add light group tests
dmulcahey Mar 23, 2020
cc3730d
test some light group logic
dmulcahey Mar 23, 2020
2317b64
type hints
dmulcahey Mar 23, 2020
78851d5
fix tests and cleanup
dmulcahey Mar 23, 2020
8901bf6
revert init changes except for create task
dmulcahey Mar 24, 2020
2883e6b
remove group entity domain changing for now
dmulcahey Mar 24, 2020
e63e864
add missing import
dmulcahey Mar 24, 2020
4d5f8c1
tricky code saving
dmulcahey Mar 24, 2020
cff3253
review comments
dmulcahey Mar 24, 2020
883f307
clean up class defs
dmulcahey Mar 25, 2020
8b304cd
cleanup
dmulcahey Mar 25, 2020
01d1c06
fix rebase because I cant read
dmulcahey Mar 25, 2020
88552a2
make pylint happy
dmulcahey Mar 25, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 3 additions & 5 deletions homeassistant/components/zha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ async def async_zha_shutdown(event):
await zha_data[DATA_ZHA_GATEWAY].async_update_device_storage()

hass.bus.async_listen_once(ha_const.EVENT_HOMEASSISTANT_STOP, async_zha_shutdown)
asyncio.create_task(async_load_entities(hass, config_entry))
asyncio.create_task(async_load_entities(hass))
return True


Expand All @@ -150,11 +150,9 @@ async def async_unload_entry(hass, config_entry):
return True


async def async_load_entities(
hass: HomeAssistantType, config_entry: config_entries.ConfigEntry
) -> None:
async def async_load_entities(hass: HomeAssistantType) -> None:
"""Load entities after integration was setup."""
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_prepare_entities()
await hass.data[DATA_ZHA][DATA_ZHA_GATEWAY].async_initialize_devices_and_entities()
to_setup = hass.data[DATA_ZHA][DATA_ZHA_PLATFORM_LOADED]
results = await asyncio.gather(*to_setup, return_exceptions=True)
for res in results:
Expand Down
2 changes: 2 additions & 0 deletions homeassistant/components/zha/core/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
ATTR_DEVICE_IEEE = "device_ieee"
ATTR_DEVICE_TYPE = "device_type"
ATTR_ENDPOINT_ID = "endpoint_id"
ATTR_ENTITY_DOMAIN = "entity_domain"
ATTR_IEEE = "ieee"
ATTR_LAST_SEEN = "last_seen"
ATTR_LEVEL = "level"
Expand Down Expand Up @@ -207,6 +208,7 @@ def list(cls):
SIGNAL_SET_LEVEL = "set_level"
SIGNAL_STATE_ATTR = "update_state_attribute"
SIGNAL_UPDATE_DEVICE = "{}_zha_update_device"
SIGNAL_REMOVE_GROUP = "remove_group"

UNKNOWN = "unknown"
UNKNOWN_MANUFACTURER = "unk_manufacturer"
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/zha/core/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ async def async_configure(self):
self.debug("started configuration")
await self._channels.async_configure()
self.debug("completed configuration")
entry = self.gateway.zha_storage.async_create_or_update(self)
entry = self.gateway.zha_storage.async_create_or_update_device(self)
self.debug("stored in registry: %s", entry)

if self._channels.identify_ch is not None:
Expand Down
100 changes: 100 additions & 0 deletions homeassistant/components/zha/core/discovery.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
"""Device discovery functions for Zigbee Home Automation."""

from collections import Counter
import logging
from typing import Callable, List, Tuple

from homeassistant import const as ha_const
from homeassistant.core import callback
from homeassistant.helpers.entity_registry import async_entries_for_device
from homeassistant.helpers.typing import HomeAssistantType

from . import const as zha_const, registries as zha_regs, typing as zha_typing
Expand Down Expand Up @@ -157,4 +159,102 @@ def initialize(self, hass: HomeAssistantType) -> None:
self._device_configs.update(overrides)


class GroupProbe:
"""Determine the appropriate component for a group."""

def __init__(self):
"""Initialize instance."""
self._hass = None

def initialize(self, hass: HomeAssistantType) -> None:
"""Initialize the group probe."""
self._hass = hass

@callback
def discover_group_entities(self, group: zha_typing.ZhaGroupType) -> None:
"""Process a group and create any entities that are needed."""
# only create a group entity if there are 2 or more members in a group
if len(group.members) < 2:
_LOGGER.debug(
"Group: %s:0x%04x has less than 2 members - skipping entity discovery",
group.name,
group.group_id,
)
return

if group.entity_domain is None:
_LOGGER.debug(
"Group: %s:0x%04x has no user set entity domain - attempting entity domain discovery",
group.name,
group.group_id,
)
group.entity_domain = GroupProbe.determine_default_entity_domain(
self._hass, group
)

if group.entity_domain is None:
return

_LOGGER.debug(
"Group: %s:0x%04x has an entity domain of: %s after discovery",
group.name,
group.group_id,
group.entity_domain,
)

zha_gateway = self._hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY]
entity_class = zha_regs.ZHA_ENTITIES.get_group_entity(group.entity_domain)
if entity_class is None:
return

self._hass.data[zha_const.DATA_ZHA][group.entity_domain].append(
(
entity_class,
(
group.domain_entity_ids,
f"{group.entity_domain}_group_{group.group_id}",
group.group_id,
zha_gateway.coordinator_zha_device,
),
)
)

@staticmethod
def determine_default_entity_domain(
hass: HomeAssistantType, group: zha_typing.ZhaGroupType
):
"""Determine the default entity domain for this group."""
if len(group.members) < 2:
_LOGGER.debug(
"Group: %s:0x%04x has less than 2 members so cannot default an entity domain",
group.name,
group.group_id,
)
return None

zha_gateway = hass.data[zha_const.DATA_ZHA][zha_const.DATA_ZHA_GATEWAY]
all_domain_occurrences = []
for device in group.members:
entities = async_entries_for_device(
zha_gateway.ha_entity_registry, device.device_id
)
all_domain_occurrences.extend(
[
entity.domain
for entity in entities
if entity.domain in zha_regs.GROUP_ENTITY_DOMAINS
]
)
counts = Counter(all_domain_occurrences)
domain = counts.most_common(1)[0][0]
_LOGGER.debug(
"The default entity domain is: %s for group: %s:0x%04x",
domain,
group.name,
group.group_id,
)
return domain


PROBE = ProbeEndpoint()
GROUP_PROBE = GroupProbe()
Loading