diff --git a/docs/nxp/JN-AN-1216-ZigBee-3-0-IoT-ControlBridge-UserGuide.pdf b/docs/nxp/JN-AN-1216-ZigBee-3-0-IoT-ControlBridge-UserGuide.pdf deleted file mode 100755 index 9d4edb4..0000000 Binary files a/docs/nxp/JN-AN-1216-ZigBee-3-0-IoT-ControlBridge-UserGuide.pdf and /dev/null differ diff --git a/docs/nxp/JN-AN-1223-ZigBee-IoT-Gateway-Control-Bridge-UserGuide-1014.pdf b/docs/nxp/JN-AN-1223-ZigBee-IoT-Gateway-Control-Bridge-UserGuide-1014.pdf deleted file mode 100644 index d97b38c..0000000 Binary files a/docs/nxp/JN-AN-1223-ZigBee-IoT-Gateway-Control-Bridge-UserGuide-1014.pdf and /dev/null differ diff --git a/docs/nxp/ZigBee 3.0 Device User Guide - JN-UG-3114.pdf b/docs/nxp/ZigBee 3.0 Device User Guide - JN-UG-3114.pdf new file mode 100644 index 0000000..260ba6f Binary files /dev/null and b/docs/nxp/ZigBee 3.0 Device User Guide - JN-UG-3114.pdf differ diff --git a/docs/nxp/JN-UG-3113.pdf b/docs/nxp/ZigBee 3.0 Stack User Guide - JN-UG-3113.pdf similarity index 100% rename from docs/nxp/JN-UG-3113.pdf rename to docs/nxp/ZigBee 3.0 Stack User Guide - JN-UG-3113.pdf diff --git a/docs/nxp/ZigBee Cluster Library User Guide - JN-UG-3115.pdf b/docs/nxp/ZigBee Cluster Library User Guide - JN-UG-3115.pdf new file mode 100644 index 0000000..e0ec89d Binary files /dev/null and b/docs/nxp/ZigBee Cluster Library User Guide - JN-UG-3115.pdf differ diff --git a/docs/nxp/ZigBee Green Power User Guide - JN-UG-3119.pdf b/docs/nxp/ZigBee Green Power User Guide - JN-UG-3119.pdf new file mode 100644 index 0000000..14817cc Binary files /dev/null and b/docs/nxp/ZigBee Green Power User Guide - JN-UG-3119.pdf differ diff --git a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp index 3415baa..a634eb7 100644 --- a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp +++ b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp @@ -671,8 +671,15 @@ void ZigbeeNetworkDeconz::onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress } if (hasNode(ieeeAddress)) { - qCWarning(dcZigbeeNetwork()) << "Already known device announced. FIXME: Ignoring announcement" << ieeeAddress.toString(); - return; + ZigbeeNode *node = getZigbeeNode(ieeeAddress); + if (shortAddress == node->shortAddress()) { + qCDebug(dcZigbeeNetwork()) << "Already known device announced and is reachable again" << node; + setNodeReachable(node, true); + return; + } else { + qCDebug(dcZigbeeNetwork()) << "Already known device announced with different network address. Removing node and reinitialize..."; + removeNode(node); + } } ZigbeeNode *node = createNode(shortAddress, ieeeAddress, macCapabilities, this); diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp index 5cfcf6f..5362f1a 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp @@ -88,33 +88,13 @@ ZigbeeNetworkReply *ZigbeeNetworkNxp::sendRequest(const ZigbeeNetworkRequest &re // Finish the reply right the way if the network is offline if (!m_controller->available() || state() == ZigbeeNetwork::StateOffline) { - finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNetworkOffline); + finishReplyInternally(reply, ZigbeeNetworkReply::ErrorNetworkOffline); return reply; } - ZigbeeInterfaceNxpReply *interfaceReply = m_controller->requestSendRequest(request); - connect(interfaceReply, &ZigbeeInterfaceNxpReply::finished, reply, [this, reply, interfaceReply](){ - if (interfaceReply->status() != Nxp::StatusSuccess) { - qCWarning(dcZigbeeController()) << "Could send request to controller. SQN:" << interfaceReply->sequenceNumber() << interfaceReply->status(); - finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError); - return; - } - - // Note: this is a special case for nxp coordinator requests, they don't send a confirm because the request will not be sent trough the network - if (reply->request().destinationShortAddress() == 0x0000 && reply->request().profileId() == Zigbee::ZigbeeProfileDevice) { - qCDebug(dcZigbeeNetwork()) << "Finish reply since there will be no CONFIRM for local node requests."; - finishNetworkReply(reply); - return; - } - - quint8 networkRequestId = interfaceReply->responseData().at(0); - //qCDebug(dcZigbeeNetwork()) << "Request has network SQN" << networkRequestId; - reply->request().setRequestId(networkRequestId); - //qCWarning(dcZigbeeNetwork()) << "#### Insert network reply" << reply << "ID:" << networkRequestId << "Current reply count" << m_pendingReplies.count(); - m_pendingReplies.insert(networkRequestId, reply); - // The request has been sent successfully to the device, start the timeout timer now - startWaitingReply(reply); - }); + // Enqueu reply and send next one if we have enouth capacity + m_replyQueue.enqueue(reply); + sendNextReply(); return reply; } @@ -191,6 +171,52 @@ void ZigbeeNetworkNxp::setPermitJoining(quint8 duration, quint16 address) }); } +void ZigbeeNetworkNxp::sendNextReply() +{ + if (m_replyQueue.isEmpty()) + return; + + if (m_currentReply) + return; + + + ZigbeeNetworkReply *reply = m_replyQueue.dequeue(); + ZigbeeInterfaceNxpReply *interfaceReply = m_controller->requestSendRequest(reply->request()); + connect(interfaceReply, &ZigbeeInterfaceNxpReply::finished, reply, [this, reply, interfaceReply](){ + if (interfaceReply->status() != Nxp::StatusSuccess) { + qCWarning(dcZigbeeController()) << "Could send request to controller. SQN:" << interfaceReply->sequenceNumber() << interfaceReply->status(); + finishReplyInternally(reply, ZigbeeNetworkReply::ErrorInterfaceError); + return; + } + + // Note: this is a special case for nxp coordinator requests, they don't send a confirm because the request will not be sent trough the network + if (reply->request().destinationShortAddress() == 0x0000 && reply->request().profileId() == Zigbee::ZigbeeProfileDevice) { + qCDebug(dcZigbeeNetwork()) << "Finish reply since there will be no CONFIRM for local node requests."; + finishReplyInternally(reply); + return; + } + + quint8 networkRequestId = interfaceReply->responseData().at(0); + //qCDebug(dcZigbeeNetwork()) << "Request has network SQN" << networkRequestId; + reply->request().setRequestId(networkRequestId); + //qCWarning(dcZigbeeNetwork()) << "#### Insert network reply" << reply << "ID:" << networkRequestId << "Current reply count" << m_pendingReplies.count(); + m_pendingReplies.insert(networkRequestId, reply); + // The request has been sent successfully to the device, start the timeout timer now + startWaitingReply(reply); + }); + +} + +void ZigbeeNetworkNxp::finishReplyInternally(ZigbeeNetworkReply *reply, ZigbeeNetworkReply::Error error) +{ + finishNetworkReply(reply, error); + if (m_currentReply == reply) { + m_currentReply = nullptr; + } + + sendNextReply(); +} + ZigbeeNetworkReply *ZigbeeNetworkNxp::requestSetPermitJoin(quint16 shortAddress, quint8 duration) { // Get the power descriptor @@ -410,22 +436,22 @@ void ZigbeeNetworkNxp::onControllerStateChanged(ZigbeeBridgeControllerNxp::Contr connect(coordinatorNode, &ZigbeeNode::stateChanged, this, [this, coordinatorNode](ZigbeeNode::State state){ if (state == ZigbeeNode::StateInitialized) { qCDebug(dcZigbeeNetwork()) << "Coordinator initialized successfully." << coordinatorNode; -// ZigbeeClusterGroups *groupsCluster = coordinatorNode->getEndpoint(0x01)->inputCluster(ZigbeeClusterLibrary::ClusterIdGroups); -// if (!groupsCluster) { -// qCWarning(dcZigbeeNetwork()) << "Failed to get groups cluster from coordinator. The coordinator will not be in default group 0x0000"; -// setState(StateRunning); -// setPermitJoining(0); -// return; -// } + // ZigbeeClusterGroups *groupsCluster = coordinatorNode->getEndpoint(0x01)->inputCluster(ZigbeeClusterLibrary::ClusterIdGroups); + // if (!groupsCluster) { + // qCWarning(dcZigbeeNetwork()) << "Failed to get groups cluster from coordinator. The coordinator will not be in default group 0x0000"; + // setState(StateRunning); + // setPermitJoining(0); + // return; + // } -// ZigbeeClusterReply *reply = groupsCluster->addGroup(0x0000, "Default"); -// connect(reply, &ZigbeeClusterReply::finished, this, [=](){ -// if (reply->error() != ZigbeeClusterReply::ErrorNoError) { -// qCWarning(dcZigbeeNetwork()) << "Failed to add coordinator to default group 0x0000. The coordinator will not be in default group 0x0000"; -// } -// setState(StateRunning); -// setPermitJoining(0); -// }); + // ZigbeeClusterReply *reply = groupsCluster->addGroup(0x0000, "Default"); + // connect(reply, &ZigbeeClusterReply::finished, this, [=](){ + // if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + // qCWarning(dcZigbeeNetwork()) << "Failed to add coordinator to default group 0x0000. The coordinator will not be in default group 0x0000"; + // } + // setState(StateRunning); + // setPermitJoining(0); + // }); setState(StateRunning); setPermitJoining(0); @@ -581,8 +607,15 @@ void ZigbeeNetworkNxp::onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress iee } if (hasNode(ieeeAddress)) { - qCWarning(dcZigbeeNetwork()) << "Already known device announced. FIXME: Ignoring announcement" << ieeeAddress.toString(); - return; + ZigbeeNode *node = getZigbeeNode(ieeeAddress); + if (shortAddress == node->shortAddress()) { + qCDebug(dcZigbeeNetwork()) << "Already known device announced and is reachable again" << node; + setNodeReachable(node, true); + return; + } else { + qCDebug(dcZigbeeNetwork()) << "Already known device announced with different network address. Removing node and reinitialize..."; + removeNode(node); + } } ZigbeeNode *node = createNode(shortAddress, ieeeAddress, macCapabilities, this); diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h index 33504dd..093ca64 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h @@ -51,9 +51,15 @@ public: private: ZigbeeBridgeControllerNxp *m_controller = nullptr; bool m_networkRunning = false; - QHash m_pendingReplies; - int m_reconnectCounter = 0; + QHash m_pendingReplies; + QQueue m_replyQueue; + ZigbeeNetworkReply *m_currentReply = nullptr; + + void sendNextReply(); + void finishReplyInternally(ZigbeeNetworkReply *reply, ZigbeeNetworkReply::Error error = ZigbeeNetworkReply::ErrorNoError); + + int m_reconnectCounter = 0; bool processVersionReply(ZigbeeInterfaceNxpReply *reply); // ZDO diff --git a/libnymea-zigbee/zigbee.h b/libnymea-zigbee/zigbee.h index 54328bc..639169f 100644 --- a/libnymea-zigbee/zigbee.h +++ b/libnymea-zigbee/zigbee.h @@ -323,7 +323,8 @@ public: ZigbeeNwkLayerStatusRouteDiscoveryFailed = 0xd0, ZigbeeNwkLayerStatusRouteError = 0xd1, ZigbeeNwkLayerStatusBtTableFull = 0xd2, - ZigbeeNwkLayerStatusFrameNotBuffered = 0xd3 + ZigbeeNwkLayerStatusFrameNotBuffered = 0xd3, + ZigbeeNwkLayerStatusFrameBuffered = 0xd4 }; Q_ENUM(ZigbeeNwkLayerStatus) diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 144865f..dae2e8f 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -53,13 +53,21 @@ ZigbeeNetwork::ZigbeeNetwork(const QUuid &networkUuid, QObject *parent) : }); + m_reachableRefreshTimer = new QTimer(this); + m_reachableRefreshTimer->setInterval(300000); + m_reachableRefreshTimer->setSingleShot(false); + connect(m_reachableRefreshTimer, &QTimer::timeout, this, &ZigbeeNetwork::evaluateNodeReachableStates); + connect(this, &ZigbeeNetwork::stateChanged, this, [this](ZigbeeNetwork::State state){ - if (state != ZigbeeNetwork::StateRunning) { + if (state == ZigbeeNetwork::StateRunning) { + evaluateNodeReachableStates(); + m_reachableRefreshTimer->start(); + } else { foreach (ZigbeeNode *node, m_nodes) { node->setReachable(false); } + m_reachableRefreshTimer->stop(); } - }); } @@ -549,6 +557,11 @@ void ZigbeeNetwork::removeUninitializedNode(ZigbeeNode *node) node->deleteLater(); } +void ZigbeeNetwork::setNodeReachable(ZigbeeNode *node, bool reachable) +{ + node->setReachable(reachable); +} + void ZigbeeNetwork::setState(ZigbeeNetwork::State state) { if (m_state == state) @@ -599,7 +612,7 @@ void ZigbeeNetwork::setReplyResponseError(ZigbeeNetworkReply *reply, Zigbee::Zig } else { // There has been an error while transporting the request to the device // Note: if the APS status is >= 0xc1, it has to interpreted as NWK layer error - if (zigbeeApsStatus >= 0xc1 && zigbeeApsStatus <= 0xd3) { + if (zigbeeApsStatus >= 0xc1 && zigbeeApsStatus <= 0xd4) { reply->m_zigbeeNwkStatus = static_cast(static_cast(zigbeeApsStatus)); finishNetworkReply(reply, ZigbeeNetworkReply::ErrorZigbeeNwkStatusError); } else if (zigbeeApsStatus >= 0xE0 && zigbeeApsStatus <= 0xF4) { @@ -662,6 +675,32 @@ void ZigbeeNetwork::onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const m_database->saveAttribute(cluster, attribute); } +void ZigbeeNetwork::evaluateNodeReachableStates() +{ + qCDebug(dcZigbeeNetwork()) << "Evaluate reachable state of nodes"; + foreach (ZigbeeNode *node, m_nodes) { + if (node->macCapabilities().receiverOnWhenIdle && node->shortAddress() != 0x0000) { + ZigbeeDeviceObjectReply *zdoReply = node->deviceObject()->requestMgmtLqi(); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, this, [=](){ + if (zdoReply->error()) { + qCWarning(dcZigbeeNetwork()) << node << "seems not to be reachable" << zdoReply->error(); + setNodeReachable(node, false); + } + }); + } else { + // Note: sleeping devices should send some message within 6 hours, + // otherwise the device might not be reachable any more + int msSinceLastSeen = node->lastSeen().msecsTo(QDateTime::currentDateTimeUtc()); + qCDebug(dcZigbeeNetwork()) << node << "last seen" << QTime::fromMSecsSinceStartOfDay(msSinceLastSeen).toString(); + if (msSinceLastSeen < 1000*60*60*6) { + setNodeReachable(node, true); + } else { + setNodeReachable(node, false); + } + } + } +} + QDebug operator<<(QDebug debug, ZigbeeNetwork *network) { debug.nospace().noquote() << "ZigbeeNetwork(" << network->macAddress().toString() << ", " diff --git a/libnymea-zigbee/zigbeenetwork.h b/libnymea-zigbee/zigbeenetwork.h index 527a77c..f35e176 100644 --- a/libnymea-zigbee/zigbeenetwork.h +++ b/libnymea-zigbee/zigbeenetwork.h @@ -175,6 +175,8 @@ protected: quint8 m_permitJoiningDuration = 120; quint8 m_permitJoiningRemaining = 0; + QTimer *m_reachableRefreshTimer = nullptr; + void setPermitJoiningEnabled(bool permitJoiningEnabled); void setPermitJoiningDuration(quint8 duration); void setPermitJoiningRemaining(quint8 remaining); @@ -188,6 +190,8 @@ protected: void removeNode(ZigbeeNode *node); void removeUninitializedNode(ZigbeeNode *node); + void setNodeReachable(ZigbeeNode *node, bool reachable); + void setState(State state); void setError(Error error); @@ -227,6 +231,7 @@ signals: private slots: void onNodeStateChanged(ZigbeeNode::State state); void onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute); + void evaluateNodeReachableStates(); public slots: virtual void startNetwork() = 0; diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 31b4281..331147a 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -244,7 +244,7 @@ void ZigbeeNode::initNodeDescriptor() m_requestRetry++; if (m_requestRetry < 3) { qCDebug(dcZigbeeNode()) << "Retry to request node descriptor" << m_requestRetry << "/" << "3"; - initNodeDescriptor(); + QTimer::singleShot(1000, this, [=](){ initNodeDescriptor(); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read node descriptor from" << this << "after 3 attempts. Giving up."; m_requestRetry = 0; @@ -273,7 +273,7 @@ void ZigbeeNode::initPowerDescriptor() if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to request power descriptor from" << this << m_requestRetry << "/" << "3 attempts."; - initPowerDescriptor(); + QTimer::singleShot(1000, this, [=](){ initPowerDescriptor(); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read power descriptor from" << this << "after 3 attempts. Giving up."; m_requestRetry = 0; @@ -305,7 +305,7 @@ void ZigbeeNode::initEndpoints() if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to request active endpoints from" << this << m_requestRetry << "/" << "3 attempts."; - initEndpoints(); + QTimer::singleShot(1000, this, [=](){ initEndpoints(); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read active endpoints from" << this << "after 3 attempts. Giving up."; m_requestRetry = 0; @@ -355,7 +355,7 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to request simple descriptor from" << this << ZigbeeUtils::convertByteToHexString(endpointId) << m_requestRetry << "/" << "3 attempts."; - initEndpoint(endpointId); + QTimer::singleShot(1000, this, [=](){ initEndpoint(endpointId); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read simple descriptor from" << this << ZigbeeUtils::convertByteToHexString(endpointId) << "after 3 attempts. Giving up."; m_requestRetry = 0; @@ -721,7 +721,19 @@ QDebug operator<<(QDebug debug, ZigbeeNode *node) { debug.nospace().noquote() << "ZigbeeNode(" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress()); debug.nospace().noquote() << ", " << node->extendedAddress().toString(); - debug.nospace().noquote() << ", RX on:" << node->macCapabilities().receiverOnWhenIdle; + switch (node->nodeDescriptor().nodeType) { + case ZigbeeDeviceProfile::NodeTypeCoordinator: + debug.nospace().noquote() << ", Coordinator"; + break; + case ZigbeeDeviceProfile::NodeTypeRouter: + debug.nospace().noquote() << ", Router"; + break; + case ZigbeeDeviceProfile::NodeTypeEndDevice: + debug.nospace().noquote() << ", End device"; + break; + } + + debug.nospace().noquote() << ", RxOn:" << node->macCapabilities().receiverOnWhenIdle; debug.nospace().noquote() << ")"; return debug.space().quote(); } diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index 1feb79a..a798f32 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -56,6 +56,9 @@ public: Q_ENUM(State) State state() const; + + // Note: For sleepy devices this indicates best effort. + // If a device does not send any data within 6h, it will be assumed to no reachable bool reachable() const; QUuid networkUuid() const;