Make the rest of ZHA platforms to use ZHA class registry (#30261)

* Refactor ZHA component tests fixtures.

* Add tests for ZHA device discovery.

* Refactor ZHA registry MatchRule.

Allow callables as a matching criteria.
Allow sets for model & manufacturer.

* Minor ZHA class registry refactoring.

Less cluttered strict_matching registrations.

* Add entities only if there are any.

* Migrate rest of ZHA platforms to ZHA registry.

* Pylint fixes.
This commit is contained in:
Alexei Chetroi 2019-12-31 11:09:58 -05:00 committed by David F. Mulcahey
parent 5ed44297e6
commit a3061bda60
13 changed files with 2152 additions and 80 deletions

View file

@ -5,7 +5,7 @@ For more details about this component, please refer to the documentation at
https://home-assistant.io/integrations/zha/
"""
import collections
from typing import Callable, Set
from typing import Callable, Set, Union
import attr
import bellows.ezsp
@ -171,14 +171,33 @@ def establish_device_mappings():
REMOTE_DEVICE_TYPES[zll.PROFILE_ID].append(zll.DeviceType.SCENE_CONTROLLER)
def set_or_callable(value):
"""Convert single str or None to a set. Pass through callables and sets."""
if value is None:
return frozenset()
if callable(value):
return value
if isinstance(value, (frozenset, set, list)):
return frozenset(value)
return frozenset([str(value)])
@attr.s(frozen=True)
class MatchRule:
"""Match a ZHA Entity to a channel name or generic id."""
channel_names: Set[str] = attr.ib(factory=frozenset, converter=frozenset)
generic_ids: Set[str] = attr.ib(factory=frozenset, converter=frozenset)
manufacturer: str = attr.ib(default=None)
model: str = attr.ib(default=None)
channel_names: Union[Callable, Set[str], str] = attr.ib(
factory=frozenset, converter=set_or_callable
)
generic_ids: Union[Callable, Set[str], str] = attr.ib(
factory=frozenset, converter=set_or_callable
)
manufacturers: Union[Callable, Set[str], str] = attr.ib(
factory=frozenset, converter=set_or_callable
)
models: Union[Callable, Set[str], str] = attr.ib(
factory=frozenset, converter=set_or_callable
)
class ZHAEntityRegistry:
@ -190,7 +209,7 @@ class ZHAEntityRegistry:
self._loose_registry = collections.defaultdict(dict)
def get_entity(
self, component: str, zha_device, chnls: list, default: CALLABLE_T = None
self, component: str, zha_device, chnls: dict, default: CALLABLE_T = None
) -> CALLABLE_T:
"""Match a ZHA Channels to a ZHA Entity class."""
for match in self._strict_registry[component]:
@ -200,10 +219,17 @@ class ZHAEntityRegistry:
return default
def strict_match(
self, component: str, rule: MatchRule
self,
component: str,
channel_names: Union[Callable, Set[str], str] = None,
generic_ids: Union[Callable, Set[str], str] = None,
manufacturers: Union[Callable, Set[str], str] = None,
models: Union[Callable, Set[str], str] = None,
) -> Callable[[CALLABLE_T], CALLABLE_T]:
"""Decorate a strict match rule."""
rule = MatchRule(channel_names, generic_ids, manufacturers, models)
def decorator(zha_ent: CALLABLE_T) -> CALLABLE_T:
"""Register a strict match rule.
@ -215,10 +241,17 @@ class ZHAEntityRegistry:
return decorator
def loose_match(
self, component: str, rule: MatchRule
self,
component: str,
channel_names: Union[Callable, Set[str], str] = None,
generic_ids: Union[Callable, Set[str], str] = None,
manufacturers: Union[Callable, Set[str], str] = None,
models: Union[Callable, Set[str], str] = None,
) -> Callable[[CALLABLE_T], CALLABLE_T]:
"""Decorate a loose match rule."""
rule = MatchRule(channel_names, generic_ids, manufacturers, models)
def decorator(zha_entity: CALLABLE_T) -> CALLABLE_T:
"""Register a loose match rule.
@ -238,7 +271,7 @@ class ZHAEntityRegistry:
return any(self._matched(zha_device, chnls, rule))
@staticmethod
def _matched(zha_device, chnls: list, rule: MatchRule) -> bool:
def _matched(zha_device, chnls: dict, rule: MatchRule) -> list:
"""Return a list of field matches."""
if not any(attr.asdict(rule).values()):
return [False]
@ -252,11 +285,17 @@ class ZHAEntityRegistry:
all_generic_ids = {ch.generic_id for ch in chnls}
matches.append(rule.generic_ids.issubset(all_generic_ids))
if rule.manufacturer:
matches.append(zha_device.manufacturer == rule.manufacturer)
if rule.manufacturers:
if callable(rule.manufacturers):
matches.append(rule.manufacturers(zha_device.manufacturer))
else:
matches.append(zha_device.manufacturer in rule.manufacturers)
if rule.model:
matches.append(zha_device.model == rule.model)
if rule.models:
if callable(rule.models):
matches.append(rule.models(zha_device.model))
else:
matches.append(zha_device.model in rule.models)
return matches