Skip to content

Commit

Permalink
Add storage helper to ZHA and use it for the device node descriptor (#…
Browse files Browse the repository at this point in the history
…21500)

* node descriptor implementation

add info to device info

disable pylint rule

check for success

* review comments

* send manufacturer code for get attr value for mfg clusters

* ST report configs

* do zdo task first

* add guard

* use faster reporting config

* disable false positive pylint
  • Loading branch information
dmulcahey authored and balloob committed Mar 4, 2019
1 parent ee6f09d commit fc07d3a
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 26 deletions.
65 changes: 62 additions & 3 deletions homeassistant/components/zha/core/channels/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,13 @@
safe_read, get_attr_id_by_name)
from ..const import (
CLUSTER_REPORT_CONFIGS, REPORT_CONFIG_DEFAULT, SIGNAL_ATTR_UPDATED,
ATTRIBUTE_CHANNEL, EVENT_RELAY_CHANNEL
ATTRIBUTE_CHANNEL, EVENT_RELAY_CHANNEL, ZDO_CHANNEL
)
from ..store import async_get_registry

NODE_DESCRIPTOR_REQUEST = 0x0002
MAINS_POWERED = 1
BATTERY_OR_UNKNOWN = 0

ZIGBEE_CHANNEL_REGISTRY = {}
_LOGGER = logging.getLogger(__name__)
Expand Down Expand Up @@ -181,11 +186,16 @@ async def async_update(self):

async def get_attribute_value(self, attribute, from_cache=True):
"""Get the value for an attribute."""
manufacturer = None
manufacturer_code = self._zha_device.manufacturer_code
if self.cluster.cluster_id >= 0xfc00 and manufacturer_code:
manufacturer = manufacturer_code
result = await safe_read(
self._cluster,
[attribute],
allow_cache=from_cache,
only_cache=from_cache
only_cache=from_cache,
manufacturer=manufacturer
)
return result.get(attribute)

Expand Down Expand Up @@ -235,14 +245,21 @@ async def async_initialize(self, from_cache):
class ZDOChannel:
"""Channel for ZDO events."""

POWER_SOURCES = {
MAINS_POWERED: 'Mains',
BATTERY_OR_UNKNOWN: 'Battery or Unknown'
}

def __init__(self, cluster, device):
"""Initialize ZDOChannel."""
self.name = 'zdo'
self.name = ZDO_CHANNEL
self._cluster = cluster
self._zha_device = device
self._status = ChannelStatus.CREATED
self._unique_id = "{}_ZDO".format(device.name)
self._cluster.add_listener(self)
self.power_source = None
self.manufacturer_code = None

@property
def unique_id(self):
Expand Down Expand Up @@ -271,10 +288,52 @@ def permit_duration(self, duration):

async def async_initialize(self, from_cache):
"""Initialize channel."""
entry = (await async_get_registry(
self._zha_device.hass)).async_get_or_create(self._zha_device)
_LOGGER.debug("entry loaded from storage: %s", entry)
if entry is not None:
self.power_source = entry.power_source
self.manufacturer_code = entry.manufacturer_code

if self.power_source is None:
self.power_source = BATTERY_OR_UNKNOWN

if self.manufacturer_code is None and not from_cache:
# this should always be set. This is from us not doing
# this previously so lets set it up so users don't have
# to reconfigure every device.
await self.async_get_node_descriptor(False)
entry = (await async_get_registry(
self._zha_device.hass)).async_update(self._zha_device)
_LOGGER.debug("entry after getting node desc in init: %s", entry)
self._status = ChannelStatus.INITIALIZED

async def async_get_node_descriptor(self, from_cache):
"""Request the node descriptor from the device."""
from zigpy.zdo.types import Status

if from_cache:
return

node_descriptor = await self._cluster.request(
NODE_DESCRIPTOR_REQUEST,
self._cluster.device.nwk, tries=3, delay=2)

def get_bit(byteval, idx):
return int(((byteval & (1 << idx)) != 0))

if node_descriptor is not None and\
node_descriptor[0] == Status.SUCCESS:
mac_capability_flags = node_descriptor[2].mac_capability_flags

