diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp index 947d020..5cfcf6f 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp @@ -78,12 +78,12 @@ ZigbeeNetworkReply *ZigbeeNetworkNxp::sendRequest(const ZigbeeNetworkRequest &re // Send the request, and keep the reply until transposrt, zigbee trasmission and response arrived connect(reply, &ZigbeeNetworkReply::finished, this, [this, reply](){ if (!m_pendingReplies.values().contains(reply)) { - qCWarning(dcZigbeeNetwork()) << "#### Reply finished but not in the pending replies list" << reply; + //qCWarning(dcZigbeeNetwork()) << "#### Reply finished but not in the pending replies list" << reply; return; } quint8 requestId = m_pendingReplies.key(reply); m_pendingReplies.remove(requestId); - qCWarning(dcZigbeeNetwork()) << "#### Removed network reply" << reply << "ID:" << requestId << "Current reply count" << m_pendingReplies.count(); + //qCWarning(dcZigbeeNetwork()) << "#### Removed network reply" << reply << "ID:" << requestId << "Current reply count" << m_pendingReplies.count(); }); // Finish the reply right the way if the network is offline @@ -110,7 +110,7 @@ ZigbeeNetworkReply *ZigbeeNetworkNxp::sendRequest(const ZigbeeNetworkRequest &re 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(); + //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); @@ -550,7 +550,7 @@ void ZigbeeNetworkNxp::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm & { ZigbeeNetworkReply *reply = m_pendingReplies.value(confirm.requestId); if (!reply) { - qCWarning(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation"; + qCDebug(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation"; return; } diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index 83805e9..066acf3 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -53,6 +53,7 @@ SOURCES += \ zigbeenetworkreply.cpp \ zigbeenetworkrequest.cpp \ zigbeenodeendpoint.cpp \ + zigbeereply.cpp \ zigbeesecurityconfiguration.cpp \ zigbeeuartadapter.cpp \ zigbeeuartadaptermonitor.cpp \ @@ -109,6 +110,7 @@ HEADERS += \ zigbeenetworkreply.h \ zigbeenetworkrequest.h \ zigbeenodeendpoint.h \ + zigbeereply.h \ zigbeesecurityconfiguration.h \ zigbeeuartadapter.h \ zigbeeuartadaptermonitor.h \ diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp index 76a47d5..1e92e07 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp +++ b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp @@ -36,6 +36,11 @@ ZigbeeClusterBasic::ZigbeeClusterBasic(ZigbeeNetwork *network, ZigbeeNode *node, } +ZigbeeClusterReply *ZigbeeClusterBasic::resetToFactoryDefaults() +{ + return executeClusterCommand(ZigbeeClusterBasic::CommandResetToFactoryDefaults); +} + void ZigbeeClusterBasic::setAttribute(const ZigbeeClusterAttribute &attribute) { qCDebug(dcZigbeeCluster()) << "Update attribute" << m_node << m_endpoint << this << static_cast(attribute.id()) << attribute.dataType(); diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h index 494e51e..fbad828 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h +++ b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h @@ -58,6 +58,11 @@ public: }; Q_ENUM(Attribute) + enum Command { + CommandResetToFactoryDefaults = 0x00 + }; + Q_ENUM(Command) + // Enum for AttributePowerSource(0x0007) enum AttributePowerSourceValue { AttributePowerSourceValueUnknown = 0x00, @@ -199,7 +204,7 @@ public: explicit ZigbeeClusterBasic(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); - // TODO: reset all clusters to factory defaults command 0x00, optional + ZigbeeClusterReply *resetToFactoryDefaults(); private: void setAttribute(const ZigbeeClusterAttribute &attribute) override; diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp index 21217b7..a8af525 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp @@ -306,6 +306,58 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestBindIeeeAddress(quint8 sourc return zdoReply; } +ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestUnbind(const ZigbeeDeviceProfile::BindingTableListRecord &bindingRecord) +{ + qCDebug(dcZigbeeDeviceObject()) << "Request unbind" << m_node << bindingRecord; + + // Build APS request + ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::UnbindRequest); + + // Generate a new transaction sequence number for this device object + quint8 transactionSequenceNumber = m_transactionSequenceNumber++; + + // Build ZDO frame + QByteArray asdu; + QDataStream stream(&asdu, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << transactionSequenceNumber; + stream << bindingRecord.sourceAddress.toUInt64(); + stream << bindingRecord.sourceEndpoint; + stream << bindingRecord.clusterId; + stream << static_cast(bindingRecord.destinationAddressMode); + if (bindingRecord.destinationAddressMode == Zigbee::DestinationAddressModeGroup) { + stream << bindingRecord.destinationAddressShort; + } else { + stream << bindingRecord.destinationAddress.toUInt64(); + stream << bindingRecord.destinationEndpoint; + } + + // Set the ZDO frame as APS request payload + request.setAsdu(asdu); + + // Create the device object reply and wait for the response indication + ZigbeeDeviceObjectReply *zdoReply = createZigbeeDeviceObjectReply(request, transactionSequenceNumber); + + // Send the request, on finished read the confirm information + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zdoReply](){ + if (!verifyNetworkError(zdoReply, networkReply)) { + finishZdoReply(zdoReply); + return; + } + + // The request was successfully sent to the device + // Now check if the expected indication response received already + if (zdoReply->isComplete()) { + finishZdoReply(zdoReply); + return; + } + // We received the confirmation but not yet the indication + }); + + return zdoReply; +} + ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLeaveNetwork(bool rejoin, bool removeChildren) { qCDebug(dcZigbeeDeviceObject()) << "Request management leave network from" << m_node << "rejoin" << rejoin << "remove children" << removeChildren; diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.h b/libnymea-zigbee/zdo/zigbeedeviceobject.h index c96f8a8..2f497c4 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.h +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.h @@ -53,6 +53,7 @@ public: ZigbeeDeviceObjectReply *requestBindShortAddress(quint8 sourceEndpointId, quint16 clusterId, quint16 destinationAddress); ZigbeeDeviceObjectReply *requestBindIeeeAddress(quint8 sourceEndpointId, quint16 clusterId, const ZigbeeAddress &destinationIeeeAddress, quint8 destinationEndpointId); + ZigbeeDeviceObjectReply *requestUnbind(const ZigbeeDeviceProfile::BindingTableListRecord &bindingRecord); // Management request ZigbeeDeviceObjectReply *requestMgmtLeaveNetwork(bool rejoin = false, bool removeChildren = false); diff --git a/libnymea-zigbee/zdo/zigbeedeviceobjectreply.cpp b/libnymea-zigbee/zdo/zigbeedeviceobjectreply.cpp index b03a7c7..1d4a0a6 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobjectreply.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobjectreply.cpp @@ -86,6 +86,11 @@ Zigbee::ZigbeeMacLayerStatus ZigbeeDeviceObjectReply::zigbeeMacStatus() const return m_zigbeeMacStatus; } +ZigbeeDeviceProfile::Status ZigbeeDeviceObjectReply::zigbeeDeviceObjectStatus() const +{ + return m_zigbeeDeviceObjectStatus; +} + ZigbeeNetworkRequest ZigbeeDeviceObjectReply::request() const { return m_request; diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index a4460a9..6fca8ef 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -117,6 +117,11 @@ ZigbeeDeviceProfile::PowerDescriptor ZigbeeNode::powerDescriptor() const return m_powerDescriptor; } +QList ZigbeeNode::bindingTableRecords() const +{ + return m_bindingTableRecords; +} + void ZigbeeNode::setState(ZigbeeNode::State state) { if (m_state == state) @@ -154,22 +159,42 @@ void ZigbeeNode::startInitialization() initNodeDescriptor(); } -void ZigbeeNode::removeAllBindings() +ZigbeeReply *ZigbeeNode::removeAllBindings() { + ZigbeeReply *reply = new ZigbeeReply(this); -} - -void ZigbeeNode::readBindingTableEntries() -{ - ZigbeeDeviceObjectReply *reply = deviceObject()->requestMgmtBind(); - connect(reply, &ZigbeeDeviceObjectReply::finished, this, [=](){ - if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { - qCWarning(dcZigbeeNode()) << "Failed to read binding table" << reply->error(); + ZigbeeReply *readBindingsReply = readBindingTableEntries(); + connect(readBindingsReply, &ZigbeeReply::finished, reply, [=](){ + if (readBindingsReply->error()) { + qCWarning(dcZigbeeNode()) << "Failed to remove all bindings because the current bindings could not be fetched from node" << this; + reply->finishReply(readBindingsReply->error()); return; } - qCDebug(dcZigbeeDeviceObject()) << "Bind table payload" << ZigbeeUtils::convertByteArrayToHexString(reply->responseData()); - QByteArray response = reply->responseData(); + qCDebug(dcZigbeeNode()) << "Current binding table records:"; + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &binding, m_bindingTableRecords) { + qCDebug(dcZigbeeNode()) << binding; + } + + // Remove bindings sequentially and finish reply if error occures or all bindings removed + removeNextBinding(reply); + }); + return reply; +} + +ZigbeeReply *ZigbeeNode::readBindingTableEntries() +{ + ZigbeeReply *reply = new ZigbeeReply(this); + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestMgmtBind(); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, this, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to read binding table" << zdoReply->error(); + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + return; + } + + qCDebug(dcZigbeeDeviceObject()) << "Bind table payload" << ZigbeeUtils::convertByteArrayToHexString(zdoReply->responseData()); + QByteArray response = zdoReply->responseData(); QDataStream stream(&response, QIODevice::ReadOnly); stream.setByteOrder(QDataStream::LittleEndian); quint8 sqn; quint8 statusInt; quint8 entriesCount; quint8 startIndex; quint8 bindingTableListCount; @@ -177,7 +202,7 @@ void ZigbeeNode::readBindingTableEntries() ZigbeeDeviceProfile::Status status = static_cast(statusInt); qCDebug(dcZigbeeDeviceObject()) << "SQN:" << sqn << status << "entries:" << entriesCount << "index:" << startIndex << "list count:" << bindingTableListCount; - QList bindingTableRecords; + m_bindingTableRecords.clear(); for (int i = 0; i < bindingTableListCount; i++) { quint64 sourceAddress; quint8 addressMode; ZigbeeDeviceProfile::BindingTableListRecord record; @@ -198,11 +223,15 @@ void ZigbeeNode::readBindingTableEntries() break; } qCDebug(dcZigbeeDeviceObject()) << record; - bindingTableRecords << record; + m_bindingTableRecords << record; } // TODO: continue reading if there are more entries + + emit bindingTableRecordsChanged(); + reply->finishReply(); }); + return reply; } void ZigbeeNode::initNodeDescriptor() @@ -418,6 +447,31 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) }); } +void ZigbeeNode::removeNextBinding(ZigbeeReply *reply) +{ + // If we have no bindings left, finish the given reply + if (m_bindingTableRecords.isEmpty()) { + reply->finishReply(); + return; + } + + ZigbeeDeviceProfile::BindingTableListRecord record = m_bindingTableRecords.last(); + ZigbeeDeviceObjectReply *zdoReply = m_deviceObject->requestUnbind(record); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to remove" << record << zdoReply->error(); + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + return; + } + + // Successfully removed + m_bindingTableRecords.removeLast(); + emit bindingTableRecordsChanged(); + + removeNextBinding(reply); + }); +} + void ZigbeeNode::initBasicCluster() { // Get the first endpoint which implements the basic cluster @@ -476,7 +530,7 @@ void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster) if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to read manufacturer name from" << this << basicCluster << m_requestRetry << "/" << "3 attempts."; - QTimer::singleShot(1000, this, [=](){readManufacturerName(basicCluster);}); + QTimer::singleShot(1000, this, [=](){ readManufacturerName(basicCluster); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read manufacturer name from" << this << basicCluster << "after 3 attempts. Giving up and continue..."; m_requestRetry = 0; @@ -535,7 +589,7 @@ void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster) if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to read model identifier from" << this << basicCluster << m_requestRetry << "/" << "3 attempts."; - QTimer::singleShot(1000, this, [=](){readModelIdentifier(basicCluster);}); + QTimer::singleShot(1000, this, [=](){ readModelIdentifier(basicCluster); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read model identifier from" << this << basicCluster << "after 3 attempts. Giving up and continue..."; m_requestRetry = 0; @@ -576,7 +630,7 @@ void ZigbeeNode::readSoftwareBuildId(ZigbeeClusterBasic *basicCluster) if (m_requestRetry < 3) { m_requestRetry++; qCDebug(dcZigbeeNode()) << "Retry to read model identifier from" << this << basicCluster << m_requestRetry << "/" << "3 attempts."; - QTimer::singleShot(1000, this, [=](){readSoftwareBuildId(basicCluster);}); + QTimer::singleShot(1000, this, [=](){ readSoftwareBuildId(basicCluster); }); } else { qCWarning(dcZigbeeNode()) << "Failed to read model identifier from" << this << basicCluster << "after 3 attempts. Giving up and continue..."; m_requestRetry = 0; diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index 95e55c1..218ae25 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -32,6 +32,7 @@ #include #include "zigbee.h" +#include "zigbeereply.h" #include "zigbeeaddress.h" #include "zigbeenodeendpoint.h" #include "zdo/zigbeedeviceobject.h" @@ -82,11 +83,14 @@ public: QList availablePowerSources() const; ZigbeeDeviceProfile::PowerLevel powerLevel() const; + // Only available if fetched + QList bindingTableRecords() const; + // This method starts the node initialization phase (read descriptors and endpoints) void startInitialization(); - void removeAllBindings(); - void readBindingTableEntries(); + ZigbeeReply *removeAllBindings(); + ZigbeeReply *readBindingTableEntries(); private: ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent = nullptr); @@ -107,6 +111,8 @@ private: ZigbeeDeviceProfile::MacCapabilities m_macCapabilities; ZigbeeDeviceProfile::PowerDescriptor m_powerDescriptor; + QList m_bindingTableRecords; + void setState(State state); void setReachable(bool reachable); @@ -118,6 +124,8 @@ private: void initEndpoints(); void initEndpoint(quint8 endpointId); + void removeNextBinding(ZigbeeReply *reply); + // For convenience and having base information about the first endpoint void initBasicCluster(); void readManufacturerName(ZigbeeClusterBasic *basicCluster); @@ -132,6 +140,7 @@ signals: void lqiChanged(quint8 lqi); void lastSeenChanged(const QDateTime &lastSeen); void reachableChanged(bool reachable); + void bindingTableRecordsChanged(); void clusterAdded(ZigbeeCluster *cluster); void clusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute); diff --git a/libnymea-zigbee/zigbeereply.cpp b/libnymea-zigbee/zigbeereply.cpp new file mode 100644 index 0000000..f351f4f --- /dev/null +++ b/libnymea-zigbee/zigbeereply.cpp @@ -0,0 +1,45 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeereply.h" + +ZigbeeReply::Error ZigbeeReply::error() const +{ + return m_error; +} + +ZigbeeReply::ZigbeeReply(QObject *parent) : QObject(parent) +{ + +} + +void ZigbeeReply::finishReply(ZigbeeReply::Error error) +{ + m_error = error; + emit finished(); + deleteLater(); +} diff --git a/libnymea-zigbee/zigbeereply.h b/libnymea-zigbee/zigbeereply.h new file mode 100644 index 0000000..1174dc0 --- /dev/null +++ b/libnymea-zigbee/zigbeereply.h @@ -0,0 +1,66 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef ZIGBEEREPLY_H +#define ZIGBEEREPLY_H + +#include + +#include "zigbee.h" + +class ZigbeeReply : public QObject +{ + Q_OBJECT + + friend class ZigbeeNode; + friend class ZigbeeNetwork; + friend class ZigbeeNodeEndpoint; + +public: + enum Error { + ErrorNoError, + ErrorTimeout, + ErrorInterfaceError, + ErrorZigbeeError, + ErrorNetworkOffline + }; + Q_ENUM(Error) + + Error error() const; + +protected: + explicit ZigbeeReply(QObject *parent = nullptr); + + Error m_error = ErrorNoError; + + void finishReply(Error error = ErrorNoError); + +signals: + void finished(); +}; + +#endif // ZIGBEEREPLY_H