Use node descriptor from Zigpy for ZHA (#24316)

* use zigpy node descriptor

* cleanup
This commit is contained in:
David F. Mulcahey 2019-06-06 08:31:03 -04:00 committed by GitHub
parent 9fb1f2fa17
commit ae1bcd5fef
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 40 additions and 95 deletions

View File

@ -22,10 +22,6 @@ from ..const import (
)
from ..registries import CLUSTER_REPORT_CONFIGS
NODE_DESCRIPTOR_REQUEST = 0x0002
MAINS_POWERED = 1
BATTERY_OR_UNKNOWN = 0
ZIGBEE_CHANNEL_REGISTRY = {}
_LOGGER = logging.getLogger(__name__)
@ -268,11 +264,6 @@ class AttributeListeningChannel(ZigbeeChannel):
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_CHANNEL
@ -281,8 +272,6 @@ class ZDOChannel:
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):
@ -314,49 +303,10 @@ class ZDOChannel:
entry = self._zha_device.gateway.zha_storage.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 = self._zha_device.gateway.zha_storage.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

View File

@ -8,7 +8,7 @@ import logging
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.event import async_call_later
from . import ZigbeeChannel, parse_and_log_command, MAINS_POWERED
from . import ZigbeeChannel, parse_and_log_command
from ..helpers import get_attr_id_by_name
from ..const import (
SIGNAL_ATTR_UPDATED, SIGNAL_MOVE_LEVEL, SIGNAL_SET_LEVEL,
@ -87,7 +87,7 @@ class OnOffChannel(ZigbeeChannel):
async def async_update(self):
"""Initialize channel."""
from_cache = not self.device.power_source == MAINS_POWERED
from_cache = not self.device.is_mains_powered
_LOGGER.debug(
"%s is attempting to update onoff state - from cache: %s",
self._unique_id,

View File

@ -104,6 +104,8 @@ QUIRK_APPLIED = 'quirk_applied'
QUIRK_CLASS = 'quirk_class'
MANUFACTURER_CODE = 'manufacturer_code'
POWER_SOURCE = 'power_source'
MAINS_POWERED = 'Mains'
BATTERY_OR_UNKNOWN = 'Battery or Unknown'
BELLOWS = 'bellows'
ZHA = 'homeassistant.components.zha'

View File

@ -17,9 +17,10 @@ from .const import (
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, ZDO_CHANNEL, MANUFACTURER_CODE, POWER_SOURCE
QUIRK_CLASS, ZDO_CHANNEL, MANUFACTURER_CODE, POWER_SOURCE, MAINS_POWERED,
BATTERY_OR_UNKNOWN
)
from .channels import EventRelayChannel, ZDOChannel
from .channels import EventRelayChannel
_LOGGER = logging.getLogger(__name__)
@ -68,7 +69,6 @@ class ZHADevice:
self._zigpy_device.__class__.__module__,
self._zigpy_device.__class__.__name__
)
self._power_source = None
self.status = DeviceStatus.CREATED
@property
@ -91,6 +91,13 @@ class ZHADevice:
"""Return model for device."""
return self._model
@property
def manufacturer_code(self):
"""Return the manufacturer code for the device."""
if self._zigpy_device.node_desc.is_valid:
return self._zigpy_device.node_desc.manufacturer_code
return None
@property
def nwk(self):
"""Return nwk for device."""
@ -112,20 +119,29 @@ class ZHADevice:
return self._zigpy_device.last_seen
@property
def manufacturer_code(self):
"""Return manufacturer code for device."""
if ZDO_CHANNEL in self.cluster_channels:
return self.cluster_channels.get(ZDO_CHANNEL).manufacturer_code
return None
def is_mains_powered(self):
"""Return true if device is mains powered."""
return self._zigpy_device.node_desc.is_mains_powered
@property
def power_source(self):
"""Return the power source for the device."""
if self._power_source is not None:
return self._power_source
if ZDO_CHANNEL in self.cluster_channels:
return self.cluster_channels.get(ZDO_CHANNEL).power_source
return None
return MAINS_POWERED if self.is_mains_powered else BATTERY_OR_UNKNOWN
@property
def is_router(self):
"""Return true if this is a routing capable device."""
return self._zigpy_device.node_desc.is_router
@property
def is_coordinator(self):
"""Return true if this device represents the coordinator."""
return self._zigpy_device.node_desc.is_coordinator
@property
def is_end_device(self):
"""Return true if this device is an end device."""
return self._zigpy_device.node_desc.is_end_device
@property
def gateway(self):
@ -151,10 +167,6 @@ class ZHADevice:
"""Set availability from restore and prevent signals."""
self._available = available
def set_power_source(self, power_source):
"""Set the power source."""
self._power_source = power_source
def update_available(self, available):
"""Set sensor availability."""
if self._available != available and available:
@ -183,7 +195,7 @@ class ZHADevice:
QUIRK_APPLIED: self.quirk_applied,
QUIRK_CLASS: self.quirk_class,
MANUFACTURER_CODE: self.manufacturer_code,
POWER_SOURCE: ZDOChannel.POWER_SOURCES.get(self.power_source)
POWER_SOURCE: self.power_source
}
def add_cluster_channel(self, cluster_channel):
@ -256,7 +268,7 @@ class ZHADevice:
_LOGGER.debug(
'%s: power source: %s',
self.name,
ZDOChannel.POWER_SOURCES.get(self.power_source)
self.power_source
)
self.status = DeviceStatus.INITIALIZED
_LOGGER.debug('%s: completed initialization', self.name)

