Skip to content
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
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
43 changes: 43 additions & 0 deletions homeassistant/components/zha/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ def __init__(self, hass, config):
self._config = config
self._component = EntityComponent(_LOGGER, DOMAIN, hass)
self._device_registry = collections.defaultdict(list)
import homeassistant.components.zha.const as zha_const
Comment thread
dmulcahey marked this conversation as resolved.
Outdated
self._events = []
hass.data[DISCOVERY_KEY] = hass.data.get(DISCOVERY_KEY, {})

def device_joined(self, device):
Expand Down Expand Up @@ -177,6 +179,7 @@ def device_removed(self, device):
async def async_device_initialized(self, device, join):
"""Handle device joined and basic information discovered (async)."""
import zigpy.profiles
import homeassistant.components.zha.event
Comment thread
dmulcahey marked this conversation as resolved.
Outdated
import homeassistant.components.zha.const as zha_const

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please move this too.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a design issue w/ the modules that causes circular imports. I have it corrected in another branch and can fix this in a subsequent PR if that is ok.

zha_const.populate_data()

Expand All @@ -197,6 +200,46 @@ async def async_device_initialized(self, device, join):
node_config = self._config[DOMAIN][CONF_DEVICE_CONFIG].get(
device_key, {})

_LOGGER.debug(
"Manufacturer: %s model: %s",
endpoint.manufacturer,
endpoint.model
)

supported_remote_models = zha_const.REMOTE_DEVICE_TYPES.get(
endpoint.profile_id, {}).get(endpoint.manufacturer, [])

_LOGGER.debug(
"Supported remote models: %s",
supported_remote_models
)

if endpoint.profile_id in zigpy.profiles.PROFILES and \
endpoint.model in supported_remote_models:
profile = zigpy.profiles.PROFILES[endpoint.profile_id]
profile_clusters = profile.CLUSTERS[endpoint.device_type]
in_clusters = [endpoint.in_clusters[c]
for c in profile_clusters[0]
if c in endpoint.in_clusters]
out_clusters = [endpoint.out_clusters[c]
for c in profile_clusters[1]
if c in endpoint.out_clusters]
discovery_info = {
'application_listener': self,
'endpoint': endpoint,
'in_clusters': in_clusters,
'out_clusters': out_clusters,
'manufacturer': endpoint.manufacturer,
'model': endpoint.model,
'new_join': join,
'unique_id': device_key,
}
created_events = await event.async_setup_event(
Comment thread
dmulcahey marked this conversation as resolved.
Outdated
self._hass,
discovery_info
)
self._events.extend(created_events)

if endpoint.profile_id in zigpy.profiles.PROFILES:
profile = zigpy.profiles.PROFILES[endpoint.profile_id]
if zha_const.DEVICE_CLASS.get(endpoint.profile_id,
Expand Down
17 changes: 16 additions & 1 deletion homeassistant/components/zha/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
SINGLE_OUTPUT_CLUSTER_DEVICE_CLASS = {}
CUSTOM_CLUSTER_MAPPINGS = {}
COMPONENT_CLUSTERS = {}
REMOTE_DEVICE_TYPES = {}


def populate_data():
Expand All @@ -30,6 +31,11 @@ def populate_data():
zha.DeviceType.DIMMER_SWITCH: 'binary_sensor',
zha.DeviceType.COLOR_DIMMER_SWITCH: 'binary_sensor',
}
REMOTE_DEVICE_TYPES[zha.PROFILE_ID] = {
'CentraLite': ['3130'],
'LUMI': ['lumi.sensor_switch.aq2'],
'OSRAM': ['LIGHTIFY Dimming Switch'],
}
DEVICE_CLASS[zll.PROFILE_ID] = {
zll.DeviceType.ON_OFF_LIGHT: 'light',
zll.DeviceType.ON_OFF_PLUGIN_UNIT: 'switch',
Expand All @@ -44,7 +50,8 @@ def populate_data():
zll.DeviceType.SCENE_CONTROLLER: 'binary_sensor',
zll.DeviceType.ON_OFF_SENSOR: 'binary_sensor',
}

REMOTE_DEVICE_TYPES[zll.PROFILE_ID] = [
]
SINGLE_INPUT_CLUSTER_DEVICE_CLASS.update({
zcl.clusters.general.OnOff: 'switch',
zcl.clusters.general.LevelControl: 'light',
Expand All @@ -67,6 +74,14 @@ def populate_data():
('sensor', sensor_zha.RelativeHumiditySensor)
})

# This registers a device that Xiaomi didn't follow the spec on.
# Translated: For device type: 0x5F01 in the ZHA zigbee profile
# the input clusters are: [0x0000, 0x0006, 0xFFFF] and the output
# clusters are: [0x0000, 0x0004, 0xFFFF]. The goal is to read this
# from a configuration file in the future
PROFILES[zha.PROFILE_ID].CLUSTERS[0x5F01] = ([0x0000, 0x0006, 0xFFFF],
[0x0000, 0x0004, 0xFFFF])

# A map of hass components to all Zigbee clusters it could use
for profile_id, classes in DEVICE_CLASS.items():
profile = PROFILES[profile_id]
Expand Down
109 changes: 109 additions & 0 deletions homeassistant/components/zha/event.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
"""
Support for Zigbee Home Automation devices that should fire events.

For more details about this component, please refer to the documentation at
https://home-assistant.io/components/zha/
"""
import logging
from homeassistant.util import slugify
from homeassistant.core import EventOrigin, callback


_LOGGER = logging.getLogger(__name__)


async def async_setup_event(hass, discovery_info):
"""Set up events for devices that have been registered in const.py.

Will create events for devices registered in REMOTE_DEVICE_TYPES.
"""
from homeassistant.components.zha import const as zha_const
Comment thread
dmulcahey marked this conversation as resolved.
Outdated
from homeassistant.components.zha import configure_reporting
out_clusters = discovery_info['out_clusters']
in_clusters = discovery_info['in_clusters']
events = []
for in_cluster in in_clusters:
event = ZHAEvent(hass, in_cluster, discovery_info)
if discovery_info['new_join']:
await configure_reporting(event.event_id, in_cluster, 0,
False, 0, 600, 1)
events.append(event)
for out_cluster in out_clusters:
event = ZHAEvent(hass, out_cluster, discovery_info)
if discovery_info['new_join']:
await configure_reporting(event.event_id, out_cluster, 0,
False, 0, 600, 1)
events.append(event)
return events


class ZHAEvent():
"""When you want signals instead of entities.
Stateless sensors such as remotes are expected to generate an event
instead of a sensor entity in hass.
"""

def __init__(self, hass, cluster, discovery_info):
"""Register callback that will be used for signals."""
self._hass = hass
self._cluster = cluster
self._cluster.add_listener(self)
ieee = discovery_info['endpoint'].device.ieee
ieeetail = ''.join(['%02x' % (o, ) for o in ieee[-4:]])
if discovery_info['manufacturer'] and discovery_info['model'] is not \
None:
self.event_id = "{}.{}_{}_{}{}".format(
slugify(discovery_info['manufacturer']),
slugify(discovery_info['model']),
ieeetail,
discovery_info['endpoint'].endpoint_id,
discovery_info.get('entity_suffix', '')
)
else:
self.event_id = "{}.event_{}{}".format(
ieeetail,
discovery_info['endpoint'].endpoint_id,
discovery_info.get('entity_suffix', '')
)

@callback
def cluster_command(self, tsn, command_id, args):
"""Handle commands received to this cluster."""
self._hass.bus.async_fire(
'zha_' + self._cluster.server_commands.get(command_id)[0],
{'device': self.event_id, 'args': args},
EventOrigin.remote
)
_LOGGER.debug(
Comment thread
dmulcahey marked this conversation as resolved.
Outdated
"%s: fired %s event with arguments: %s", self.event_id,
self._cluster.server_commands.get(command_id)[0],
args
)

@callback
def attribute_updated(self, attrid, value):
"""Handle attribute updates."""
self._hass.bus.async_fire(
'zha_attribute_updated',
{'device': self.event_id,
'attribute': self._cluster.attributes.get(attrid, ['Unknown'])[0],
'attribute_id': attrid,
'value': value},
EventOrigin.remote
)
_LOGGER.debug(
Comment thread
dmulcahey marked this conversation as resolved.
Outdated
"%s: updated attribute %s with value: %s and id: %s",
self.event_id,
self._cluster.attributes.get(attrid, ['Unknown'])[0],
value,
attrid
)

@callback
def zdo_command(self, *args, **kwargs):
Comment thread
MartinHjelmare marked this conversation as resolved.
"""log zdo commands for debugging."""
_LOGGER.debug(
"%s: issued zdo command %s with args: %s", self.event_id,
args,
kwargs
)