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 from ..registries import CLUSTER_REPORT_CONFIGS
NODE_DESCRIPTOR_REQUEST = 0x0002
MAINS_POWERED = 1
BATTERY_OR_UNKNOWN = 0
ZIGBEE_CHANNEL_REGISTRY = {} ZIGBEE_CHANNEL_REGISTRY = {}
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -268,11 +264,6 @@ class AttributeListeningChannel(ZigbeeChannel):
class ZDOChannel: class ZDOChannel:
"""Channel for ZDO events.""" """Channel for ZDO events."""
POWER_SOURCES = {
MAINS_POWERED: 'Mains',
BATTERY_OR_UNKNOWN: 'Battery or Unknown'
}
def __init__(self, cluster, device): def __init__(self, cluster, device):
"""Initialize ZDOChannel.""" """Initialize ZDOChannel."""
self.name = ZDO_CHANNEL self.name = ZDO_CHANNEL
@ -281,8 +272,6 @@ class ZDOChannel:
self._status = ChannelStatus.CREATED self._status = ChannelStatus.CREATED
self._unique_id = "{}_ZDO".format(device.name) self._unique_id = "{}_ZDO".format(device.name)
self._cluster.add_listener(self) self._cluster.add_listener(self)
self.power_source = None
self.manufacturer_code = None
@property @property
def unique_id(self): def unique_id(self):
@ -314,49 +303,10 @@ class ZDOChannel:
entry = self._zha_device.gateway.zha_storage.async_get_or_create( entry = self._zha_device.gateway.zha_storage.async_get_or_create(
self._zha_device) self._zha_device)
_LOGGER.debug("entry loaded from storage: %s", entry) _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 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): async def async_configure(self):
"""Configure channel.""" """Configure channel."""
await self.async_get_node_descriptor(False)
self._status = ChannelStatus.CONFIGURED self._status = ChannelStatus.CONFIGURED

View File

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

View File

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

View File

@ -17,9 +17,10 @@ from .const import (
ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER, ATTR_CLUSTER_ID, ATTR_ATTRIBUTE, ATTR_VALUE, ATTR_COMMAND, SERVER,
ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS, ATTR_COMMAND_TYPE, ATTR_ARGS, CLIENT_COMMANDS, SERVER_COMMANDS,
ATTR_ENDPOINT_ID, IEEE, MODEL, NAME, UNKNOWN, QUIRK_APPLIED, 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__) _LOGGER = logging.getLogger(__name__)
@ -68,7 +69,6 @@ class ZHADevice:
self._zigpy_device.__class__.__module__, self._zigpy_device.__class__.__module__,
self._zigpy_device.__class__.__name__ self._zigpy_device.__class__.__name__
) )
self._power_source = None
self.status = DeviceStatus.CREATED self.status = DeviceStatus.CREATED
@property @property
@ -91,6 +91,13 @@ class ZHADevice:
"""Return model for device.""" """Return model for device."""
return self._model 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 @property
def nwk(self): def nwk(self):
"""Return nwk for device.""" """Return nwk for device."""
@ -112,20 +119,29 @@ class ZHADevice:
return self._zigpy_device.last_seen return self._zigpy_device.last_seen
@property @property
def manufacturer_code(self): def is_mains_powered(self):
"""Return manufacturer code for device.""" """Return true if device is mains powered."""
if ZDO_CHANNEL in self.cluster_channels: return self._zigpy_device.node_desc.is_mains_powered
return self.cluster_channels.get(ZDO_CHANNEL).manufacturer_code
return None
@property @property
def power_source(self): def power_source(self):
"""Return the power source for the device.""" """Return the power source for the device."""
if self._power_source is not None: return MAINS_POWERED if self.is_mains_powered else BATTERY_OR_UNKNOWN
return self._power_source
if ZDO_CHANNEL in self.cluster_channels: @property
return self.cluster_channels.get(ZDO_CHANNEL).power_source def is_router(self):
return None """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 @property
def gateway(self): def gateway(self):
@ -151,10 +167,6 @@ class ZHADevice:
"""Set availability from restore and prevent signals.""" """Set availability from restore and prevent signals."""
self._available = available self._available = available
def set_power_source(self, power_source):
"""Set the power source."""
self._power_source = power_source
def update_available(self, available): def update_available(self, available):
"""Set sensor availability.""" """Set sensor availability."""
if self._available != available and available: if self._available != available and available:
@ -183,7 +195,7 @@ class ZHADevice:
QUIRK_APPLIED: self.quirk_applied, QUIRK_APPLIED: self.quirk_applied,
QUIRK_CLASS: self.quirk_class, QUIRK_CLASS: self.quirk_class,
MANUFACTURER_CODE: self.manufacturer_code, 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): def add_cluster_channel(self, cluster_channel):
@ -256,7 +268,7 @@ class ZHADevice:
_LOGGER.debug( _LOGGER.debug(
'%s: power source: %s', '%s: power source: %s',
self.name, self.name,
ZDOChannel.POWER_SOURCES.get(self.power_source) self.power_source
) )
self.status = DeviceStatus.INITIALIZED self.status = DeviceStatus.INITIALIZED
_LOGGER.debug('%s: completed initialization', self.name) _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 homeassistant.helpers.entity_component import EntityComponent
from ..api import async_get_device_info from ..api import async_get_device_info
from .channels import MAINS_POWERED, ZDOChannel
from .const import ( from .const import (
ADD_DEVICE_RELAY_LOGGERS, ATTR_MANUFACTURER, BELLOWS, CONF_BAUDRATE, ADD_DEVICE_RELAY_LOGGERS, ATTR_MANUFACTURER, BELLOWS, CONF_BAUDRATE,
CONF_DATABASE, CONF_RADIO_TYPE, CONF_USB_PATH, CONTROLLER, CURRENT, CONF_DATABASE, CONF_RADIO_TYPE, CONF_USB_PATH, CONTROLLER, CURRENT,
@ -234,7 +233,6 @@ class ZHAGateway:
if not is_new_join: if not is_new_join:
entry = self.zha_storage.async_get_or_create(zha_device) entry = self.zha_storage.async_get_or_create(zha_device)
zha_device.async_update_last_seen(entry.last_seen) zha_device.async_update_last_seen(entry.last_seen)
zha_device.set_power_source(entry.power_source)
return zha_device return zha_device
@callback @callback
@ -290,16 +288,13 @@ class ZHAGateway:
# configure the device # configure the device
await zha_device.async_configure() await zha_device.async_configure()
zha_device.update_available(True) zha_device.update_available(True)
elif zha_device.power_source is not None\ elif zha_device.is_mains_powered:
and zha_device.power_source == MAINS_POWERED:
# the device isn't a battery powered device so we should be able # the device isn't a battery powered device so we should be able
# to update it now # to update it now
_LOGGER.debug( _LOGGER.debug(
"attempting to request fresh state for %s %s", "attempting to request fresh state for %s %s",
zha_device.name, zha_device.name,
"with power source: {}".format( "with power source: {}".format(zha_device.power_source)
ZDOChannel.POWER_SOURCES.get(zha_device.power_source)
)
) )
await zha_device.async_initialize(from_cache=False) await zha_device.async_initialize(from_cache=False)
else: else:

View File

@ -26,8 +26,6 @@ class ZhaDeviceEntry:
name = attr.ib(type=str, default=None) name = attr.ib(type=str, default=None)
ieee = 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) last_seen = attr.ib(type=float, default=None)
@ -46,8 +44,6 @@ class ZhaDeviceStorage:
device_entry = ZhaDeviceEntry( device_entry = ZhaDeviceEntry(
name=device.name, name=device.name,
ieee=str(device.ieee), ieee=str(device.ieee),
power_source=device.power_source,
manufacturer_code=device.manufacturer_code,
last_seen=device.last_seen last_seen=device.last_seen
) )
@ -85,13 +81,6 @@ class ZhaDeviceStorage:
old = self.devices[ieee_str] old = self.devices[ieee_str]
changes = {} 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 changes['last_seen'] = device.last_seen
new = self.devices[ieee_str] = attr.evolve(old, **changes) new = self.devices[ieee_str] = attr.evolve(old, **changes)
@ -109,8 +98,6 @@ class ZhaDeviceStorage:
devices[device['ieee']] = ZhaDeviceEntry( devices[device['ieee']] = ZhaDeviceEntry(
name=device['name'], name=device['name'],
ieee=device['ieee'], ieee=device['ieee'],
power_source=device['power_source'],
manufacturer_code=device['manufacturer_code'],
last_seen=device['last_seen'] if 'last_seen' in device last_seen=device['last_seen'] if 'last_seen' in device
else None else None
) )
@ -135,8 +122,6 @@ class ZhaDeviceStorage:
{ {
'name': entry.name, 'name': entry.name,
'ieee': entry.ieee, 'ieee': entry.ieee,
'power_source': entry.power_source,
'manufacturer_code': entry.manufacturer_code,
'last_seen': entry.last_seen 'last_seen': entry.last_seen
} for entry in self.devices.values() } 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, DOMAIN, ATTR_MANUFACTURER, DATA_ZHA, DATA_ZHA_BRIDGE_ID, MODEL, NAME,
SIGNAL_REMOVE SIGNAL_REMOVE
) )
from .core.channels import MAINS_POWERED
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@ -157,7 +156,7 @@ class ZhaEntity(RestoreEntity, entity.Entity):
time.time() - self._zha_device.last_seen < time.time() - self._zha_device.last_seen <
RESTART_GRACE_PERIOD): RESTART_GRACE_PERIOD):
self.async_set_available(True) 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 # mains powered devices will get real time state
self.async_restore_last_state(last_state) self.async_restore_last_state(last_state)
self._zha_device.set_available(True) self._zha_device.set_available(True)

View File

@ -82,6 +82,8 @@ class FakeDevice:
self.initializing = False self.initializing = False
self.manufacturer = manufacturer self.manufacturer = manufacturer
self.model = model 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, def make_device(in_cluster_ids, out_cluster_ids, device_type, ieee,