self.power_source = get_bit(mac_capability_flags, 2)
self.manufacturer_code = node_descriptor[2].manufacturer_code

_LOGGER.debug("node descriptor: %s", node_descriptor)

async def async_configure(self):
"""Configure channel."""
await self.async_get_node_descriptor(False)
self._status = ChannelStatus.CONFIGURED


Expand Down
3 changes: 3 additions & 0 deletions homeassistant/components/zha/core/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@

ATTR_LEVEL = 'level'

ZDO_CHANNEL = 'zdo'
ON_OFF_CHANNEL = 'on_off'
ATTRIBUTE_CHANNEL = 'attribute'
BASIC_CHANNEL = 'basic'
Expand All @@ -91,6 +92,8 @@

QUIRK_APPLIED = 'quirk_applied'
QUIRK_CLASS = 'quirk_class'
MANUFACTURER_CODE = 'manufacturer_code'
POWER_SOURCE = 'power_source'


class RadioType(enum.Enum):
Expand Down
54 changes: 36 additions & 18 deletions homeassistant/components/zha/core/device.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@
ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER,
ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS,
ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN, QUIRK_APPLIED,
QUIRK_CLASS, BASIC_CHANNEL
QUIRK_CLASS, ZDO_CHANNEL, MANUFACTURER_CODE, POWER_SOURCE
)
from .channels import EventRelayChannel
from .channels.general import BasicChannel
from .channels import EventRelayChannel, ZDOChannel
from .store import async_get_registry

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -69,7 +69,6 @@ def __init__(self, hass, zigpy_device, zha_gateway):
self._zigpy_device.__class__.__module__,
self._zigpy_device.__class__.__name__
)
self.power_source = None
self.status = DeviceStatus.CREATED

@property
Expand All @@ -84,12 +83,12 @@ def ieee(self):

@property
def manufacturer(self):
"""Return ieee address for device."""
"""Return manufacturer for device."""
return self._manufacturer

@property
def model(self):
"""Return ieee address for device."""
"""Return model for device."""
return self._model

@property
Expand All @@ -115,7 +114,15 @@ def last_seen(self):
@property
def manufacturer_code(self):
"""Return manufacturer code for device."""
# will eventually get this directly from Zigpy
if ZDO_CHANNEL in self.cluster_channels:
return self.cluster_channels.get(ZDO_CHANNEL).manufacturer_code
return None

@property
def power_source(self):
"""Return True if sensor is available."""
if ZDO_CHANNEL in self.cluster_channels:
return self.cluster_channels.get(ZDO_CHANNEL).power_source
return None

@property
Expand Down Expand Up @@ -164,7 +171,9 @@ def device_info(self):
MODEL: self.model,
NAME: self.name or ieee,
QUIRK_APPLIED: self.quirk_applied,
QUIRK_CLASS: self.quirk_class
QUIRK_CLASS: self.quirk_class,
MANUFACTURER_CODE: self.manufacturer_code,
POWER_SOURCE: ZDOChannel.POWER_SOURCES.get(self.power_source)
}

def add_cluster_channel(self, cluster_channel):
Expand All @@ -186,29 +195,38 @@ async def async_configure(self):
_LOGGER.debug('%s: started configuration', self.name)
await self._execute_channel_tasks('async_configure')
_LOGGER.debug('%s: completed configuration', self.name)
entry = (await async_get_registry(
self.hass)).async_create_or_update(self)
_LOGGER.debug('%s: stored in registry: %s', self.name, entry)

async def async_initialize(self, from_cache=False):
"""Initialize channels."""
_LOGGER.debug('%s: started initialization', self.name)
await self._execute_channel_tasks('async_initialize', from_cache)
if BASIC_CHANNEL in self.cluster_channels:
self.power_source = self.cluster_channels.get(
BASIC_CHANNEL).get_power_source()
_LOGGER.debug(
'%s: power source: %s',
self.name,
BasicChannel.POWER_SOURCES.get(self.power_source)
)
_LOGGER.debug(
'%s: power source: %s',
self.name,
ZDOChannel.POWER_SOURCES.get(self.power_source)
)
self.status = DeviceStatus.INITIALIZED
_LOGGER.debug('%s: completed initialization', self.name)

async def _execute_channel_tasks(self, task_name, *args):
"""Gather and execute a set of CHANNEL tasks."""
channel_tasks = []
semaphore = asyncio.Semaphore(3)
zdo_task = None
for channel in self.all_channels:
channel_tasks.append(
self._async_create_task(semaphore, channel, task_name, *args))
if channel.name == ZDO_CHANNEL:
# pylint: disable=E1111
zdo_task = self._async_create_task(
semaphore, channel, task_name, *args)
else:
channel_tasks.append(
self._async_create_task(
semaphore, channel, task_name, *args))
if zdo_task is not None:
await zdo_task
await asyncio.gather(*channel_tasks)

async def _async_create_task(self, semaphore, channel, func_name, *args):
Expand Down
31 changes: 26 additions & 5 deletions homeassistant/components/zha/core/gateway.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,8 @@
from .device import ZHADevice, DeviceStatus
from ..device_entity import ZhaDeviceEntity
from .channels import (
AttributeListeningChannel, EventRelayChannel, ZDOChannel
AttributeListeningChannel, EventRelayChannel, ZDOChannel, MAINS_POWERED
)
from .channels.general import BasicChannel
from .channels.registry import ZIGBEE_CHANNEL_REGISTRY
from .helpers import convert_ieee

Expand All @@ -38,6 +37,7 @@
SENSOR_TYPES = {}
BINARY_SENSOR_TYPES = {}
SMARTTHINGS_HUMIDITY_CLUSTER = 64581
SMARTTHINGS_ACCELERATION_CLUSTER = 64514
EntityReference = collections.namedtuple(
'EntityReference', 'reference_id zha_device cluster_channels device_info')

Expand Down Expand Up @@ -163,15 +163,14 @@ async def async_device_initialized(self, device, is_new_join):
# configure the device
await zha_device.async_configure()
elif not zha_device.available and zha_device.power_source is not None\
and zha_device.power_source != BasicChannel.BATTERY\
and zha_device.power_source != BasicChannel.UNKNOWN:
and zha_device.power_source == MAINS_POWERED:
# the device is currently marked unavailable and it isn't a battery
# powered device so we should be able to update it now
_LOGGER.debug(
"attempting to request fresh state for %s %s",
zha_device.name,
"with power source: {}".format(
BasicChannel.POWER_SOURCES.get(zha_device.power_source)
ZDOChannel.POWER_SOURCES.get(zha_device.power_source)
)
)
await zha_device.async_initialize(from_cache=False)
Expand Down Expand Up @@ -453,6 +452,7 @@ def establish_device_mappings():
NO_SENSOR_CLUSTERS.append(
zcl.clusters.general.PowerConfiguration.cluster_id)
NO_SENSOR_CLUSTERS.append(zcl.clusters.lightlink.LightLink.cluster_id)
NO_SENSOR_CLUSTERS.append(SMARTTHINGS_ACCELERATION_CLUSTER)

BINDABLE_CLUSTERS.append(zcl.clusters.general.LevelControl.cluster_id)
BINDABLE_CLUSTERS.append(zcl.clusters.general.OnOff.cluster_id)
Expand Down Expand Up @@ -575,6 +575,27 @@ def establish_device_mappings():
50
)
}],
SMARTTHINGS_ACCELERATION_CLUSTER: [{
'attr': 'acceleration',
'config': REPORT_CONFIG_ASAP
}, {
'attr': 'x_axis',
'config': REPORT_CONFIG_ASAP
}, {
'attr': 'y_axis',
'config': REPORT_CONFIG_ASAP
}, {
'attr': 'z_axis',
'config': REPORT_CONFIG_ASAP
}],
SMARTTHINGS_HUMIDITY_CLUSTER: [{
'attr': 'measured_value',
'config': (
REPORT_CONFIG_MIN_INT,
REPORT_CONFIG_MAX_INT,
50
)
}],
zcl.clusters.measurement.PressureMeasurement.cluster_id: [{
'attr': 'measured_value',
'config': REPORT_CONFIG_DEFAULT
Expand Down

0 comments on commit fc07d3a

Please sign in to comment.