View File

@ -18,7 +18,6 @@ from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.entity_component import EntityComponent
from ..api import async_get_device_info
from .channels import MAINS_POWERED, ZDOChannel
from .const import (
ADD_DEVICE_RELAY_LOGGERS, ATTR_MANUFACTURER, BELLOWS, CONF_BAUDRATE,
CONF_DATABASE, CONF_RADIO_TYPE, CONF_USB_PATH, CONTROLLER, CURRENT,
@ -234,7 +233,6 @@ class ZHAGateway:
if not is_new_join:
entry = self.zha_storage.async_get_or_create(zha_device)
zha_device.async_update_last_seen(entry.last_seen)
zha_device.set_power_source(entry.power_source)
return zha_device
@callback
@ -290,16 +288,13 @@ class ZHAGateway:
# configure the device
await zha_device.async_configure()
zha_device.update_available(True)
elif zha_device.power_source is not None\
and zha_device.power_source == MAINS_POWERED:
elif zha_device.is_mains_powered:
# the device 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(
ZDOChannel.POWER_SOURCES.get(zha_device.power_source)
)
"with power source: {}".format(zha_device.power_source)
)
await zha_device.async_initialize(from_cache=False)
else:

View File

@ -26,8 +26,6 @@ class ZhaDeviceEntry:
name = attr.ib(type=str, default=None)
ieee = attr.ib(type=str, default=None)
power_source = attr.ib(type=int, default=None)
manufacturer_code = attr.ib(type=int, default=None)
last_seen = attr.ib(type=float, default=None)
@ -46,8 +44,6 @@ class ZhaDeviceStorage:
device_entry = ZhaDeviceEntry(
name=device.name,
ieee=str(device.ieee),
power_source=device.power_source,
manufacturer_code=device.manufacturer_code,
last_seen=device.last_seen
)
@ -85,13 +81,6 @@ class ZhaDeviceStorage:
old = self.devices[ieee_str]
changes = {}
if device.power_source != old.power_source:
changes['power_source'] = device.power_source
if device.manufacturer_code != old.manufacturer_code:
changes['manufacturer_code'] = device.manufacturer_code
changes['last_seen'] = device.last_seen
new = self.devices[ieee_str] = attr.evolve(old, **changes)
@ -109,8 +98,6 @@ class ZhaDeviceStorage:
devices[device['ieee']] = ZhaDeviceEntry(
name=device['name'],
ieee=device['ieee'],
power_source=device['power_source'],
manufacturer_code=device['manufacturer_code'],
last_seen=device['last_seen'] if 'last_seen' in device
else None
)
@ -135,8 +122,6 @@ class ZhaDeviceStorage:
{
'name': entry.name,
'ieee': entry.ieee,
'power_source': entry.power_source,
'manufacturer_code': entry.manufacturer_code,
'last_seen': entry.last_seen
} for entry in self.devices.values()
]

View File

@ -14,7 +14,6 @@ from .core.const import (
DOMAIN, ATTR_MANUFACTURER, DATA_ZHA, DATA_ZHA_BRIDGE_ID, MODEL, NAME,
SIGNAL_REMOVE
)
from .core.channels import MAINS_POWERED
_LOGGER = logging.getLogger(__name__)
@ -157,7 +156,7 @@ class ZhaEntity(RestoreEntity, entity.Entity):
time.time() - self._zha_device.last_seen <
RESTART_GRACE_PERIOD):
self.async_set_available(True)
if self.zha_device.power_source != MAINS_POWERED:
if not self.zha_device.is_mains_powered:
# mains powered devices will get real time state
self.async_restore_last_state(last_state)
self._zha_device.set_available(True)

View File

@ -82,6 +82,8 @@ class FakeDevice:
self.initializing = False
self.manufacturer = manufacturer
self.model = model
from zigpy.zdo.types import NodeDescriptor
self.node_desc = NodeDescriptor()
def make_device(in_cluster_ids, out_cluster_ids, device_type, ieee,