From acfd73c271f5d6102026dd1aa93e05bae4cb250e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Sat, 27 Aug 2022 23:27:00 +0200 Subject: [PATCH] Obtain LQI neighbor and routing tables from the network --- libnymea-zigbee/zdo/zigbeedeviceobject.cpp | 47 ++++++- libnymea-zigbee/zdo/zigbeedeviceobject.h | 1 + libnymea-zigbee/zdo/zigbeedeviceprofile.cpp | 97 ++++++++++++- libnymea-zigbee/zdo/zigbeedeviceprofile.h | 55 +++++++- libnymea-zigbee/zigbeenetwork.cpp | 79 +++++++---- libnymea-zigbee/zigbeenetwork.h | 5 +- libnymea-zigbee/zigbeenode.cpp | 144 ++++++++++++++++++++ libnymea-zigbee/zigbeenode.h | 13 ++ 8 files changed, 405 insertions(+), 36 deletions(-) diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp index a454cac..c878271 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp @@ -555,7 +555,7 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLeaveNetwork(bool rejoin ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLqi(quint8 startIndex) { - qCDebug(dcZigbeeDeviceObject()) << "Request lqi table from" << m_node << "start index" << startIndex; + qCDebug(dcZigbeeDeviceObject()) << "Requesting lqi table from" << m_node << "start index" << startIndex; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtLqiRequest); @@ -597,7 +597,7 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLqi(quint8 startIndex) ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtBind(quint8 startIndex) { - qCDebug(dcZigbeeDeviceObject()) << "Request management bind table from" << m_node << "start index" << startIndex; + qCDebug(dcZigbeeDeviceObject()) << "Requesting management bind table from" << m_node << "start index" << startIndex; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtBindRequest); @@ -637,6 +637,49 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtBind(quint8 startIndex) return zdoReply; } +ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtRtg(quint8 startIndex) +{ + qCDebug(dcZigbeeDeviceObject()) << "Requesting routing table from" << m_node << "start index" << startIndex; + + // Build APS request + ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtRoutingTableRequest); + + // Generate a new transaction sequence number for this device object + quint8 transactionSequenceNumber = m_transactionSequenceNumber++; + + QByteArray asdu; + QDataStream stream(&asdu, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << transactionSequenceNumber << startIndex; + + // 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, zdoReply, [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()) { + qCDebug(dcZigbeeDeviceObject()) << "Successfully received response for" << static_cast(networkReply->request().clusterId()); + finishZdoReply(zdoReply); + return; + } + // We received the confirmation but not yet the indication + }); + + return zdoReply; + +} + ZigbeeNetworkRequest ZigbeeDeviceObject::buildZdoRequest(quint16 zdoRequest) { ZigbeeNetworkRequest request; diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.h b/libnymea-zigbee/zdo/zigbeedeviceobject.h index 70202b9..7bcb07f 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.h +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.h @@ -61,6 +61,7 @@ public: ZigbeeDeviceObjectReply *requestMgmtLeaveNetwork(bool rejoin = false, bool removeChildren = false); ZigbeeDeviceObjectReply *requestMgmtLqi(quint8 startIndex = 0x00); ZigbeeDeviceObjectReply *requestMgmtBind(quint8 startIndex = 0x00); + ZigbeeDeviceObjectReply *requestMgmtRtg(quint8 startIndex = 0x00); // TODO: write all requests diff --git a/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp b/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp index 81b0e1f..1362446 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp @@ -174,6 +174,67 @@ ZigbeeDeviceProfile::PowerDescriptor ZigbeeDeviceProfile::parsePowerDescriptor(q return powerDescriptor; } +ZigbeeDeviceProfile::NeighborTable ZigbeeDeviceProfile::parseNeighborTable(const QByteArray &payload) +{ + ZigbeeDeviceProfile::NeighborTable table; + QDataStream stream(payload); + stream.setByteOrder(QDataStream::LittleEndian); + + quint8 messageId; quint8 listRecordCount; + stream >> messageId >> table.status >> table.tableSize >> table.startIndex >> listRecordCount; + for (int i = 0; i < listRecordCount; i++) { + ZigbeeDeviceProfile::NeighborTableListRecord record; + stream >> record.extendedPanId; + quint64 ieeeAddress; + stream >> ieeeAddress; + record.ieeeAddress = ZigbeeAddress(ieeeAddress); + stream >> record.shortAddress; + + quint8 nodeFlags; + stream >> nodeFlags; + record.nodeType = static_cast(nodeFlags & 0x03); + record.receiverOnWhenIdle = static_cast((nodeFlags & 0x0c) >> 2); + record.relationship = static_cast((nodeFlags & 0x70) >> 4); + + stream >> nodeFlags; + record.permitJoining = (nodeFlags & 0x03) == 1; + stream >> record.depth; + stream >> record.lqi; + + table.records.append(record); + } + + return table; +} + +ZigbeeDeviceProfile::RoutingTable ZigbeeDeviceProfile::parseRoutingTable(const QByteArray &payload) +{ + ZigbeeDeviceProfile::RoutingTable table; + QDataStream stream(payload); + stream.setByteOrder(QDataStream::LittleEndian); + + quint8 messageId; quint8 listRecordCount; + stream >> messageId >> table.status >> table.tableSize >> table.startIndex >> listRecordCount; + for (int i = 0; i < listRecordCount; i++) { + ZigbeeDeviceProfile::RoutingTableListRecord record; + stream >> record.destinationAddress; + + qint8 flags; + stream >> flags; + record.status = static_cast(flags & 0x07); + record.memoryConstrained = (flags & 0x08) > 0; + record.manyToOne = (flags & 0x10) > 0; + record.routeRecordRequired = (flags & 0x20) > 0; + + stream >> record.nextHopAddress; + + table.records.append(record); + } + + return table; + +} + ZigbeeDeviceProfile::Adpu ZigbeeDeviceProfile::parseAdpu(const QByteArray &adpu) { QDataStream stream(adpu); @@ -187,12 +248,12 @@ ZigbeeDeviceProfile::Adpu ZigbeeDeviceProfile::parseAdpu(const QByteArray &adpu) return deviceAdpu; } -QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::Adpu &deviceAdpu) +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::Adpu &adpu) { - debug.nospace() << "DeviceProfileAdpu(SQN: " << deviceAdpu.transactionSequenceNumber << ", "; - debug.nospace() << deviceAdpu.status << ", "; - debug.nospace() << ZigbeeUtils::convertUint16ToHexString(deviceAdpu.addressOfInterest) << ", "; - debug.nospace() << "Payload: " << ZigbeeUtils::convertByteArrayToHexString(deviceAdpu.payload) << ")"; + debug.nospace() << "DeviceProfileAdpu(SQN: " << adpu.transactionSequenceNumber << ", "; + debug.nospace() << adpu.status << ", "; + debug.nospace() << ZigbeeUtils::convertUint16ToHexString(adpu.addressOfInterest) << ", "; + debug.nospace() << "Payload: " << ZigbeeUtils::convertByteArrayToHexString(adpu.payload) << ")"; return debug.space(); } @@ -273,3 +334,29 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecor } return debug; } + +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::NeighborTableListRecord &neighborTableListRecord) +{ + debug.nospace() << "NeighborTableListRecord(" << neighborTableListRecord.ieeeAddress.toString() << ", "; + debug.nospace() << "NWK address: " << ZigbeeUtils::convertUint16ToHexString(neighborTableListRecord.shortAddress) << ", "; + debug.nospace() << "Extended PAN ID: " << neighborTableListRecord.extendedPanId << ", "; + debug.nospace() << "Node type: " << neighborTableListRecord.nodeType << ", "; + debug.nospace() << "RxOn: " << neighborTableListRecord.receiverOnWhenIdle << ", "; + debug.nospace() << "Relationship: " << neighborTableListRecord.relationship << ", "; + debug.nospace() << "Permit join: " << neighborTableListRecord.permitJoining << ", "; + debug.nospace() << "Depth: " << neighborTableListRecord.depth << ", "; + debug.nospace() << "LQI: " << neighborTableListRecord.lqi << ") "; + return debug; +} + +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::RoutingTableListRecord &routingTableListRecord) +{ + debug.nospace() << "RoutingTableListRecord("; + debug.nospace() << "Destination address: " << ZigbeeUtils::convertUint16ToHexString(routingTableListRecord.destinationAddress) << ", "; + debug.nospace() << "Next hop: " << routingTableListRecord.nextHopAddress << ", "; + debug.nospace() << "Status: " << routingTableListRecord.status << ", "; + debug.nospace() << "Memory constrained: " << routingTableListRecord.memoryConstrained << ", "; + debug.nospace() << "Many-to-one: " << routingTableListRecord.manyToOne << ", "; + debug.nospace() << "RRR: " << routingTableListRecord.routeRecordRequired << ")"; + return debug; +} diff --git a/libnymea-zigbee/zdo/zigbeedeviceprofile.h b/libnymea-zigbee/zdo/zigbeedeviceprofile.h index 03444fc..f311463 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceprofile.h +++ b/libnymea-zigbee/zdo/zigbeedeviceprofile.h @@ -198,12 +198,23 @@ public: Q_ENUM(DeviceType) enum Relationship { - Parent, - Child, - Sibling + RelationshipParent, + RelationshipChild, + RelationshipSibling, + RelationshipNone, + RelationshipPreviousChild }; Q_ENUM(Relationship) + enum RouteStatus { + RouteStatusActive, + RouteStatusDiscoveryUnderway, + RouteStatusDiscoveryFailed, + RouteStatusInactive, + RouteStatusValidationUnderway + }; + Q_ENUM(RouteStatus) + enum PowerMode { PowerModeAlwaysOn, PowerModeOnPeriodically, @@ -287,12 +298,48 @@ public: quint8 destinationEndpoint; // Only for destination address 0x03 } BindingTableListRecord; + typedef struct NeighborTableListRecord { + quint64 extendedPanId; + ZigbeeAddress ieeeAddress; + quint16 shortAddress; + NodeType nodeType; + bool receiverOnWhenIdle; + Relationship relationship; + bool permitJoining; + quint8 depth; + quint8 lqi; + } NeighborTableListRecord; + + typedef struct NeighborTable{ + quint8 status; + quint8 tableSize; + quint8 startIndex; + QList records; + } NeighborTable; + + typedef struct RoutingTableListRecord { + quint16 destinationAddress; + RouteStatus status; + bool memoryConstrained; + bool manyToOne; + bool routeRecordRequired; + quint16 nextHopAddress; + } RoutingTableListRecord; + + typedef struct RoutingTable{ + quint8 status; + quint8 tableSize; + quint8 startIndex; + QList records; + } RoutingTable; static NodeDescriptor parseNodeDescriptor(const QByteArray &payload); static MacCapabilities parseMacCapabilities(quint8 macCapabilitiesFlag); static ServerMask parseServerMask(quint16 serverMaskFlag); static DescriptorCapabilities parseDescriptorCapabilities(quint8 descriptorCapabilitiesFlag); static PowerDescriptor parsePowerDescriptor(quint16 powerDescriptorFlag); + static NeighborTable parseNeighborTable(const QByteArray &payload); + static RoutingTable parseRoutingTable(const QByteArray &payload); }; QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::Adpu &deviceAdpu); @@ -302,5 +349,7 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::ServerMask &serverMas QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::DescriptorCapabilities &descriptorCapabilities); QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::PowerDescriptor &powerDescriptor); QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecord &bindingTableListRecord); +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::NeighborTableListRecord &neighborTableListRecord); +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::RoutingTableListRecord &routingTableListRecord); #endif // ZIGBEEDEVICEPROFILE_H diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index f769487..679568b 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -53,7 +53,6 @@ ZigbeeNetwork::ZigbeeNetwork(const QUuid &networkUuid, QObject *parent) : m_reachableRefreshTimer = new QTimer(this); m_reachableRefreshTimer->setInterval(120000); - m_reachableRefreshTimer->setSingleShot(false); connect(m_reachableRefreshTimer, &QTimer::timeout, this, &ZigbeeNetwork::evaluateNodeReachableStates); connect(this, &ZigbeeNetwork::stateChanged, this, [this](ZigbeeNetwork::State state){ @@ -331,6 +330,16 @@ void ZigbeeNetwork::removeZigbeeNode(const ZigbeeAddress &address) }); } +void ZigbeeNetwork::refreshNeighborTable() +{ + foreach (ZigbeeNode *node, m_nodes) { + if (node->macCapabilities().receiverOnWhenIdle) { + m_refreshNeighborTableAddresses.append(node->extendedAddress()); + } + } + fetchNextNodeLqiTable(); +} + void ZigbeeNetwork::printNetwork() { qCDebug(dcZigbeeNetwork()) << this; @@ -454,30 +463,43 @@ ZigbeeNode *ZigbeeNetwork::createNode(quint16 shortAddress, const ZigbeeAddress return node; } -void ZigbeeNetwork::evaluateNextNodeReachableState() +void ZigbeeNetwork::fetchNextNodeLqiTable() { - if (m_reachableRefreshAddresses.isEmpty()) - return; - - ZigbeeNode *node = getZigbeeNode(m_reachableRefreshAddresses.takeFirst()); + ZigbeeNode *node = nullptr; + while (!node && !m_refreshNeighborTableAddresses.isEmpty()) { + node = getZigbeeNode(m_refreshNeighborTableAddresses.takeFirst()); + } + while (!node && !m_reachableRefreshAddresses.isEmpty()) { + node = getZigbeeNode(m_reachableRefreshAddresses.takeFirst()); + } if (!node) { - // Not does not exit any more...continue - evaluateNextNodeReachableState(); + // Nothing to do... return; } + qCDebug(dcZigbeeNetwork()) << "Refreshing LQI neighbor table for node" << node->shortAddress() << node->modelName(); + // Make a lqi request in order to check if the node is reachable - ZigbeeDeviceObjectReply *zdoReply = node->deviceObject()->requestNetworkAddress(); - connect(zdoReply, &ZigbeeDeviceObjectReply::finished, this, [=](){ - if (zdoReply->error()) { - qCWarning(dcZigbeeNetwork()) << node << "seems not to be reachable" << zdoReply->error(); + ZigbeeReply *reply = node->readLqiTableEntries(); + connect(reply, &ZigbeeReply::finished, this, [=](){ + if (reply->error()) { + qCWarning(dcZigbeeNetwork()) << node << "seems not to be reachable" << reply->error(); setNodeReachable(node, false); } else { setNodeReachable(node, true); } - // Give some time for other requests to be processed - QTimer::singleShot(5000, this, &ZigbeeNetwork::evaluateNextNodeReachableState); + ZigbeeReply *reply = node->readRoutingTableEntries(); + connect(reply, &ZigbeeReply::finished, this, [=]() { + + // While we still need to refresh neighbor tables, send the next request right away... + if (!m_refreshNeighborTableAddresses.isEmpty()) { + fetchNextNodeLqiTable(); + } else { + // ... else be easier on the resources for the cyclic refresh + QTimer::singleShot(5000, this, &ZigbeeNetwork::fetchNextNodeLqiTable); + } + }); }); } @@ -652,6 +674,7 @@ void ZigbeeNetwork::setState(ZigbeeNetwork::State state) if (state == StateRunning) { printNetwork(); + } emit stateChanged(m_state); } @@ -927,29 +950,35 @@ void ZigbeeNetwork::onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const void ZigbeeNetwork::evaluateNodeReachableStates() { - qCDebug(dcZigbeeNetwork()) << "Evaluate reachable state of nodes"; - m_reachableRefreshAddresses.clear(); + qCDebug(dcZigbeeNetwork()) << "Evaluating reachable state of nodes..."; foreach (ZigbeeNode *node, m_nodes) { - // Skip the coordinator - if (node->shortAddress() == 0x0000) + if (node->shortAddress() == 0x0000) { + // While we wouldn't need to check ourselves for being reachable, do it nevertheless so we keep the + // neighbor table in sync which is useful in logs + if (!m_reachableRefreshAddresses.contains(node->extendedAddress())) { + m_reachableRefreshAddresses.append(node->extendedAddress()); + } continue; + } - if (node->macCapabilities().receiverOnWhenIdle && node->shortAddress() != 0x0000) { + if (node->macCapabilities().receiverOnWhenIdle) { // Lets send a request to all things which are not reachable if (!node->reachable()) { - qCDebug(dcZigbeeNetwork()) << node << "enqueue evaluating reachable state"; - m_reachableRefreshAddresses.append(node->extendedAddress()); + if (!m_reachableRefreshAddresses.contains(node->extendedAddress())) { + qCDebug(dcZigbeeNetwork()) << node << "is not reachable. Scheduling LQI request."; + m_reachableRefreshAddresses.append(node->extendedAddress()); + } continue; } // Lets send a request to nodes which have not been seen more than 10 min - int msSinceLastSeen = node->lastSeen().msecsTo(QDateTime::currentDateTimeUtc()); + qulonglong msSinceLastSeen = node->lastSeen().msecsTo(QDateTime::currentDateTimeUtc()); qCDebug(dcZigbeeNetwork()) << node << "has been seen the last time" << QTime::fromMSecsSinceStartOfDay(msSinceLastSeen).toString() << "ago."; // 10 min = 10 * 60 * 1000 = 600000 ms - if (msSinceLastSeen > 600000) { - qCDebug(dcZigbeeNetwork()) << node << "enqueue evaluating reachable state"; + if (msSinceLastSeen > 600000 && !m_reachableRefreshAddresses.contains(node->extendedAddress())) { + qCDebug(dcZigbeeNetwork()) << node << "has not been seen in" << (msSinceLastSeen / 1000 / 60) << "minutes. Scheduling LQI request."; m_reachableRefreshAddresses.append(node->extendedAddress()); } } else { @@ -966,7 +995,7 @@ void ZigbeeNetwork::evaluateNodeReachableStates() } } - evaluateNextNodeReachableState(); + fetchNextNodeLqiTable(); } QDebug operator<<(QDebug debug, ZigbeeNetwork *network) diff --git a/libnymea-zigbee/zigbeenetwork.h b/libnymea-zigbee/zigbeenetwork.h index 98e6da4..da57113 100644 --- a/libnymea-zigbee/zigbeenetwork.h +++ b/libnymea-zigbee/zigbeenetwork.h @@ -132,6 +132,8 @@ public: void removeZigbeeNode(const ZigbeeAddress &address); + void refreshNeighborTable(); + private: QUuid m_networkUuid; State m_state = StateUninitialized; @@ -185,7 +187,8 @@ protected: QTimer *m_reachableRefreshTimer = nullptr; QList m_reachableRefreshAddresses; - void evaluateNextNodeReachableState(); + QList m_refreshNeighborTableAddresses; + void fetchNextNodeLqiTable(); void setPermitJoiningState(bool permitJoiningEnabled, quint8 duration = 0); diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 86be65f..6726921 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -147,6 +147,16 @@ QList ZigbeeNode::bindingTableRecor return m_bindingTableRecords; } +QList ZigbeeNode::neighborTableRecords() const +{ + return m_neighborTableRecords.values(); +} + +QList ZigbeeNode::routingTableRecords() const +{ + return m_routingTableRecords.values(); +} + void ZigbeeNode::setState(ZigbeeNode::State state) { if (m_state == state) @@ -264,6 +274,20 @@ ZigbeeReply *ZigbeeNode::readBindingTableEntries() return reply; } +ZigbeeReply *ZigbeeNode::readLqiTableEntries() +{ + ZigbeeReply *reply = new ZigbeeReply(this); + readNeighborTableChunk(reply, 0); + return reply; +} + +ZigbeeReply *ZigbeeNode::readRoutingTableEntries() +{ + ZigbeeReply *reply = new ZigbeeReply(this); + readRoutingTableChunk(reply, 0); + return reply; +} + void ZigbeeNode::initNodeDescriptor() { qCDebug(dcZigbeeNode()) << "Requesting node descriptor from" << this; @@ -510,6 +534,126 @@ void ZigbeeNode::removeNextBinding(ZigbeeReply *reply) }); } +void ZigbeeNode::readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex) +{ + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestMgmtLqi(startIndex); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to read neighbor table" << zdoReply->error(); + if (zdoReply->zigbeeDeviceObjectStatus() == ZigbeeDeviceProfile::StatusNotSupported) { + // Not an error, merely a valid response code that this optional request is not supported. + reply->finishReply(ZigbeeReply::ErrorNoError); + } else { + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + } + return; + } + + +// qCDebug(dcZigbeeNode()) << "LQI response:" << zdoReply->responseData().toHex(); + ZigbeeDeviceProfile::NeighborTable neighborTable = ZigbeeDeviceProfile::parseNeighborTable(zdoReply->responseData()); + if (neighborTable.startIndex == 0) { + m_neighborTable = neighborTable; + } else if (m_neighborTable.records.count() == neighborTable.startIndex) { + m_neighborTable.records.append(neighborTable.records); + } else { + qCWarning(dcZigbeeNode()) << "Error processing neighbor table. Indices not matching. Starting from scratch."; + readNeighborTableChunk(reply, 0); + return; + } + if (m_neighborTable.records.count() < m_neighborTable.tableSize) { + qCDebug(dcZigbeeNode) << "Neighbor table not complete yet. Fetching next chunk"; + readNeighborTableChunk(reply, m_neighborTable.records.count()); + return; + } + + QList removedRecords = m_neighborTableRecords.keys(); + foreach(const ZigbeeDeviceProfile::NeighborTableListRecord &record, m_neighborTable.records) { + qCDebug(dcZigbeeNode()).nospace() << "LQI neighbor for node: " << ZigbeeUtils::convertUint16ToHexString(m_shortAddress) << ": " << record; + removedRecords.removeAll(record.shortAddress); + if (m_neighborTableRecords.contains(record.shortAddress)) { + ZigbeeDeviceProfile::NeighborTableListRecord existingRecord = m_neighborTableRecords.value(record.shortAddress); + if (existingRecord.relationship != record.relationship + || existingRecord.permitJoining != record.permitJoining + || existingRecord.depth != record.depth + || existingRecord.lqi != record.lqi) { + m_neighborTableRecords[record.shortAddress] = record; + emit neighborTableRecordsChanged(); + } + } else { + m_neighborTableRecords.insert(record.shortAddress, record); + emit neighborTableRecordsChanged(); + } + } + foreach (quint16 removedRecord, removedRecords) { + m_neighborTableRecords.remove(removedRecord); + emit neighborTableRecordsChanged(); + } + reply->finishReply(ZigbeeReply::ErrorNoError); + }); +} + +void ZigbeeNode::readRoutingTableChunk(ZigbeeReply *reply, quint8 startIndex) +{ + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestMgmtRtg(startIndex); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to read routing table" << zdoReply->error(); + if (zdoReply->zigbeeDeviceObjectStatus() == ZigbeeDeviceProfile::StatusNotSupported) { + // Not an error, merely a valid response code that this optional request is not supported. + reply->finishReply(ZigbeeReply::ErrorNoError); + } else { + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + } + return; + } + +// qCDebug(dcZigbeeNetwork()) << "Routing table reply from" << ZigbeeUtils::convertUint16ToHexString(shortAddress()) << zdoReply->responseData().toHex(); + + ZigbeeDeviceProfile::RoutingTable routingTable = ZigbeeDeviceProfile::parseRoutingTable(zdoReply->responseData()); + if (routingTable.startIndex == 0) { + m_routingTable = routingTable; + } else if (m_routingTable.records.count() == routingTable.startIndex) { + m_routingTable.records.append(routingTable.records); + } else { + qCWarning(dcZigbeeNode()) << "Error processing routing table. Indices not matching. Starting from scratch."; + readRoutingTableChunk(reply, 0); + return; + } + if (m_routingTable.records.count() < m_routingTable.tableSize) { + qCDebug(dcZigbeeNode) << "Routing table not complete yet. Fetching next chunk"; + readRoutingTableChunk(reply, m_routingTable.records.count()); + return; + } + + QList removedRecords = m_routingTableRecords.keys(); + foreach(const ZigbeeDeviceProfile::RoutingTableListRecord &record, m_routingTable.records) { + qCDebug(dcZigbeeNode()).nospace() << "Route entry for node: " << ZigbeeUtils::convertUint16ToHexString(m_shortAddress) << ": " << record; + removedRecords.removeAll(record.destinationAddress); + if (m_routingTableRecords.contains(record.destinationAddress)) { + ZigbeeDeviceProfile::RoutingTableListRecord existingRecord = m_routingTableRecords.value(record.destinationAddress); + if (existingRecord.status != record.status + || existingRecord.memoryConstrained != record.memoryConstrained + || existingRecord.manyToOne != record.manyToOne + || existingRecord.routeRecordRequired != record.routeRecordRequired + || existingRecord.nextHopAddress != record.nextHopAddress) { + m_routingTableRecords[record.destinationAddress] = record; + emit routingTableRecordsChanged(); + } + } else { + m_routingTableRecords.insert(record.destinationAddress, record); + emit routingTableRecordsChanged(); + } + } + foreach (quint16 removedRecord, removedRecords) { + m_routingTableRecords.remove(removedRecord); + emit routingTableRecordsChanged(); + } + reply->finishReply(ZigbeeReply::ErrorNoError); + + }); +} + void ZigbeeNode::setupEndpointInternal(ZigbeeNodeEndpoint *endpoint) { // Connect after initialization for out of spec nodes diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index 5a6631f..e89bab7 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -92,12 +92,16 @@ public: // Only available if fetched QList bindingTableRecords() const; + QList neighborTableRecords() const; + QList routingTableRecords() const; // This method starts the node initialization phase (read descriptors and endpoints) void startInitialization(); ZigbeeReply *removeAllBindings(); ZigbeeReply *readBindingTableEntries(); + ZigbeeReply *readLqiTableEntries(); + ZigbeeReply *readRoutingTableEntries(); private: ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent = nullptr); @@ -126,6 +130,10 @@ private: bool m_powerDescriptorAvailable = false; QList m_bindingTableRecords; + QHash m_neighborTableRecords; + QHash m_routingTableRecords; + ZigbeeDeviceProfile::NeighborTable m_neighborTable; // Used internally to sync the table from the device in chunks + ZigbeeDeviceProfile::RoutingTable m_routingTable; // Used internally to sync the table from the device in chunks void setState(State state); void setReachable(bool reachable); @@ -140,6 +148,8 @@ private: void initEndpoint(quint8 endpointId); void removeNextBinding(ZigbeeReply *reply); + void readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex); + void readRoutingTableChunk(ZigbeeReply *reply, quint8 startIndex); void setupEndpointInternal(ZigbeeNodeEndpoint *endpoint); @@ -151,6 +161,7 @@ private: void handleDataIndication(const Zigbee::ApsdeDataIndication &indication); + signals: void nodeInitializationFailed(); void stateChanged(State state); @@ -162,6 +173,8 @@ signals: void versionChanged(const QString &version); void reachableChanged(bool reachable); void bindingTableRecordsChanged(); + void neighborTableRecordsChanged(); + void routingTableRecordsChanged(); void clusterAdded(ZigbeeCluster *cluster); void endpointClusterAttributeChanged(ZigbeeNodeEndpoint *endpoint, ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute);