diff --git a/libnymea-zigbee/zcl/zigbeeclusterlibrary.h b/libnymea-zigbee/zcl/zigbeeclusterlibrary.h index d2c2c38..b21e384 100644 --- a/libnymea-zigbee/zcl/zigbeeclusterlibrary.h +++ b/libnymea-zigbee/zcl/zigbeeclusterlibrary.h @@ -147,7 +147,7 @@ public: // Heating, Ventilation and Air-Conditioning (HVAC) ClusterIdPumpConfigurationControl = 0x0200, ClusterIdThermostat = 0x0201, - ClusterIdFanControll = 0x0202, + ClusterIdFanControl = 0x0202, ClusterIdDehumiditationControl = 0x0203, ClusterIdThermostatUserControl = 0x0204, diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp index c878271..f7a2cc1 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp @@ -427,9 +427,9 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestUnbind(const ZigbeeDevicePro stream << bindingRecord.clusterId; stream << static_cast(bindingRecord.destinationAddressMode); if (bindingRecord.destinationAddressMode == Zigbee::DestinationAddressModeGroup) { - stream << bindingRecord.destinationAddressShort; + stream << bindingRecord.destinationShortAddress; } else { - stream << bindingRecord.destinationAddress.toUInt64(); + stream << bindingRecord.destinationIeeeAddress.toUInt64(); stream << bindingRecord.destinationEndpoint; } diff --git a/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp b/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp index 1362446..f1723d8 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp @@ -174,14 +174,53 @@ ZigbeeDeviceProfile::PowerDescriptor ZigbeeDeviceProfile::parsePowerDescriptor(q return powerDescriptor; } +ZigbeeDeviceProfile::BindingTable ZigbeeDeviceProfile::parseBindingTable(const QByteArray &payload) +{ + ZigbeeDeviceProfile::BindingTable table; + + QDataStream stream(payload); + stream.setByteOrder(QDataStream::LittleEndian); + + quint8 sqn, status, listRecordCount; + stream >> sqn >> status >> table.tableSize >> table.startIndex >> listRecordCount; + + table.status = static_cast(status); + + for (int i = 0; i < listRecordCount; i++) { + ZigbeeDeviceProfile::BindingTableListRecord record; + + quint64 sourceAddress; + stream >> sourceAddress; + record.sourceAddress = ZigbeeAddress(sourceAddress); + + stream >> record.sourceEndpoint; + stream >> record.clusterId; + quint8 addressMode; + stream >> addressMode; + record.destinationAddressMode = static_cast(addressMode); + + if (addressMode == Zigbee::DestinationAddressModeGroup) { + stream >> record.destinationShortAddress; + } else if (addressMode == Zigbee::DestinationAddressModeIeeeAddress) { + quint64 destinationAddressIeee; + stream >> destinationAddressIeee >> record.destinationEndpoint; + record.destinationIeeeAddress = ZigbeeAddress(destinationAddressIeee); + } + table.records.append(record); + } + + return table; +} + 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; + quint8 messageId, status, listRecordCount; + stream >> messageId >> status >> table.tableSize >> table.startIndex >> listRecordCount; + table.status = static_cast(status); for (int i = 0; i < listRecordCount; i++) { ZigbeeDeviceProfile::NeighborTableListRecord record; stream >> record.extendedPanId; @@ -213,8 +252,9 @@ ZigbeeDeviceProfile::RoutingTable ZigbeeDeviceProfile::parseRoutingTable(const Q QDataStream stream(payload); stream.setByteOrder(QDataStream::LittleEndian); - quint8 messageId; quint8 listRecordCount; - stream >> messageId >> table.status >> table.tableSize >> table.startIndex >> listRecordCount; + quint8 messageId, status, listRecordCount; + stream >> messageId >> status >> table.tableSize >> table.startIndex >> listRecordCount; + table.status = static_cast(status); for (int i = 0; i < listRecordCount; i++) { ZigbeeDeviceProfile::RoutingTableListRecord record; stream >> record.destinationAddress; @@ -323,10 +363,10 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecor debug.nospace() << "cluster: " << static_cast(bindingTableListRecord.clusterId) << " --> "; switch (bindingTableListRecord.destinationAddressMode) { case Zigbee::DestinationAddressModeGroup: - debug.nospace() << "destination address (group): " << ZigbeeUtils::convertUint16ToHexString(bindingTableListRecord.destinationAddressShort) << ") "; + debug.nospace() << "destination address (group): " << ZigbeeUtils::convertUint16ToHexString(bindingTableListRecord.destinationShortAddress) << ") "; break; case Zigbee::DestinationAddressModeIeeeAddress: - debug.nospace() << "destination address (unicast): " << bindingTableListRecord.destinationAddress.toString() << ", "; + debug.nospace() << "destination address (unicast): " << bindingTableListRecord.destinationIeeeAddress.toString() << ", "; debug.nospace() << "destination endpoint: " << bindingTableListRecord.destinationEndpoint << ") "; break; default: diff --git a/libnymea-zigbee/zdo/zigbeedeviceprofile.h b/libnymea-zigbee/zdo/zigbeedeviceprofile.h index f311463..272df3d 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceprofile.h +++ b/libnymea-zigbee/zdo/zigbeedeviceprofile.h @@ -292,12 +292,19 @@ public: ZigbeeAddress sourceAddress; quint8 sourceEndpoint; quint16 clusterId; - Zigbee::DestinationAddressMode destinationAddressMode; // Note: group or unicast - quint16 destinationAddressShort; // Only for destination address 0x01 - ZigbeeAddress destinationAddress; // Only for destination address 0x03 - quint8 destinationEndpoint; // Only for destination address 0x03 + Zigbee::DestinationAddressMode destinationAddressMode; + quint16 destinationShortAddress; + ZigbeeAddress destinationIeeeAddress; + quint8 destinationEndpoint; } BindingTableListRecord; + typedef struct BindingTable { + Status status; + quint8 tableSize; + quint8 startIndex; + QList records; + } BindingTable; + typedef struct NeighborTableListRecord { quint64 extendedPanId; ZigbeeAddress ieeeAddress; @@ -310,8 +317,8 @@ public: quint8 lqi; } NeighborTableListRecord; - typedef struct NeighborTable{ - quint8 status; + typedef struct NeighborTable { + Status status; quint8 tableSize; quint8 startIndex; QList records; @@ -327,7 +334,7 @@ public: } RoutingTableListRecord; typedef struct RoutingTable{ - quint8 status; + Status status; quint8 tableSize; quint8 startIndex; QList records; @@ -338,6 +345,7 @@ public: static ServerMask parseServerMask(quint16 serverMaskFlag); static DescriptorCapabilities parseDescriptorCapabilities(quint8 descriptorCapabilitiesFlag); static PowerDescriptor parsePowerDescriptor(quint16 powerDescriptorFlag); + static BindingTable parseBindingTable(const QByteArray &payload); static NeighborTable parseNeighborTable(const QByteArray &payload); static RoutingTable parseRoutingTable(const QByteArray &payload); }; diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 679568b..6ba8d3f 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -330,7 +330,7 @@ void ZigbeeNetwork::removeZigbeeNode(const ZigbeeAddress &address) }); } -void ZigbeeNetwork::refreshNeighborTable() +void ZigbeeNetwork::refreshNeighborTables() { foreach (ZigbeeNode *node, m_nodes) { if (node->macCapabilities().receiverOnWhenIdle) { @@ -415,6 +415,10 @@ void ZigbeeNetwork::addNodeInternally(ZigbeeNode *node) } }); + connect(node, &ZigbeeNode::bindingTableRecordsChanged, this, [this, node](){ + m_database->updateNodeBindingTable(node); + }); + // Note: if a cluster shows up after initialization (out of spec devices), save the cluster and it's attributes foreach (ZigbeeNodeEndpoint *endpoint, node->endpoints()) { connect(endpoint, &ZigbeeNodeEndpoint::clusterAttributeChanged, this, &ZigbeeNetwork::onNodeClusterAttributeChanged); diff --git a/libnymea-zigbee/zigbeenetwork.h b/libnymea-zigbee/zigbeenetwork.h index da57113..bdad1e7 100644 --- a/libnymea-zigbee/zigbeenetwork.h +++ b/libnymea-zigbee/zigbeenetwork.h @@ -132,7 +132,7 @@ public: void removeZigbeeNode(const ZigbeeAddress &address); - void refreshNeighborTable(); + void refreshNeighborTables(); private: QUuid m_networkUuid; diff --git a/libnymea-zigbee/zigbeenetworkdatabase.cpp b/libnymea-zigbee/zigbeenetworkdatabase.cpp index 6e5861a..608f0c3 100644 --- a/libnymea-zigbee/zigbeenetworkdatabase.cpp +++ b/libnymea-zigbee/zigbeenetworkdatabase.cpp @@ -180,6 +180,23 @@ QList ZigbeeNetworkDatabase::loadNodes() node->m_endpoints.append(endpoint); node->setupEndpointInternal(endpoint); } + + QSqlQuery bindingsQuery(m_db); + bindingsQuery.prepare("SELECT * FROM bindings WHERE sourceAddress = ?;"); + bindingsQuery.addBindValue(node->extendedAddress().toString()); + bindingsQuery.exec(); + while (bindingsQuery.next()) { + ZigbeeDeviceProfile::BindingTableListRecord record; + record.sourceAddress = ZigbeeAddress(bindingsQuery.value("sourceAddress").toString()); + record.sourceEndpoint = bindingsQuery.value("sourceEndpointId").toUInt(); + record.clusterId = bindingsQuery.value("clusterId").toUInt(); + record.destinationAddressMode = static_cast(bindingsQuery.value("destinationAddressMode").toUInt()); + record.destinationShortAddress = bindingsQuery.value("destinationShortAddress").toUInt(); + record.destinationIeeeAddress = ZigbeeAddress(bindingsQuery.value("destinationIeeeAddress").toString()); + record.destinationEndpoint = bindingsQuery.value("destinationEndpointId").toUInt(); + node->m_bindingTableRecords.append(record); + } + nodes.append(node); } @@ -287,6 +304,18 @@ bool ZigbeeNetworkDatabase::initDatabase() createIndices("attributesIndex", "attributes", "clusterId, attributeId"); } + if (!m_db.tables().contains("bindings")) { + createTable("bindings", "(sourceAddress TEXT NOT NULL, " + "sourceEndpointId INTEGER NOT NULL, " + "clusterId INTEGER NOT NULL, " + "destinationAddressMode INTEGER NOT NULL, " + "destinationShortAddress INTEGER, " + "destinationIeeeAddress TEXT, " + "destinationEndpointId INTEGER, " + "CONSTRAINT fk FOREIGN KEY(sourceAddress) REFERENCES nodes(ieeeAddress) ON DELETE CASCADE" + ")"); + } + return true; } @@ -436,6 +465,8 @@ bool ZigbeeNetworkDatabase::saveNode(ZigbeeNode *node) } } + updateNodeBindingTable(node); + return true; } @@ -479,6 +510,37 @@ bool ZigbeeNetworkDatabase::updateNodeLastSeen(ZigbeeNode *node, const QDateTime return true; } +bool ZigbeeNetworkDatabase::updateNodeBindingTable(ZigbeeNode *node) +{ + bool error = false; + QSqlQuery query(m_db); + query.prepare("DELETE FROM bindings WHERE sourceAddress = ?"); + query.addBindValue(node->extendedAddress().toString()); + query.exec(); + if (query.lastError().type() != QSqlError::NoError) { + qCWarning(dcZigbeeNetworkDatabase()) << "Error clearing old binding table:" << query.executedQuery() << query.lastError().databaseText() << query.lastError().driverText(); + error = true; + } + + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &record, node->bindingTableRecords()) { + QSqlQuery insertQuery(m_db); + insertQuery.prepare("INSERT INTO bindings (sourceAddress, sourceEndpointId, clusterId, destinationAddressMode, destinationShortAddress, destinationIeeeAddress, destinationEndpointId) VALUES(?, ?, ?, ?, ?, ?, ?)"); + insertQuery.addBindValue(record.sourceAddress.toString()); + insertQuery.addBindValue(record.sourceEndpoint); + insertQuery.addBindValue(record.clusterId); + insertQuery.addBindValue(record.destinationAddressMode); + insertQuery.addBindValue(record.destinationShortAddress); + insertQuery.addBindValue(record.destinationIeeeAddress.toString()); + insertQuery.addBindValue(record.destinationEndpoint); + insertQuery.exec(); + if (insertQuery.lastError().type() != QSqlError::NoError) { + qCWarning(dcZigbeeNetworkDatabase()) << "Error inserting into binding table:" << query.executedQuery() << query.lastError().databaseText() << query.lastError().driverText(); + error = true; + } + } + return error; +} + bool ZigbeeNetworkDatabase::removeNode(ZigbeeNode *node) { qCDebug(dcZigbeeNetworkDatabase()) << "Remove" << node; diff --git a/libnymea-zigbee/zigbeenetworkdatabase.h b/libnymea-zigbee/zigbeenetworkdatabase.h index 2874708..1d0aba0 100644 --- a/libnymea-zigbee/zigbeenetworkdatabase.h +++ b/libnymea-zigbee/zigbeenetworkdatabase.h @@ -73,6 +73,7 @@ public slots: bool updateNodeLqi(ZigbeeNode *node, quint8 lqi); bool updateNodeNetworkAddress(ZigbeeNode *node, quint16 networkAddress); bool updateNodeLastSeen(ZigbeeNode *node, const QDateTime &lastSeen); + bool updateNodeBindingTable(ZigbeeNode *node); bool removeNode(ZigbeeNode *node); }; diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 6726921..72aa17b 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -194,6 +194,7 @@ void ZigbeeNode::startInitialization() * - Simple descriptor request * - for each endpoint * - read basic cluster + * - Read binding table */ initNodeDescriptor(); @@ -225,52 +226,7 @@ ZigbeeReply *ZigbeeNode::removeAllBindings() 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; - stream >> sqn >> statusInt >> entriesCount >> startIndex >> bindingTableListCount; - ZigbeeDeviceProfile::Status status = static_cast(statusInt); - qCDebug(dcZigbeeDeviceObject()) << "SQN:" << sqn << status << "entries:" << entriesCount << "index:" << startIndex << "list count:" << bindingTableListCount; - - m_bindingTableRecords.clear(); - for (int i = 0; i < bindingTableListCount; i++) { - quint64 sourceAddress; quint8 addressMode; - ZigbeeDeviceProfile::BindingTableListRecord record; - stream >> sourceAddress; - record.sourceAddress = ZigbeeAddress(sourceAddress); - - stream >> record.sourceEndpoint >> record.clusterId >> addressMode; - record.destinationAddressMode = static_cast(addressMode); - - if (addressMode == Zigbee::DestinationAddressModeGroup) { - stream >> record.destinationAddressShort; - } else if (addressMode == Zigbee::DestinationAddressModeIeeeAddress) { - quint64 destinationAddressIeee; - stream >> destinationAddressIeee >> record.destinationEndpoint; - record.destinationAddress = ZigbeeAddress(destinationAddressIeee); - } else { - qCWarning(dcZigbeeDeviceObject()) << "Invalid destination address mode in binding table record."; - break; - } - qCDebug(dcZigbeeDeviceObject()) << record; - m_bindingTableRecords << record; - } - - // TODO: continue reading if there are more entries - - emit bindingTableRecordsChanged(); - reply->finishReply(); - }); + readBindingTableChunk(reply, 0); return reply; } @@ -288,6 +244,63 @@ ZigbeeReply *ZigbeeNode::readRoutingTableEntries() return reply; } +ZigbeeReply *ZigbeeNode::addBinding(quint8 sourceEndpointId, quint16 clusterId, const ZigbeeAddress &destinationAddress, quint8 destinationEndpoint) +{ + ZigbeeReply *reply = new ZigbeeReply(this); + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestBindIeeeAddress(sourceEndpointId, clusterId, destinationAddress, destinationEndpoint); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [this, zdoReply, reply](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to configure binding on node" << this; + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + return; + } + qCDebug(dcZigbeeNode) << "Binding added"; + + reply->finishReply(); + + readBindingTableEntries(); + }); + return reply; +} + +ZigbeeReply *ZigbeeNode::addBinding(quint8 sourceEndpointId, quint16 clusterId, quint16 destinationGroupAddress) +{ + ZigbeeReply *reply = new ZigbeeReply(this); + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestBindGroupAddress(sourceEndpointId, clusterId, destinationGroupAddress); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [this, zdoReply, reply](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to configure binding on node" << this; + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + return; + } + qCDebug(dcZigbeeNode) << "Binding added"; + + reply->finishReply(); + + readBindingTableEntries(); + }); + return reply; +} + +ZigbeeReply *ZigbeeNode::removeBinding(const ZigbeeDeviceProfile::BindingTableListRecord &binding) +{ + ZigbeeReply *reply = new ZigbeeReply(this); + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestUnbind(binding); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [this, zdoReply, reply](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to rebinding binding on node" << this; + reply->finishReply(ZigbeeReply::ErrorZigbeeError); + return; + } + qCDebug(dcZigbeeNode) << "Binding removed"; + + reply->finishReply(); + + readBindingTableEntries(); + }); + return reply; +} + void ZigbeeNode::initNodeDescriptor() { qCDebug(dcZigbeeNode()) << "Requesting node descriptor from" << this; @@ -534,6 +547,90 @@ void ZigbeeNode::removeNextBinding(ZigbeeReply *reply) }); } +void ZigbeeNode::readBindingTableChunk(ZigbeeReply *reply, quint8 startIndex) +{ + ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestMgmtBind(startIndex); + connect(zdoReply, &ZigbeeDeviceObjectReply::finished, reply, [=](){ + if (zdoReply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Failed to read binding 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()) << "Binding table reply from" << ZigbeeUtils::convertUint16ToHexString(shortAddress()) << zdoReply->responseData().toHex(); + + ZigbeeDeviceProfile::BindingTable bindingTable = ZigbeeDeviceProfile::parseBindingTable(zdoReply->responseData()); + if (bindingTable.startIndex == 0) { + m_bindingTable = bindingTable; + } else if (m_bindingTable.records.count() == bindingTable.startIndex) { + m_bindingTable.records.append(bindingTable.records); + } else { + qCWarning(dcZigbeeNode()) << "Error reading binding table. Indices not matching. Starting from scratch."; + readBindingTableChunk(reply, 0); + return; + } + if (m_bindingTable.records.count() < m_bindingTable.tableSize) { + qCDebug(dcZigbeeNode) << "Binding table not complete yet (" << m_bindingTable.records.count() << "/" << m_bindingTable.tableSize << "). Fetching next chunk..."; + readBindingTableChunk(reply, m_bindingTable.records.count()); + return; + } + + bool changed = false; + QMutableListIterator it(m_bindingTableRecords); + while (it.hasNext()) { + ZigbeeDeviceProfile::BindingTableListRecord oldRecord = it.next(); + bool found = false; + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &record, m_bindingTable.records) { + if (record.sourceAddress == oldRecord.sourceAddress + && record.sourceEndpoint == oldRecord.sourceEndpoint + && record.clusterId == oldRecord.clusterId + && record.destinationAddressMode == oldRecord.destinationAddressMode + && record.destinationShortAddress == oldRecord.destinationShortAddress + && record.destinationIeeeAddress == oldRecord.destinationIeeeAddress + && record.destinationEndpoint == oldRecord.destinationEndpoint) { + found = true; + break; + } + } + if (!found) { + it.remove(); + changed = true; + } + } + + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &record, m_bindingTable.records) { + qCDebug(dcZigbeeNode()) << "Binding table record fetched" << record; + bool found = false; + foreach (const ZigbeeDeviceProfile::BindingTableListRecord &oldRecord, m_bindingTableRecords) { + if (record.sourceAddress == oldRecord.sourceAddress + && record.sourceEndpoint == oldRecord.sourceEndpoint + && record.clusterId == oldRecord.clusterId + && record.destinationAddressMode == oldRecord.destinationAddressMode + && record.destinationShortAddress == oldRecord.destinationShortAddress + && record.destinationIeeeAddress == oldRecord.destinationIeeeAddress + && record.destinationEndpoint == oldRecord.destinationEndpoint) { + found = true; + break; + } + } + if (!found) { + qCDebug(dcZigbeeNode()) << "New binding table record:" << record; + m_bindingTableRecords.append(record); + changed = true; + } + } + if (changed) { + emit bindingTableRecordsChanged(); + } + reply->finishReply(ZigbeeReply::ErrorNoError); + }); +} + void ZigbeeNode::readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex) { ZigbeeDeviceObjectReply *zdoReply = deviceObject()->requestMgmtLqi(startIndex); @@ -550,7 +647,7 @@ void ZigbeeNode::readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex) } -// qCDebug(dcZigbeeNode()) << "LQI response:" << zdoReply->responseData().toHex(); +// qCDebug(dcZigbeeNode()) << "LQI neighbor table reply:" << zdoReply->responseData().toHex(); ZigbeeDeviceProfile::NeighborTable neighborTable = ZigbeeDeviceProfile::parseNeighborTable(zdoReply->responseData()); if (neighborTable.startIndex == 0) { m_neighborTable = neighborTable; @@ -567,6 +664,7 @@ void ZigbeeNode::readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex) return; } + bool changed = false; 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; @@ -578,15 +676,18 @@ void ZigbeeNode::readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex) || existingRecord.depth != record.depth || existingRecord.lqi != record.lqi) { m_neighborTableRecords[record.shortAddress] = record; - emit neighborTableRecordsChanged(); + changed = true; } } else { m_neighborTableRecords.insert(record.shortAddress, record); - emit neighborTableRecordsChanged(); + changed = true; } } foreach (quint16 removedRecord, removedRecords) { m_neighborTableRecords.remove(removedRecord); + changed = true; + } + if (changed) { emit neighborTableRecordsChanged(); } reply->finishReply(ZigbeeReply::ErrorNoError); @@ -626,6 +727,7 @@ void ZigbeeNode::readRoutingTableChunk(ZigbeeReply *reply, quint8 startIndex) return; } + bool changed = false; 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; @@ -638,19 +740,21 @@ void ZigbeeNode::readRoutingTableChunk(ZigbeeReply *reply, quint8 startIndex) || existingRecord.routeRecordRequired != record.routeRecordRequired || existingRecord.nextHopAddress != record.nextHopAddress) { m_routingTableRecords[record.destinationAddress] = record; - emit routingTableRecordsChanged(); + changed = true; } } else { m_routingTableRecords.insert(record.destinationAddress, record); - emit routingTableRecordsChanged(); + changed = true; } } foreach (quint16 removedRecord, removedRecords) { m_routingTableRecords.remove(removedRecord); + changed = true; + } + if (changed) { emit routingTableRecordsChanged(); } reply->finishReply(ZigbeeReply::ErrorNoError); - }); } @@ -903,6 +1007,9 @@ void ZigbeeNode::readSoftwareBuildId(ZigbeeClusterBasic *basicCluster) // Finished with reading basic cluster, the node is initialized. setState(StateInitialized); + + // Reading binding table. If this fails, it's not critical, so setting the node to initialized before this is fine. + readBindingTableEntries(); }); } diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index e89bab7..fe3f3c5 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -98,11 +98,15 @@ public: // This method starts the node initialization phase (read descriptors and endpoints) void startInitialization(); - ZigbeeReply *removeAllBindings(); ZigbeeReply *readBindingTableEntries(); ZigbeeReply *readLqiTableEntries(); ZigbeeReply *readRoutingTableEntries(); + ZigbeeReply *addBinding(quint8 sourceEndpointId, quint16 clusterId, const ZigbeeAddress &destinationAddress, quint8 destinationEndpoint); + ZigbeeReply *addBinding(quint8 sourceEndpointId, quint16 clusterId, quint16 destinationGroupAddress); + ZigbeeReply *removeBinding(const ZigbeeDeviceProfile::BindingTableListRecord &binding); + ZigbeeReply *removeAllBindings(); + private: ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent = nullptr); @@ -132,6 +136,7 @@ private: QList m_bindingTableRecords; QHash m_neighborTableRecords; QHash m_routingTableRecords; + ZigbeeDeviceProfile::BindingTable m_bindingTable; 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 @@ -148,6 +153,7 @@ private: void initEndpoint(quint8 endpointId); void removeNextBinding(ZigbeeReply *reply); + void readBindingTableChunk(ZigbeeReply *reply, quint8 startIndex); void readNeighborTableChunk(ZigbeeReply *reply, quint8 startIndex); void readRoutingTableChunk(ZigbeeReply *reply, quint8 startIndex);