Skip to content

Commit

Permalink
Add storage helper to ZHA and use it for the device node descriptor (h…
Browse files Browse the repository at this point in the history
…ome-assistant#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
Loading

0 comments on commit fc07d3a

Please sign in to comment.