Introduce binding management and cleaning

This commit is contained in:
Simon Stürz 2020-11-25 13:06:31 +01:00
parent 61114fc773
commit e245f7c845
11 changed files with 267 additions and 23 deletions

View File

@ -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;
}

View File

@ -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 \

View File

@ -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>(attribute.id()) << attribute.dataType();

View File

@ -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;

View File

@ -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<quint8>(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;

View File

@ -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);

View File

@ -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;

View File

@ -117,6 +117,11 @@ ZigbeeDeviceProfile::PowerDescriptor ZigbeeNode::powerDescriptor() const
return m_powerDescriptor;
}
QList<ZigbeeDeviceProfile::BindingTableListRecord> 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<ZigbeeDeviceProfile::Status>(statusInt);
qCDebug(dcZigbeeDeviceObject()) << "SQN:" << sqn << status << "entries:" << entriesCount << "index:" << startIndex << "list count:" << bindingTableListCount;
QList<ZigbeeDeviceProfile::BindingTableListRecord> 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;

View File

@ -32,6 +32,7 @@
#include <QDateTime>
#include "zigbee.h"
#include "zigbeereply.h"
#include "zigbeeaddress.h"
#include "zigbeenodeendpoint.h"
#include "zdo/zigbeedeviceobject.h"
@ -82,11 +83,14 @@ public:
QList<ZigbeeDeviceProfile::PowerSource> availablePowerSources() const;
ZigbeeDeviceProfile::PowerLevel powerLevel() const;
// Only available if fetched
QList<ZigbeeDeviceProfile::BindingTableListRecord> 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<ZigbeeDeviceProfile::BindingTableListRecord> 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);

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#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