mirror of
https://github.com/home-assistant/core.git
synced 2025-04-26 10:17:51 +00:00
Use node descriptor from Zigpy for ZHA (#24316)
* use zigpy node descriptor * cleanup
This commit is contained in:
parent
9fb1f2fa17
commit
ae1bcd5fef
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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,
|
||||||
|
@ -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'
|
||||||
|
@ -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)
|
||||||
|
@ -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:
|
||||||
|
@ -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()
|
||||||
]
|
]
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user