/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea-zigbee. * This project including source code and documentation is protected by copyright law, and * remains the property of nymea GmbH. All rights, including reproduction, publication, * editing and translation, are reserved. The use of this project is subject to the terms of a * license agreement to be concluded with nymea GmbH in accordance with the terms * of use of nymea GmbH, available under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the terms of the GNU * Lesser General Public License as published by the Free Software Foundation; version 3. * this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this project. * If not, see . * * For any further details and any questions please contact us under contact@nymea.io * or see our FAQ/Licensing Information on https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "zigbeenode.h" #include "zigbeeutils.h" #include "zigbeenetwork.h" #include "loggingcategory.h" #include ZigbeeNode::ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent) : QObject(parent), m_network(network), m_shortAddress(shortAddress), m_extendedAddress(extendedAddress) { m_deviceObject = new ZigbeeDeviceObject(m_network, this, this); } ZigbeeNode::State ZigbeeNode::state() const { return m_state; } bool ZigbeeNode::connected() const { return m_connected; } ZigbeeDeviceObject *ZigbeeNode::deviceObject() const { return m_deviceObject; } quint16 ZigbeeNode::shortAddress() const { return m_shortAddress; } ZigbeeAddress ZigbeeNode::extendedAddress() const { return m_extendedAddress; } QList ZigbeeNode::endpoints() const { return m_endpoints; } bool ZigbeeNode::hasEndpoint(quint8 endpointId) const { return getEndpoint(endpointId) != nullptr; } ZigbeeNodeEndpoint *ZigbeeNode::getEndpoint(quint8 endpointId) const { foreach (ZigbeeNodeEndpoint *ep, m_endpoints) { if (ep->endpointId()== endpointId) { return ep; } } return nullptr; } ZigbeeNode::NodeType ZigbeeNode::nodeType() const { return m_nodeType; } ZigbeeNode::FrequencyBand ZigbeeNode::frequencyBand() const { return m_frequencyBand; } ZigbeeNode::Relationship ZigbeeNode::relationship() const { return m_relationship; } quint16 ZigbeeNode::manufacturerCode() const { return m_manufacturerCode; } bool ZigbeeNode::complexDescriptorAvailable() const { return m_complexDescriptorAvailable; } bool ZigbeeNode::userDescriptorAvailable() const { return m_userDescriptorAvailable; } quint16 ZigbeeNode::maximumRxSize() const { return m_maximumRxSize; } quint16 ZigbeeNode::maximumTxSize() const { return m_maximumTxSize; } quint8 ZigbeeNode::maximumBufferSize() const { return m_maximumBufferSize; } bool ZigbeeNode::isPrimaryTrustCenter() const { return m_isPrimaryTrustCenter; } bool ZigbeeNode::isBackupTrustCenter() const { return m_isBackupTrustCenter; } bool ZigbeeNode::isPrimaryBindingCache() const { return m_isPrimaryBindingCache; } bool ZigbeeNode::isBackupBindingCache() const { return m_isBackupBindingCache; } bool ZigbeeNode::isPrimaryDiscoveryCache() const { return m_isPrimaryDiscoveryCache; } bool ZigbeeNode::isBackupDiscoveryCache() const { return m_isBackupDiscoveryCache; } bool ZigbeeNode::isNetworkManager() const { return m_isNetworkManager; } bool ZigbeeNode::extendedActiveEndpointListAvailable() const { return m_extendedActiveEndpointListAvailable; } bool ZigbeeNode::extendedSimpleDescriptorListAvailable() const { return m_extendedSimpleDescriptorListAvailable; } bool ZigbeeNode::alternatePanCoordinator() const { return m_alternatePanCoordinator; } ZigbeeNode::DeviceType ZigbeeNode::deviceType() const { return m_deviceType; } bool ZigbeeNode::powerSourceFlagMainPower() const { return m_powerSourceFlagMainPower; } bool ZigbeeNode::receiverOnWhenIdle() const { return m_receiverOnWhenIdle; } bool ZigbeeNode::securityCapability() const { return m_securityCapability; } bool ZigbeeNode::allocateAddress() const { return m_allocateAddress; } ZigbeeNode::PowerMode ZigbeeNode::powerMode() const { return m_powerMode; } ZigbeeNode::PowerSource ZigbeeNode::powerSource() const { return m_powerSource; } QList ZigbeeNode::availablePowerSources() const { return m_availablePowerSources; } ZigbeeNode::PowerLevel ZigbeeNode::powerLevel() const { return m_powerLevel; } void ZigbeeNode::setState(ZigbeeNode::State state) { if (m_state == state) return; qCDebug(dcZigbeeNode()) << "State changed" << this << state; m_state = state; emit stateChanged(m_state); } void ZigbeeNode::setConnected(bool connected) { if (m_connected == connected) return; qCDebug(dcZigbeeNode()) << "Connected changed" << this << connected; m_connected = connected; emit connectedChanged(m_connected); } void ZigbeeNode::setShortAddress(const quint16 &shortAddress) { m_shortAddress = shortAddress; } void ZigbeeNode::setExtendedAddress(const ZigbeeAddress &extendedAddress) { m_extendedAddress = extendedAddress; } quint16 ZigbeeNode::serverMask() const { return m_serverMask; } void ZigbeeNode::setServerMask(quint16 serverMask) { m_serverMask = serverMask; m_isPrimaryTrustCenter = ((m_serverMask >> 0) & 0x0001); m_isBackupTrustCenter = ((m_serverMask >> 1) & 0x0001); m_isPrimaryBindingCache = ((m_serverMask >> 2) & 0x0001); m_isBackupBindingCache = ((m_serverMask >> 3) & 0x0001); m_isPrimaryDiscoveryCache = ((m_serverMask >> 4) & 0x0001); m_isBackupDiscoveryCache = ((m_serverMask >> 5) & 0x0001); m_isNetworkManager = ((m_serverMask >> 6) & 0x0001); } quint8 ZigbeeNode::macCapabilitiesFlag() const { return m_macCapabilitiesFlag; } void ZigbeeNode::setMacCapabilitiesFlag(quint8 macFlag) { m_macCapabilitiesFlag = macFlag; m_alternatePanCoordinator = ((m_macCapabilitiesFlag >> 0) & 0x01); if (((m_macCapabilitiesFlag >> 1) & 0x01)) { m_deviceType = DeviceTypeFullFunction; } else { m_deviceType = DeviceTypeReducedFunction; } m_powerSourceFlagMainPower = ((m_macCapabilitiesFlag >> 2) & 0x01); m_receiverOnWhenIdle = ((m_macCapabilitiesFlag >> 3) & 0x01); m_securityCapability = ((m_macCapabilitiesFlag >> 6) & 0x01); m_allocateAddress = ((m_macCapabilitiesFlag >> 7) & 0x01); } void ZigbeeNode::setDescriptorFlag(quint8 descriptorFlag) { m_descriptorFlag = descriptorFlag; m_extendedActiveEndpointListAvailable = ((m_descriptorFlag >> 0) & 0x01); m_extendedSimpleDescriptorListAvailable = ((m_descriptorFlag >> 1) & 0x01); } quint16 ZigbeeNode::powerDescriptorFlag() const { return m_powerDescriptorFlag; } void ZigbeeNode::setPowerDescriptorFlag(quint16 powerDescriptorFlag) { m_powerDescriptorFlag = powerDescriptorFlag; qCDebug(dcZigbeeNode()) << "Parse power descriptor flag" << ZigbeeUtils::convertUint16ToHexString(m_powerDescriptorFlag); // Bit 0 - 3 Power mode // 0000: Receiver configured according to “Receiver on when idle” MAC flag in the Node Descriptor // 0001: Receiver switched on periodically // 0010: Receiver switched on when stimulated, e.g. by pressing a button m_powerMode = PowerModeAlwaysOn; if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 1)) { m_powerMode = PowerModeAlwaysOn; } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 1)) { m_powerMode = PowerModeOnPeriodically; } else if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 0) && ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 1)) { m_powerMode = PowerModeOnWhenStimulated; } // Bit 4 - 7 Available power sources // Bit 0: Permanent mains supply // Bit 1: Rechargeable battery // Bit 2: Disposable battery // Bit 4: Reserved m_availablePowerSources.clear(); if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 4)) { m_availablePowerSources.append(PowerSourcePermanentMainSupply); } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 5)) { m_availablePowerSources.append(PowerSourceRecharchableBattery); } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 6)) { m_availablePowerSources.append(PowerSourceDisposableBattery); } // Bit 8 - 11 Active source: according to the same schema as available power sources m_powerSource = PowerSourcePermanentMainSupply; if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 8)) { m_powerSource = PowerSourcePermanentMainSupply; } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 9)) { m_powerSource = PowerSourceRecharchableBattery; } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 10)) { m_powerSource = PowerSourceDisposableBattery; } // Bit 12 - 15: Battery level if available // 0000: Critically low // 0100: Approximately 33% // 1000: Approximately 66% // 1100: Approximately 100% (near fully charged) m_powerLevel = PowerLevelCriticalLow; if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) { m_powerLevel = PowerLevelCriticalLow; } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && !ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) { m_powerLevel = PowerLevelLow; } else if (!ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) { m_powerLevel = PowerLevelOk; } else if (ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 14) && ZigbeeUtils::checkBitUint16(m_powerDescriptorFlag, 15)) { m_powerLevel = PowerLevelFull; } // qCDebug(dcZigbeeNode()) << "Node power descriptor (" << ZigbeeUtils::convertUint16ToHexString(m_powerDescriptorFlag) << "):"; // qCDebug(dcZigbeeNode()) << " Power mode:" << m_powerMode; // qCDebug(dcZigbeeNode()) << " Available power sources:"; // foreach (const PowerSource &source, m_availablePowerSources) { // qCDebug(dcZigbeeNode()) << " " << source; // } // qCDebug(dcZigbeeNode()) << " Power source:" << m_powerSource; // qCDebug(dcZigbeeNode()) << " Power level:" << m_powerLevel; } void ZigbeeNode::startInitialization() { setState(StateInitializing); /* Node initialisation steps (sequentially) * - Node descriptor * - Power descriptor * - Active endpoints * - for each endpoint do: * - Simple descriptor request * - for each endpoint * - read basic cluster */ initNodeDescriptor(); } void ZigbeeNode::initNodeDescriptor() { qCDebug(dcZigbeeNode()) << "Requst node descriptor from" << this; ZigbeeDeviceObjectReply *reply = deviceObject()->requestNodeDescriptor(); connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read node descriptor" << reply->error(); m_requestRetry++; if (m_requestRetry < 3) { qCDebug(dcZigbeeNode()) << "Retry to request node descriptor" << m_requestRetry << "/" << "3"; initNodeDescriptor(); } else { qCWarning(dcZigbeeNode()) << "Failed to read node descriptor from" << this << "after 3 attempts. Giving up."; m_requestRetry = 0; // FIXME: decide what to do, remove the node again from network } return; } // The request finished, but we received a ZDP error. if (reply->responseAdpu().status != ZigbeeDeviceProfile::StatusSuccess) { qCWarning(dcZigbeeNode()) << this << "failed to read node descriptor" << reply->responseAdpu().status; // FIXME: decide what to do, remove the node again from network return; } qCDebug(dcZigbeeNode()) << this << "reading node descriptor finished successfully."; m_requestRetry = 0; // Parse and set the node descriptor FIXME: make it nicer using the data types QDataStream stream(reply->responseAdpu().payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 typeDescriptorFlag = 0; quint8 frequencyFlag = 0; quint8 macCapabilities = 0; quint16 serverMask = 0; quint8 descriptorCapabilities = 0; stream >> typeDescriptorFlag >> frequencyFlag >> macCapabilities >> m_manufacturerCode >> m_maximumBufferSize; stream >> m_maximumRxSize >> serverMask >> m_maximumTxSize >> descriptorCapabilities; // 0-2 Bit = logical type, 0 = coordinator, 1 = router, 2 = end device if (!ZigbeeUtils::checkBitUint8(typeDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint8(typeDescriptorFlag, 1)) { m_nodeType = NodeTypeCoordinator; } else if (!ZigbeeUtils::checkBitUint8(typeDescriptorFlag, 0) && ZigbeeUtils::checkBitUint8(typeDescriptorFlag, 1)) { m_nodeType = NodeTypeRouter; } else if (ZigbeeUtils::checkBitUint8(typeDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint8(typeDescriptorFlag, 1)) { m_nodeType = NodeTypeEndDevice; } m_complexDescriptorAvailable = (typeDescriptorFlag >> 3) & 0x0001; m_userDescriptorAvailable = (typeDescriptorFlag >> 4) & 0x0001; // Frequency band, 5 bits if (ZigbeeUtils::checkBitUint8(frequencyFlag, 3)) { m_frequencyBand = FrequencyBand868Mhz; } else if (ZigbeeUtils::checkBitUint8(frequencyFlag, 5)) { m_frequencyBand = FrequencyBand902Mhz; } else if (ZigbeeUtils::checkBitUint8(frequencyFlag, 6)) { m_frequencyBand = FrequencyBand2400Mhz; } setMacCapabilitiesFlag(macCapabilities); setServerMask(serverMask); setDescriptorFlag(descriptorCapabilities); qCDebug(dcZigbeeNode()) << "Node descriptor:" << ZigbeeUtils::convertUint16ToHexString(shortAddress()) << extendedAddress().toString(); qCDebug(dcZigbeeNode()) << " Node type:" << nodeType(); qCDebug(dcZigbeeNode()) << " Complex desciptor available:" << complexDescriptorAvailable(); qCDebug(dcZigbeeNode()) << " User desciptor available:" << userDescriptorAvailable(); qCDebug(dcZigbeeNode()) << " Frequency band:" << frequencyBand(); qCDebug(dcZigbeeNode()) << " Manufacturer code:" << ZigbeeUtils::convertUint16ToHexString(m_manufacturerCode); qCDebug(dcZigbeeNode()) << " Maximum Rx size:" << ZigbeeUtils::convertUint16ToHexString(m_maximumRxSize) << "(" << m_maximumRxSize << ")"; qCDebug(dcZigbeeNode()) << " Maximum Tx size:" << ZigbeeUtils::convertUint16ToHexString(m_maximumTxSize) << "(" << m_maximumTxSize << ")"; qCDebug(dcZigbeeNode()) << " Maximum buffer size:" << ZigbeeUtils::convertByteToHexString(m_maximumBufferSize) << "(" << m_maximumBufferSize << ")"; qCDebug(dcZigbeeNode()) << " Server mask:" << ZigbeeUtils::convertUint16ToHexString(serverMask); qCDebug(dcZigbeeNode()) << " Primary Trust center:" << isPrimaryTrustCenter(); qCDebug(dcZigbeeNode()) << " Backup Trust center:" << isBackupTrustCenter(); qCDebug(dcZigbeeNode()) << " Primary Binding cache:" << isPrimaryBindingCache(); qCDebug(dcZigbeeNode()) << " Backup Binding cache:" << isBackupBindingCache(); qCDebug(dcZigbeeNode()) << " Primary Discovery cache:" << isPrimaryDiscoveryCache(); qCDebug(dcZigbeeNode()) << " Backup Discovery cache:" << isBackupDiscoveryCache(); qCDebug(dcZigbeeNode()) << " Network Manager:" << isNetworkManager(); qCDebug(dcZigbeeNode()) << " Descriptor flag:" << ZigbeeUtils::convertByteToHexString(descriptorCapabilities); qCDebug(dcZigbeeNode()) << " Extended active endpoint list available:" << extendedActiveEndpointListAvailable(); qCDebug(dcZigbeeNode()) << " Extended simple descriptor list available:" << extendedSimpleDescriptorListAvailable(); qCDebug(dcZigbeeNode()) << " MAC flags:" << ZigbeeUtils::convertByteToHexString(macCapabilities); qCDebug(dcZigbeeNode()) << " Alternate PAN coordinator:" << alternatePanCoordinator(); qCDebug(dcZigbeeNode()) << " Device type:" << deviceType(); qCDebug(dcZigbeeNode()) << " Power source flag main power:" << powerSourceFlagMainPower(); qCDebug(dcZigbeeNode()) << " Receiver on when idle:" << receiverOnWhenIdle(); qCDebug(dcZigbeeNode()) << " Security capability:" << securityCapability(); qCDebug(dcZigbeeNode()) << " Allocate address:" << allocateAddress(); // Continue with the power descriptor initPowerDescriptor(); }); } void ZigbeeNode::initPowerDescriptor() { ZigbeeDeviceObjectReply *reply = deviceObject()->requestPowerDescriptor(); connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read power descriptor" << reply->error(); if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to request power descriptor from" << this << m_requestRetry << "/" << "3 attempts."; initPowerDescriptor(); } else { qCWarning(dcZigbeeNode()) << "Failed to read power descriptor from" << this << "after 3 attempts. Giving up."; m_requestRetry = 0; // FIXME: decide what to do, remove the node again from network or continue with active endpoint request } return; } ZigbeeDeviceProfile::Adpu adpu = reply->responseAdpu(); if (adpu.status != ZigbeeDeviceProfile::StatusSuccess) { qCWarning(dcZigbeeNode()) << "Failed to read power descriptor from" << this << adpu.status; // FIXME: decide what to do, remove the node again from network or continue without powerdescriptor return; } qCDebug(dcZigbeeNode()) << this << "reading power descriptor finished successfully."; m_requestRetry = 0; QDataStream stream(adpu.payload); stream.setByteOrder(QDataStream::LittleEndian); quint16 powerDescriptorFlag = 0; stream >> powerDescriptorFlag; setPowerDescriptorFlag(powerDescriptorFlag); // Continue with endpoint fetching initEndpoints(); }); } void ZigbeeNode::initEndpoints() { ZigbeeDeviceObjectReply *reply = deviceObject()->requestActiveEndpoints(); connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read active endpoints" << reply->error(); if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to request active endpoints from" << this << m_requestRetry << "/" << "3 attempts."; initEndpoints(); } else { qCWarning(dcZigbeeNode()) << "Failed to read active endpoints from" << this << "after 3 attempts. Giving up."; m_requestRetry = 0; // FIXME: decide what to do, remove the node again from network } return; } if (reply->responseAdpu().status != ZigbeeDeviceProfile::StatusSuccess) { qCWarning(dcZigbeeNode()) << "Failed to read active endpoints" << reply->responseAdpu().status; // FIXME: decide what to do, retry or stop initialization return; } qCDebug(dcZigbeeNode()) << this << "reading active endpoints finished successfully."; m_requestRetry = 0; QDataStream stream(reply->responseAdpu().payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 endpointCount = 0; m_uninitializedEndpoints.clear(); stream >> endpointCount; for (int i = 0; i < endpointCount; i++) { quint8 endpoint = 0; stream >> endpoint; m_uninitializedEndpoints.append(endpoint); } qCDebug(dcZigbeeNode()) << "Endpoints (" << endpointCount << ")"; for (int i = 0; i < m_uninitializedEndpoints.count(); i++) { qCDebug(dcZigbeeNode()) << " -" << ZigbeeUtils::convertByteToHexString(m_uninitializedEndpoints.at(i)); } // If there a no endpoints or all endpoints have already be initialized, continue with reading the basic cluster information if (m_uninitializedEndpoints.isEmpty()) { initBasicCluster(); return; } // Start reading simple descriptors sequentially initEndpoint(m_uninitializedEndpoints.first()); }); } void ZigbeeNode::initEndpoint(quint8 endpointId) { qCDebug(dcZigbeeNode()) << "Read simple descriptor of endpoint" << ZigbeeUtils::convertByteToHexString(endpointId); ZigbeeDeviceObjectReply *reply = deviceObject()->requestSimpleDescriptor(endpointId); connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply, endpointId](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read simple descriptor for endpoint" << endpointId << reply->error(); if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to request simple descriptor from" << this << ZigbeeUtils::convertByteToHexString(endpointId) << m_requestRetry << "/" << "3 attempts."; initEndpoints(); } else { qCWarning(dcZigbeeNode()) << "Failed to read simple descriptor from" << this << ZigbeeUtils::convertByteToHexString(endpointId) << "after 3 attempts. Giving up."; m_requestRetry = 0; // FIXME: decide what to do, remove the node again from network } return; } if (reply->responseAdpu().status != ZigbeeDeviceProfile::StatusSuccess) { qCWarning(dcZigbeeNode()) << this << "failed to read simple descriptor from endpoint" << endpointId << reply->responseAdpu().status; // FIXME: decide what to do, retry or stop initialization return; } qCDebug(dcZigbeeNode()) << this << "reading simple descriptor for endpoint" << endpointId << "finished successfully."; m_requestRetry = 0; quint8 length = 0; quint8 endpointId = 0; quint16 profileId = 0; quint16 deviceId = 0; quint8 deviceVersion = 0; quint8 inputClusterCount = 0; quint8 outputClusterCount = 0; QList inputClusters; QList outputClusters; QDataStream stream(reply->responseAdpu().payload); stream.setByteOrder(QDataStream::LittleEndian); stream >> length >> endpointId >> profileId >> deviceId >> deviceVersion >> inputClusterCount; qCDebug(dcZigbeeNode()) << "Node endpoint simple descriptor:"; qCDebug(dcZigbeeNode()) << " Lenght:" << ZigbeeUtils::convertByteToHexString(length); qCDebug(dcZigbeeNode()) << " End Point:" << ZigbeeUtils::convertByteToHexString(endpointId); qCDebug(dcZigbeeNode()) << " Profile:" << ZigbeeUtils::profileIdToString(static_cast(profileId)); if (profileId == Zigbee::ZigbeeProfileLightLink) { qCDebug(dcZigbeeNode()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast(deviceId); } else if (profileId == Zigbee::ZigbeeProfileHomeAutomation) { qCDebug(dcZigbeeNode()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast(deviceId); } else if (profileId == Zigbee::ZigbeeProfileGreenPower) { qCDebug(dcZigbeeNode()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast(deviceId); } qCDebug(dcZigbeeNode()) << " Device version:" << ZigbeeUtils::convertByteToHexString(deviceVersion); // Create endpoint ZigbeeNodeEndpoint *endpoint = nullptr; if (!hasEndpoint(endpointId)) { endpoint = new ZigbeeNodeEndpoint(m_network, this, endpointId, this); m_endpoints.append(endpoint); } else { endpoint = getEndpoint(endpointId); } endpoint->setProfile(static_cast(profileId)); endpoint->setDeviceId(deviceId); endpoint->setDeviceVersion(deviceVersion); // Parse and add server clusters qCDebug(dcZigbeeNode()) << " Input clusters: (" << inputClusterCount << ")"; for (int i = 0; i < inputClusterCount; i++) { quint16 clusterId = 0; stream >> clusterId; if (!endpoint->hasInputCluster(static_cast(clusterId))) { endpoint->addInputCluster(endpoint->createCluster(static_cast(clusterId), ZigbeeCluster::Server)); } qCDebug(dcZigbeeNode()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); } // Parse and add client clusters stream >> outputClusterCount; qCDebug(dcZigbeeNode()) << " Output clusters: (" << outputClusterCount << ")"; for (int i = 0; i < outputClusterCount; i++) { quint16 clusterId = 0; stream >> clusterId; if (!endpoint->hasOutputCluster(static_cast(clusterId))) { endpoint->addOutputCluster(endpoint->createCluster(static_cast(clusterId), ZigbeeCluster::Client)); } qCDebug(dcZigbeeNode()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); } m_uninitializedEndpoints.removeAll(endpointId); endpoint->m_initialized = true; if (m_uninitializedEndpoints.isEmpty()) { // Note: if we are initializing the coordinator, we can stop here if (m_shortAddress == 0) { setState(StateInitialized); return; } // Continue with the basic cluster attributes initBasicCluster(); } else { // Fetch next endpoint initEndpoint(m_uninitializedEndpoints.first()); } }); } void ZigbeeNode::initBasicCluster() { // Get the first endpoint which implements the basic cluster ZigbeeNodeEndpoint *endpoint = nullptr; foreach (ZigbeeNodeEndpoint *ep, endpoints()) { if (ep->hasInputCluster(Zigbee::ClusterIdBasic)) { endpoint = ep; break; } } if (!endpoint) { qCWarning(dcZigbeeNode()) << "Could not find any endpoint contiaining the basic cluster on" << this << "Set the node to initialized anyways."; setState(StateInitialized); return; } ZigbeeClusterBasic *basicCluster = endpoint->inputCluster(Zigbee::ClusterIdBasic); if (!basicCluster) { qCWarning(dcZigbeeNode()) << "Could not find basic cluster on" << this << "Set the node to initialized anyways."; // Set the device initialized any ways since this ist just for convinience setState(StateInitialized); return; } // Start reading basic cluster attributes sequentially readManufacturerName(basicCluster); } void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster) { ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeManufacturerName; if (basicCluster->hasAttribute(attributeId)) { // Note: only read the basic cluster information if we don't have them already from an indication. // Some devices (Lumi/Aquara) send cluster information containing different payload than a read attribute returns. // This is bad device stack implementation, but we want to make it work either way without destroying the correct // workflow as specified by the stack. qCDebug(dcZigbeeNode()) << "The manufacturer name has already been set" << this << "Continue with model identifier"; bool valueOk = false; QString manufacturerName = basicCluster->attribute(attributeId).dataType().toString(&valueOk); if (valueOk) { endpoints().first()->m_manufacturerName = manufacturerName; } else { qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << basicCluster->attribute(attributeId).dataType(); } readModelIdentifier(basicCluster); return; } qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ if (reply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); } else { qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); if (!attributeStatusRecords.isEmpty()) { ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); qCDebug(dcZigbeeNode()) << attributeStatusRecord; basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); bool valueOk = false; QString manufacturerName = attributeStatusRecord.dataType.toString(&valueOk); if (valueOk) { endpoints().first()->m_manufacturerName = manufacturerName; } else { qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << attributeStatusRecord.dataType; } } } // Continue eiterh way with attribute reading readModelIdentifier(basicCluster); }); } void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster) { ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeModelIdentifier; if (basicCluster->hasAttribute(attributeId)) { // Note: only read the basic cluster information if we don't have them already from an indication. // Some devices (Lumi/Aquara) send cluster information containing different payload than a read attribute returns. // This is bad device stack implementation, but we want to make it work either way without destroying the correct // workflow as specified by the stack. qCDebug(dcZigbeeNode()) << "The model identifier has already been set" << this << "Continue with software build ID."; bool valueOk = false; QString modelIdentifier = basicCluster->attribute(attributeId).dataType().toString(&valueOk); if (valueOk) { endpoints().first()->m_modelIdentifier= modelIdentifier; } else { qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << basicCluster->attribute(attributeId).dataType(); } readSoftwareBuildId(basicCluster); return; } qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ if (reply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); } else { qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); if (!attributeStatusRecords.isEmpty()) { ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); qCDebug(dcZigbeeNode()) << attributeStatusRecord; basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); bool valueOk = false; QString modelIdentifier = attributeStatusRecord.dataType.toString(&valueOk); if (valueOk) { endpoints().first()->m_modelIdentifier = modelIdentifier; } else { qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << attributeStatusRecord.dataType; } } } // Continue eiterh way with attribute reading readSoftwareBuildId(basicCluster); }); } void ZigbeeNode::readSoftwareBuildId(ZigbeeClusterBasic *basicCluster) { ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeSwBuildId; qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ if (reply->error() != ZigbeeClusterReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); } else { qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); if (!attributeStatusRecords.isEmpty()) { ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); qCDebug(dcZigbeeNode()) << attributeStatusRecord; basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); bool valueOk = false; QString softwareBuildId = attributeStatusRecord.dataType.toString(&valueOk); if (valueOk) { endpoints().first()->m_softwareBuildId = softwareBuildId; } else { qCWarning(dcZigbeeNode()) << "Could not convert software build id attribute data to string" << attributeStatusRecord.dataType; } } } // Finished with reading basic cluster, the node is initialized. // TODO: read other interesting cluster information setState(StateInitialized); }); } void ZigbeeNode::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication) { qCDebug(dcZigbeeNode()) << "Processing ZCL indication" << indication; // Get the endpoint ZigbeeNodeEndpoint *endpoint = getEndpoint(indication.sourceEndpoint); if (!endpoint) { qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized endpoint. There is no such endpoint on" << this << "Creating the uninitialized endpoint"; // Create the uninitialized endpoint for now and fetch information later endpoint = new ZigbeeNodeEndpoint(m_network, this, indication.sourceEndpoint, this); endpoint->setProfile(static_cast(indication.profileId)); // Note: the endpoint is not initializd yet, but keep it anyways m_endpoints.append(endpoint); } endpoint->handleZigbeeClusterLibraryIndication(indication); } QDebug operator<<(QDebug debug, ZigbeeNode *node) { debug.nospace().noquote() << "ZigbeeNode(" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress()); debug.nospace().noquote() << ", " << node->extendedAddress().toString(); debug.nospace().noquote() << ")"; return debug.space(); }