Obtain LQI neighbor and routing tables from the network

This commit is contained in:
Michael Zanetti 2022-08-27 23:27:00 +02:00
parent a70afb965d
commit acfd73c271
8 changed files with 405 additions and 36 deletions

View File

@ -555,7 +555,7 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLeaveNetwork(bool rejoin
ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLqi(quint8 startIndex) 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 // Build APS request
ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtLqiRequest); ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtLqiRequest);
@ -597,7 +597,7 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLqi(quint8 startIndex)
ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtBind(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 // Build APS request
ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtBindRequest); ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtBindRequest);
@ -637,6 +637,49 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtBind(quint8 startIndex)
return zdoReply; 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<ZigbeeDeviceProfile::ZdoCommand>(networkReply->request().clusterId());
finishZdoReply(zdoReply);
return;
}
// We received the confirmation but not yet the indication
});
return zdoReply;
}
ZigbeeNetworkRequest ZigbeeDeviceObject::buildZdoRequest(quint16 zdoRequest) ZigbeeNetworkRequest ZigbeeDeviceObject::buildZdoRequest(quint16 zdoRequest)
{ {
ZigbeeNetworkRequest request; ZigbeeNetworkRequest request;

View File

@ -61,6 +61,7 @@ public:
ZigbeeDeviceObjectReply *requestMgmtLeaveNetwork(bool rejoin = false, bool removeChildren = false); ZigbeeDeviceObjectReply *requestMgmtLeaveNetwork(bool rejoin = false, bool removeChildren = false);
ZigbeeDeviceObjectReply *requestMgmtLqi(quint8 startIndex = 0x00); ZigbeeDeviceObjectReply *requestMgmtLqi(quint8 startIndex = 0x00);
ZigbeeDeviceObjectReply *requestMgmtBind(quint8 startIndex = 0x00); ZigbeeDeviceObjectReply *requestMgmtBind(quint8 startIndex = 0x00);
ZigbeeDeviceObjectReply *requestMgmtRtg(quint8 startIndex = 0x00);
// TODO: write all requests // TODO: write all requests

View File

@ -174,6 +174,67 @@ ZigbeeDeviceProfile::PowerDescriptor ZigbeeDeviceProfile::parsePowerDescriptor(q
return powerDescriptor; 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<NodeType>(nodeFlags & 0x03);
record.receiverOnWhenIdle = static_cast<bool>((nodeFlags & 0x0c) >> 2);
record.relationship = static_cast<Relationship>((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<RouteStatus>(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) ZigbeeDeviceProfile::Adpu ZigbeeDeviceProfile::parseAdpu(const QByteArray &adpu)
{ {
QDataStream stream(adpu); QDataStream stream(adpu);
@ -187,12 +248,12 @@ ZigbeeDeviceProfile::Adpu ZigbeeDeviceProfile::parseAdpu(const QByteArray &adpu)
return deviceAdpu; 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() << "DeviceProfileAdpu(SQN: " << adpu.transactionSequenceNumber << ", ";
debug.nospace() << deviceAdpu.status << ", "; debug.nospace() << adpu.status << ", ";
debug.nospace() << ZigbeeUtils::convertUint16ToHexString(deviceAdpu.addressOfInterest) << ", "; debug.nospace() << ZigbeeUtils::convertUint16ToHexString(adpu.addressOfInterest) << ", ";
debug.nospace() << "Payload: " << ZigbeeUtils::convertByteArrayToHexString(deviceAdpu.payload) << ")"; debug.nospace() << "Payload: " << ZigbeeUtils::convertByteArrayToHexString(adpu.payload) << ")";
return debug.space(); return debug.space();
} }
@ -273,3 +334,29 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecor
} }
return debug; 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;
}

View File

@ -198,12 +198,23 @@ public:
Q_ENUM(DeviceType) Q_ENUM(DeviceType)
enum Relationship { enum Relationship {
Parent, RelationshipParent,
Child, RelationshipChild,
Sibling RelationshipSibling,
RelationshipNone,
RelationshipPreviousChild
}; };
Q_ENUM(Relationship) Q_ENUM(Relationship)
enum RouteStatus {
RouteStatusActive,
RouteStatusDiscoveryUnderway,
RouteStatusDiscoveryFailed,
RouteStatusInactive,
RouteStatusValidationUnderway
};
Q_ENUM(RouteStatus)
enum PowerMode { enum PowerMode {
PowerModeAlwaysOn, PowerModeAlwaysOn,
PowerModeOnPeriodically, PowerModeOnPeriodically,
@ -287,12 +298,48 @@ public:
quint8 destinationEndpoint; // Only for destination address 0x03 quint8 destinationEndpoint; // Only for destination address 0x03
} BindingTableListRecord; } 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<NeighborTableListRecord> 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<RoutingTableListRecord> records;
} RoutingTable;
static NodeDescriptor parseNodeDescriptor(const QByteArray &payload); static NodeDescriptor parseNodeDescriptor(const QByteArray &payload);
static MacCapabilities parseMacCapabilities(quint8 macCapabilitiesFlag); static MacCapabilities parseMacCapabilities(quint8 macCapabilitiesFlag);
static ServerMask parseServerMask(quint16 serverMaskFlag); static ServerMask parseServerMask(quint16 serverMaskFlag);
static DescriptorCapabilities parseDescriptorCapabilities(quint8 descriptorCapabilitiesFlag); static DescriptorCapabilities parseDescriptorCapabilities(quint8 descriptorCapabilitiesFlag);
static PowerDescriptor parsePowerDescriptor(quint16 powerDescriptorFlag); 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); 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::DescriptorCapabilities &descriptorCapabilities);
QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::PowerDescriptor &powerDescriptor); QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::PowerDescriptor &powerDescriptor);
QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecord &bindingTableListRecord); 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 #endif // ZIGBEEDEVICEPROFILE_H

View File

@ -53,7 +53,6 @@ ZigbeeNetwork::ZigbeeNetwork(const QUuid &networkUuid, QObject *parent) :
m_reachableRefreshTimer = new QTimer(this); m_reachableRefreshTimer = new QTimer(this);
m_reachableRefreshTimer->setInterval(120000); m_reachableRefreshTimer->setInterval(120000);
m_reachableRefreshTimer->setSingleShot(false);
connect(m_reachableRefreshTimer, &QTimer::timeout, this, &ZigbeeNetwork::evaluateNodeReachableStates); connect(m_reachableRefreshTimer, &QTimer::timeout, this, &ZigbeeNetwork::evaluateNodeReachableStates);
connect(this, &ZigbeeNetwork::stateChanged, this, [this](ZigbeeNetwork::State state){ 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() void ZigbeeNetwork::printNetwork()
{ {
qCDebug(dcZigbeeNetwork()) << this; qCDebug(dcZigbeeNetwork()) << this;
@ -454,30 +463,43 @@ ZigbeeNode *ZigbeeNetwork::createNode(quint16 shortAddress, const ZigbeeAddress
return node; return node;
} }
void ZigbeeNetwork::evaluateNextNodeReachableState() void ZigbeeNetwork::fetchNextNodeLqiTable()
{ {
if (m_reachableRefreshAddresses.isEmpty()) ZigbeeNode *node = nullptr;
return; while (!node && !m_refreshNeighborTableAddresses.isEmpty()) {
node = getZigbeeNode(m_refreshNeighborTableAddresses.takeFirst());
ZigbeeNode *node = getZigbeeNode(m_reachableRefreshAddresses.takeFirst()); }
while (!node && !m_reachableRefreshAddresses.isEmpty()) {
node = getZigbeeNode(m_reachableRefreshAddresses.takeFirst());
}
if (!node) { if (!node) {
// Not does not exit any more...continue // Nothing to do...
evaluateNextNodeReachableState();
return; 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 // Make a lqi request in order to check if the node is reachable
ZigbeeDeviceObjectReply *zdoReply = node->deviceObject()->requestNetworkAddress(); ZigbeeReply *reply = node->readLqiTableEntries();
connect(zdoReply, &ZigbeeDeviceObjectReply::finished, this, [=](){ connect(reply, &ZigbeeReply::finished, this, [=](){
if (zdoReply->error()) { if (reply->error()) {
qCWarning(dcZigbeeNetwork()) << node << "seems not to be reachable" << zdoReply->error(); qCWarning(dcZigbeeNetwork()) << node << "seems not to be reachable" << reply->error();
setNodeReachable(node, false); setNodeReachable(node, false);
} else { } else {
setNodeReachable(node, true); setNodeReachable(node, true);
} }
// Give some time for other requests to be processed ZigbeeReply *reply = node->readRoutingTableEntries();
QTimer::singleShot(5000, this, &ZigbeeNetwork::evaluateNextNodeReachableState); 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) { if (state == StateRunning) {
printNetwork(); printNetwork();
} }
emit stateChanged(m_state); emit stateChanged(m_state);
} }
@ -927,29 +950,35 @@ void ZigbeeNetwork::onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const
void ZigbeeNetwork::evaluateNodeReachableStates() void ZigbeeNetwork::evaluateNodeReachableStates()
{ {
qCDebug(dcZigbeeNetwork()) << "Evaluate reachable state of nodes"; qCDebug(dcZigbeeNetwork()) << "Evaluating reachable state of nodes...";
m_reachableRefreshAddresses.clear();
foreach (ZigbeeNode *node, m_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; continue;
}
if (node->macCapabilities().receiverOnWhenIdle && node->shortAddress() != 0x0000) { if (node->macCapabilities().receiverOnWhenIdle) {
// Lets send a request to all things which are not reachable // Lets send a request to all things which are not reachable
if (!node->reachable()) { if (!node->reachable()) {
qCDebug(dcZigbeeNetwork()) << node << "enqueue evaluating reachable state"; if (!m_reachableRefreshAddresses.contains(node->extendedAddress())) {
m_reachableRefreshAddresses.append(node->extendedAddress()); qCDebug(dcZigbeeNetwork()) << node << "is not reachable. Scheduling LQI request.";
m_reachableRefreshAddresses.append(node->extendedAddress());
}
continue; continue;
} }
// Lets send a request to nodes which have not been seen more than 10 min // 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."; qCDebug(dcZigbeeNetwork()) << node << "has been seen the last time" << QTime::fromMSecsSinceStartOfDay(msSinceLastSeen).toString() << "ago.";
// 10 min = 10 * 60 * 1000 = 600000 ms // 10 min = 10 * 60 * 1000 = 600000 ms
if (msSinceLastSeen > 600000) { if (msSinceLastSeen > 600000 && !m_reachableRefreshAddresses.contains(node->extendedAddress())) {
qCDebug(dcZigbeeNetwork()) << node << "enqueue evaluating reachable state"; qCDebug(dcZigbeeNetwork()) << node << "has not been seen in" << (msSinceLastSeen / 1000 / 60) << "minutes. Scheduling LQI request.";
m_reachableRefreshAddresses.append(node->extendedAddress()); m_reachableRefreshAddresses.append(node->extendedAddress());
} }
} else { } else {
@ -966,7 +995,7 @@ void ZigbeeNetwork::evaluateNodeReachableStates()
} }
} }
evaluateNextNodeReachableState(); fetchNextNodeLqiTable();
} }
QDebug operator<<(QDebug debug, ZigbeeNetwork *network) QDebug operator<<(QDebug debug, ZigbeeNetwork *network)

View File

@ -132,6 +132,8 @@ public:
void removeZigbeeNode(const ZigbeeAddress &address); void removeZigbeeNode(const ZigbeeAddress &address);
void refreshNeighborTable();
private: private:
QUuid m_networkUuid; QUuid m_networkUuid;
State m_state = StateUninitialized; State m_state = StateUninitialized;
@ -185,7 +187,8 @@ protected:
QTimer *m_reachableRefreshTimer = nullptr; QTimer *m_reachableRefreshTimer = nullptr;
QList<ZigbeeAddress> m_reachableRefreshAddresses; QList<ZigbeeAddress> m_reachableRefreshAddresses;
void evaluateNextNodeReachableState(); QList<ZigbeeAddress> m_refreshNeighborTableAddresses;
void fetchNextNodeLqiTable();
void setPermitJoiningState(bool permitJoiningEnabled, quint8 duration = 0); void setPermitJoiningState(bool permitJoiningEnabled, quint8 duration = 0);

View File

@ -147,6 +147,16 @@ QList<ZigbeeDeviceProfile::BindingTableListRecord> ZigbeeNode::bindingTableRecor
return m_bindingTableRecords; return m_bindingTableRecords;
} }
QList<ZigbeeDeviceProfile::NeighborTableListRecord> ZigbeeNode::neighborTableRecords() const
{
return m_neighborTableRecords.values();
}
QList<ZigbeeDeviceProfile::RoutingTableListRecord> ZigbeeNode::routingTableRecords() const
{
return m_routingTableRecords.values();
}
void ZigbeeNode::setState(ZigbeeNode::State state) void ZigbeeNode::setState(ZigbeeNode::State state)
{ {
if (m_state == state) if (m_state == state)
@ -264,6 +274,20 @@ ZigbeeReply *ZigbeeNode::readBindingTableEntries()
return reply; 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() void ZigbeeNode::initNodeDescriptor()
{ {
qCDebug(dcZigbeeNode()) << "Requesting node descriptor from" << this; 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<quint16> 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<quint16> 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) void ZigbeeNode::setupEndpointInternal(ZigbeeNodeEndpoint *endpoint)
{ {
// Connect after initialization for out of spec nodes // Connect after initialization for out of spec nodes

View File

@ -92,12 +92,16 @@ public:
// Only available if fetched // Only available if fetched
QList<ZigbeeDeviceProfile::BindingTableListRecord> bindingTableRecords() const; QList<ZigbeeDeviceProfile::BindingTableListRecord> bindingTableRecords() const;
QList<ZigbeeDeviceProfile::NeighborTableListRecord> neighborTableRecords() const;
QList<ZigbeeDeviceProfile::RoutingTableListRecord> routingTableRecords() const;
// This method starts the node initialization phase (read descriptors and endpoints) // This method starts the node initialization phase (read descriptors and endpoints)
void startInitialization(); void startInitialization();
ZigbeeReply *removeAllBindings(); ZigbeeReply *removeAllBindings();
ZigbeeReply *readBindingTableEntries(); ZigbeeReply *readBindingTableEntries();
ZigbeeReply *readLqiTableEntries();
ZigbeeReply *readRoutingTableEntries();
private: private:
ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent = nullptr); ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent = nullptr);
@ -126,6 +130,10 @@ private:
bool m_powerDescriptorAvailable = false; bool m_powerDescriptorAvailable = false;
QList<ZigbeeDeviceProfile::BindingTableListRecord> m_bindingTableRecords; QList<ZigbeeDeviceProfile::BindingTableListRecord> m_bindingTableRecords;
QHash<quint16, ZigbeeDeviceProfile::NeighborTableListRecord> m_neighborTableRecords;
QHash<quint16, ZigbeeDeviceProfile::RoutingTableListRecord> 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 setState(State state);
void setReachable(bool reachable); void setReachable(bool reachable);
@ -140,6 +148,8 @@ private:
void initEndpoint(quint8 endpointId); void initEndpoint(quint8 endpointId);
void removeNextBinding(ZigbeeReply *reply); void removeNextBinding(ZigbeeReply *reply);
void readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex);
void readRoutingTableChunk(ZigbeeReply *reply, quint8 startIndex);
void setupEndpointInternal(ZigbeeNodeEndpoint *endpoint); void setupEndpointInternal(ZigbeeNodeEndpoint *endpoint);
@ -151,6 +161,7 @@ private:
void handleDataIndication(const Zigbee::ApsdeDataIndication &indication); void handleDataIndication(const Zigbee::ApsdeDataIndication &indication);
signals: signals:
void nodeInitializationFailed(); void nodeInitializationFailed();
void stateChanged(State state); void stateChanged(State state);
@ -162,6 +173,8 @@ signals:
void versionChanged(const QString &version); void versionChanged(const QString &version);
void reachableChanged(bool reachable); void reachableChanged(bool reachable);
void bindingTableRecordsChanged(); void bindingTableRecordsChanged();
void neighborTableRecordsChanged();
void routingTableRecordsChanged();
void clusterAdded(ZigbeeCluster *cluster); void clusterAdded(ZigbeeCluster *cluster);
void endpointClusterAttributeChanged(ZigbeeNodeEndpoint *endpoint, ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute); void endpointClusterAttributeChanged(ZigbeeNodeEndpoint *endpoint, ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute);