diff --git a/libnymea-zigbee/zigbeecluster.cpp b/libnymea-zigbee/zigbeecluster.cpp index a172ce5..6cd2be6 100644 --- a/libnymea-zigbee/zigbeecluster.cpp +++ b/libnymea-zigbee/zigbeecluster.cpp @@ -39,10 +39,8 @@ ZigbeeClusterAttribute ZigbeeCluster::attribute(quint16 id) void ZigbeeCluster::setAttribute(const ZigbeeClusterAttribute &attribute) { if (hasAttribute(attribute.id())) { - if (m_attributes.value(attribute.id()) != attribute) { - m_attributes[attribute.id()] = attribute; - emit attributeChanged(attribute); - } + m_attributes[attribute.id()] = attribute; + emit attributeChanged(attribute); } else { m_attributes.insert(attribute.id(), attribute); emit attributeChanged(attribute); diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 4afed51..8a25a67 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -162,6 +162,31 @@ bool ZigbeeNetwork::hasNode(const ZigbeeAddress &address) const return getZigbeeNode(address) != nullptr; } +void ZigbeeNetwork::addNodeInternally(ZigbeeNode *node) +{ + if (m_nodes.contains(node)) { + qCWarning(dcZigbeeNetwork()) << "The node" << node << "has already been added."; + return; + } + + node->setConnected(state() == StateRunning); + + m_nodes.append(node); + emit nodeAdded(node); +} + +void ZigbeeNetwork::removeNodeInternally(ZigbeeNode *node) +{ + if (!m_nodes.contains(node)) { + qCWarning(dcZigbeeNetwork()) << "Try to remove node" << node << "but not in the node list."; + return; + } + + m_nodes.removeAll(node); + emit nodeRemoved(node); + node->deleteLater(); +} + void ZigbeeNetwork::saveNetwork() { qCDebug(dcZigbeeNetwork()) << "Save current network configuration to" << m_settingsFileName; @@ -171,59 +196,9 @@ void ZigbeeNetwork::saveNetwork() settings.setValue("channel", channel()); settings.endGroup(); - settings.beginGroup("Nodes"); - settings.remove(""); - settings.endGroup(); - - settings.beginWriteArray("Nodes"); - for (int x = 0; x < nodes().count(); x++) { - settings.setArrayIndex(x); - ZigbeeNode *node = nodes().at(x); - settings.setValue("nwkAddress", node->shortAddress()); - settings.setValue("ieeeAddress", node->extendedAddress().toString()); - // TODO: save the rest of the node - - // Input clusters - settings.beginWriteArray("inputCluster"); - for (int i = 0; i < node->inputClusters().count(); i++) { - settings.setArrayIndex(i); - ZigbeeCluster *cluster = node->inputClusters().at(i); - settings.setValue("id", static_cast(cluster->clusterId())); - settings.beginWriteArray("attributes"); - - settings.beginWriteArray("attributes"); - for (int j = 0; j < cluster->attributes().count(); j++) { - settings.setArrayIndex(j); - ZigbeeClusterAttribute attribute = cluster->attributes().at(j); - settings.setValue("id", attribute.id()); - settings.setValue("dataType", static_cast(attribute.dataType())); - settings.setValue("data", attribute.data()); - } - settings.endArray(); - } - settings.endArray(); - - // Output clusters - settings.beginWriteArray("outputCluster"); - for (int i = 0; i < node->outputClusters().count(); i++) { - settings.setArrayIndex(i); - ZigbeeCluster *cluster = node->outputClusters().at(i); - settings.setValue("id", static_cast(cluster->clusterId())); - settings.beginWriteArray("attributes"); - - settings.beginWriteArray("attributes"); - for (int j = 0; j < cluster->attributes().count(); j++) { - settings.setArrayIndex(j); - ZigbeeClusterAttribute attribute = cluster->attributes().at(j); - settings.setValue("id", attribute.id()); - settings.setValue("dataType", static_cast(attribute.dataType())); - settings.setValue("data", attribute.data()); - } - settings.endArray(); - } - settings.endArray(); + foreach (ZigbeeNode *node, nodes()) { + saveNode(node); } - settings.endArray(); } void ZigbeeNetwork::loadNetwork() @@ -240,129 +215,81 @@ void ZigbeeNetwork::loadNetwork() setChannel(settings.value("channel", 0).toUInt()); settings.endGroup(); - int nodesCount = settings.beginReadArray("Nodes"); - for (int x = 0; x < nodesCount; x++) { - settings.setArrayIndex(x); + // Load nodes + settings.beginGroup("Nodes"); + foreach (const QString ieeeAddressString, settings.childGroups()) { + settings.beginGroup(ieeeAddressString); + ZigbeeNode *node = createNode(); + node->setExtendedAddress(ZigbeeAddress(ieeeAddressString)); node->setShortAddress(static_cast(settings.value("nwkAddress", 0).toUInt())); - node->setExtendedAddress(ZigbeeAddress(settings.value("ieeeAddress").toString())); // TODO: load the rest of the node - // Input clusters - int inputClusterCount =settings.beginReadArray("inputCluster"); - for (int i = 0; i < inputClusterCount; i++) { - settings.setArrayIndex(i); - Zigbee::ClusterId clusterId = static_cast(settings.value("id", 0).toInt()); - settings.beginWriteArray("attributes"); + settings.beginGroup("inputCluster"); + foreach (const QString &clusterIdString, settings.childGroups()) { + settings.beginGroup(clusterIdString); + Zigbee::ClusterId clusterId = static_cast(clusterIdString.toInt()); - int attributeCount = settings.beginReadArray("attributes"); - if (attributeCount == 0) { - node->setClusterAttribute(clusterId); - } else { - for (int j = 0; j < attributeCount; j++) { - settings.setArrayIndex(i); - ZigbeeClusterAttribute attribute; - quint16 id = static_cast(settings.value("id", 0).toInt()); - Zigbee::DataType dataType = static_cast(settings.value("dataType", 0).toInt()); - QByteArray data = settings.value("data").toByteArray(); - node->setClusterAttribute(clusterId, ZigbeeClusterAttribute(id, dataType, data)); - } - } - settings.endArray(); + foreach (const QString &attributeIdString, settings.childGroups()) { + settings.beginGroup(attributeIdString); + quint16 id = static_cast(attributeIdString.toInt()); + Zigbee::DataType dataType = static_cast(settings.value("dataType", 0).toInt()); + QByteArray data = settings.value("data").toByteArray(); + node->setClusterAttribute(clusterId, ZigbeeClusterAttribute(id, dataType, data)); + settings.endGroup(); // attributeId + } + settings.endGroup(); // clusterId } - settings.endArray(); + settings.endGroup(); // inputCluster - // Output clusters - int outputClusterCount =settings.beginReadArray("outputCluster"); - for (int i = 0; i < outputClusterCount; i++) { - settings.setArrayIndex(i); - Zigbee::ClusterId clusterId = static_cast(settings.value("id", 0).toInt()); - settings.beginWriteArray("attributes"); + // Output cluster + settings.beginGroup("outputCluster"); + foreach (const QString &clusterIdString, settings.childGroups()) { + settings.beginGroup(clusterIdString); + Zigbee::ClusterId clusterId = static_cast(clusterIdString.toInt()); - int attributeCount = settings.beginReadArray("attributes"); - if (attributeCount == 0) { - node->setClusterAttribute(clusterId); - } else { - for (int j = 0; j < attributeCount; j++) { - settings.setArrayIndex(i); - ZigbeeClusterAttribute attribute; - quint16 id = static_cast(settings.value("id", 0).toInt()); - Zigbee::DataType dataType = static_cast(settings.value("dataType", 0).toInt()); - QByteArray data = settings.value("data").toByteArray(); - node->setClusterAttribute(clusterId, ZigbeeClusterAttribute(id, dataType, data)); - } - } - - settings.endArray(); + foreach (const QString &attributeIdString, settings.childGroups()) { + settings.beginGroup(attributeIdString); + quint16 id = static_cast(attributeIdString.toInt()); + Zigbee::DataType dataType = static_cast(settings.value("dataType", 0).toInt()); + QByteArray data = settings.value("data").toByteArray(); + node->setClusterAttribute(clusterId, ZigbeeClusterAttribute(id, dataType, data)); + settings.endGroup(); // attributeId + } + settings.endGroup(); // clusterId } - settings.endArray(); + settings.endGroup(); // inputCluster node->setState(StateInitialized); addNodeInternally(node); + + settings.endGroup(); // ieeeAddress } - settings.endArray(); + settings.endGroup(); // Nodes qCDebug(dcZigbeeNetwork()) << "Extended PAN ID:" << m_extendedPanId << ZigbeeUtils::convertUint64ToHexString(m_extendedPanId); qCDebug(dcZigbeeNetwork()) << "Channel" << m_channel; qCDebug(dcZigbeeNetwork()) << QStringLiteral("Nodes: (%1)").arg(m_nodes.count()); foreach (ZigbeeNode *node, nodes()) { qCDebug(dcZigbeeNetwork()) << " - " << node; + qCDebug(dcZigbeeNetwork()) << "Output cluster:"; + foreach (ZigbeeCluster *cluster, node->outputClusters()) { + qCDebug(dcZigbeeNetwork()) << " " << cluster; + foreach (const ZigbeeClusterAttribute &attribute, cluster->attributes()) { + qCDebug(dcZigbeeNetwork()) << " " << attribute; + } + } + + qCDebug(dcZigbeeNetwork()) << "Input cluster:"; + foreach (ZigbeeCluster *cluster, node->inputClusters()) { + qCDebug(dcZigbeeNetwork()) << " " << cluster; + foreach (const ZigbeeClusterAttribute &attribute, cluster->attributes()) { + qCDebug(dcZigbeeNetwork()) << " " << attribute; + } + } } } - -void ZigbeeNetwork::addNode(ZigbeeNode *node) -{ - addNodeInternally(node); - saveNetwork(); -} - -void ZigbeeNetwork::addUnitializedNode(ZigbeeNode *node) -{ - if (m_uninitializedNodes.contains(node)) { - qCWarning(dcZigbeeNetwork()) << "The uninitialized node" << node << "has already been added."; - return; - } - - m_uninitializedNodes.append(node); -} - -void ZigbeeNetwork::addNodeInternally(ZigbeeNode *node) -{ - if (m_nodes.contains(node)) { - qCWarning(dcZigbeeNetwork()) << "The node" << node << "has already been added."; - return; - } - - m_nodes.append(node); - emit nodeAdded(node); -} - -void ZigbeeNetwork::removeNode(ZigbeeNode *node) -{ - removeNodeInternally(node); - saveNetwork(); -} - -void ZigbeeNetwork::removeNodeInternally(ZigbeeNode *node) -{ - if (!m_nodes.contains(node)) { - qCWarning(dcZigbeeNetwork()) << "Try to remove node" << node << "but not in the node list."; - return; - } - - m_nodes.removeAll(node); - emit nodeRemoved(node); - node->deleteLater(); -} - -ZigbeeNode *ZigbeeNetwork::createNode() -{ - ZigbeeNode *node = new ZigbeeNode(this); - connect(node, &ZigbeeNode::stateChanged, this, &ZigbeeNetwork::onNodeStateChanged); - return node; -} - void ZigbeeNetwork::clearSettings() { qCDebug(dcZigbeeNetwork()) << "Clear network settings"; @@ -375,7 +302,7 @@ void ZigbeeNetwork::clearSettings() qCDebug(dcZigbeeNetwork()) << "Remove zigbee nodes from network"; foreach (ZigbeeNode *node, m_nodes) { - removeNodeInternally(node); + removeNode(node); } qCDebug(dcZigbeeNetwork()) << "Clear network settings" << m_settingsFileName; @@ -383,6 +310,100 @@ void ZigbeeNetwork::clearSettings() settings.clear(); } +void ZigbeeNetwork::saveNode(ZigbeeNode *node) +{ + QSettings settings(m_settingsFileName, QSettings::IniFormat, this); + settings.beginGroup("Nodes"); + + // Clear settings for this node before storing it + settings.beginGroup(node->extendedAddress().toString()); + settings.remove(""); + settings.endGroup(); + + // Save this node + settings.beginGroup(node->extendedAddress().toString()); + settings.setValue("nwkAddress", node->shortAddress()); + // TODO: save the rest of the node + + // Input clusters + settings.beginGroup("inputCluster"); + foreach (ZigbeeCluster *cluster, node->inputClusters()) { + settings.beginGroup(QString::number(static_cast(cluster->clusterId()))); + foreach (const ZigbeeClusterAttribute &attribute, cluster->attributes()) { + settings.beginGroup(QString::number(static_cast(attribute.id()))); + settings.setValue("dataType", static_cast(attribute.dataType())); + settings.setValue("data", attribute.data()); + settings.endGroup(); // attributeId + } + settings.endGroup(); // clusterId + } + settings.endGroup(); // inputCluster + + + // Output clusters + settings.beginGroup("outputCluster"); + foreach (ZigbeeCluster *cluster, node->outputClusters()) { + settings.beginGroup(QString::number(static_cast(cluster->clusterId()))); + foreach (const ZigbeeClusterAttribute &attribute, cluster->attributes()) { + settings.beginGroup(QString::number(static_cast(attribute.id()))); + settings.setValue("dataType", static_cast(attribute.dataType())); + settings.setValue("data", attribute.data()); + settings.endGroup(); // attributeId + } + settings.endGroup(); // clusterId + } + settings.endGroup(); // inputCluster + + settings.endGroup(); // Node ieee address + + settings.endGroup(); // Nodes +} + +void ZigbeeNetwork::removeNodeFromSettings(ZigbeeNode *node) +{ + qCDebug(dcZigbeeNetwork()) << "Remove node" << node << "from settings" << m_settingsFileName; + QSettings settings(m_settingsFileName, QSettings::IniFormat, this); + settings.beginGroup("Nodes"); + + // Clear settings for this node before storing it + settings.beginGroup(node->extendedAddress().toString()); + settings.remove(""); + settings.endGroup(); + + settings.endGroup(); // Nodes +} + + +void ZigbeeNetwork::addNode(ZigbeeNode *node) +{ + qCDebug(dcZigbeeNetwork()) << "Add node" << node; + addNodeInternally(node); + saveNode(node); +} + +void ZigbeeNetwork::addUnitializedNode(ZigbeeNode *node) +{ + if (m_uninitializedNodes.contains(node)) { + qCWarning(dcZigbeeNetwork()) << "The uninitialized node" << node << "has already been added."; + return; + } + connect(node, &ZigbeeNode::stateChanged, this, &ZigbeeNetwork::onNodeStateChanged); + m_uninitializedNodes.append(node); +} + +void ZigbeeNetwork::removeNode(ZigbeeNode *node) +{ + qCDebug(dcZigbeeNetwork()) << "Remove node" << node; + removeNodeInternally(node); + removeNodeFromSettings(node); +} + +ZigbeeNode *ZigbeeNetwork::createNode() +{ + ZigbeeNode *node = new ZigbeeNode(this); + return node; +} + void ZigbeeNetwork::setState(ZigbeeNetwork::State state) { if (m_state == state) @@ -393,7 +414,6 @@ void ZigbeeNetwork::setState(ZigbeeNetwork::State state) emit stateChanged(m_state); if (state == StateRunning) saveNetwork(); - if (state == StateStarting) loadNetwork(); } void ZigbeeNetwork::setError(ZigbeeNetwork::Error error) @@ -411,7 +431,17 @@ void ZigbeeNetwork::onNodeStateChanged(ZigbeeNode::State state) ZigbeeNode *node = qobject_cast(sender()); if (state == ZigbeeNode::StateInitialized && m_uninitializedNodes.contains(node)) { m_uninitializedNodes.removeAll(node); + disconnect(node, &ZigbeeNode::stateChanged, this, &ZigbeeNetwork::onNodeStateChanged); addNode(node); } } +void ZigbeeNetwork::onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute) +{ + Q_UNUSED(cluster) + Q_UNUSED(attribute) + + ZigbeeNode *node = qobject_cast(sender()); + saveNode(node); +} + diff --git a/libnymea-zigbee/zigbeenetwork.h b/libnymea-zigbee/zigbeenetwork.h index a5bd481..8c67576 100644 --- a/libnymea-zigbee/zigbeenetwork.h +++ b/libnymea-zigbee/zigbeenetwork.h @@ -18,6 +18,7 @@ public: Q_ENUM(ControllerType) enum State { + StateUninitialized, StateDisconnected, StateStarting, StateRunning, @@ -72,7 +73,7 @@ public: private: ControllerType m_controllerType = ControlerTypeNxp; - State m_state = StateDisconnected; + State m_state = StateUninitialized; Error m_error = ErrorNoError; // Serial port configuration @@ -89,20 +90,23 @@ private: QList m_nodes; QList m_uninitializedNodes; - void saveNetwork(); - void loadNetwork(); void addNodeInternally(ZigbeeNode *node); void removeNodeInternally(ZigbeeNode *node); protected: + void saveNetwork(); + void loadNetwork(); + void clearSettings(); + + void saveNode(ZigbeeNode *node); + void removeNodeFromSettings(ZigbeeNode *node); + void addNode(ZigbeeNode *node); void addUnitializedNode(ZigbeeNode *node); void removeNode(ZigbeeNode *node); ZigbeeNode *createNode(); - void clearSettings(); - void setState(State state); void setError(Error error); @@ -124,6 +128,7 @@ signals: private slots: void onNodeStateChanged(ZigbeeNode::State state); + void onNodeClusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute); public slots: virtual void startNetwork() = 0; diff --git a/libnymea-zigbee/zigbeenetworkmanager.cpp b/libnymea-zigbee/zigbeenetworkmanager.cpp index 63d4e72..bb2c698 100644 --- a/libnymea-zigbee/zigbeenetworkmanager.cpp +++ b/libnymea-zigbee/zigbeenetworkmanager.cpp @@ -176,7 +176,6 @@ void ZigbeeNetworkManager::onCommandResetControllerFinished() } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; - //if (m_startingState == StartingStateReset && !m_networkRunning) setStartingState(StartingStateGetVersion); } void ZigbeeNetworkManager::onCommandSoftResetControllerFinished() @@ -207,7 +206,6 @@ void ZigbeeNetworkManager::onCommandErasePersistentDataFinished() qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; if (m_startingState == StartingStateErase) { setStartingState(StartingStateReset); - // setState(StateStarting); } } @@ -427,7 +425,6 @@ void ZigbeeNetworkManager::onCommandAuthenticateDeviceFinished() quint16 shortPan = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(38, 2)); quint64 extendedPanId = ZigbeeUtils::convertByteArrayToUint64(reply->additionalMessage().data().mid(40, 8)); - qCDebug(dcZigbeeNetwork()) << "Authentication response:"; qCDebug(dcZigbeeNetwork()) << " Gateways address:" << ZigbeeAddress(gatewayIeeeAddress); qCDebug(dcZigbeeNetwork()) << " Key:" << encryptedKey; @@ -489,7 +486,9 @@ void ZigbeeNetworkManager::processNetworkFormed(const ZigbeeInterfaceMessage &me setExtendedAddress(ZigbeeAddress(extendedAddress)); setChannel(channel); - addUnitializedNode(this); + if (!hasNode(this->shortAddress())) + addUnitializedNode(this); + } void ZigbeeNetworkManager::onCommandEnableWhitelistFinished() @@ -830,6 +829,9 @@ void ZigbeeNetworkManager::onCommandPowerDescriptorRequestFinished() if (m_startingState == StartingStateReadPowerDescriptor) { setStartingState(StartingStateNone); setState(StateRunning); + foreach (ZigbeeNode *node, nodes()) { + node->setConnected(true); + } } } @@ -1193,6 +1195,9 @@ void ZigbeeNetworkManager::startNetwork() connect(m_controller, &ZigbeeBridgeController::messageReceived, this, &ZigbeeNetworkManager::onMessageReceived); connect(m_controller, &ZigbeeBridgeController::availableChanged, this, &ZigbeeNetworkManager::onControllerAvailableChanged); + if (state() == StateUninitialized) + loadNetwork(); + if (!m_controller->enable(serialPortName(), serialBaudrate())) { setState(StateDisconnected); setStartingState(StartingStateNone); @@ -1267,6 +1272,10 @@ void ZigbeeNetworkManager::onControllerAvailableChanged(bool available) } if (!available) { + foreach (ZigbeeNode *node, nodes()) { + node->setConnected(false); + } + setError(ErrorHardwareUnavailable); setState(StateDisconnected); setStartingState(StartingStateNone); @@ -1281,7 +1290,7 @@ void ZigbeeNetworkManager::onControllerAvailableChanged(bool available) void ZigbeeNetworkManager::factoryResetNetwork() { - qCDebug(dcZigbeeNetwork()) << "Factory reset network and forgett all information. This cannot be undone."; + qCDebug(dcZigbeeNetwork()) << "Factory reset network and forget all information. This cannot be undone."; clearSettings(); setState(StateStarting); diff --git a/libnymea-zigbee/zigbeenetworkmanager.h b/libnymea-zigbee/zigbeenetworkmanager.h index 780d541..83d4de4 100644 --- a/libnymea-zigbee/zigbeenetworkmanager.h +++ b/libnymea-zigbee/zigbeenetworkmanager.h @@ -25,7 +25,6 @@ public: bool permitJoining() const; void setPermitJoining(bool permitJoining); - private: enum StartingState { StartingStateNone, diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 9963480..3a7ecdb 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -15,6 +15,11 @@ ZigbeeNode::State ZigbeeNode::state() const return m_state; } +bool ZigbeeNode::connected() const +{ + return m_connected; +} + quint16 ZigbeeNode::shortAddress() const { return m_shortAddress; @@ -220,6 +225,16 @@ void ZigbeeNode::setState(ZigbeeNode::State state) emit stateChanged(m_state); } +void ZigbeeNode::setConnected(bool connected) +{ + if (m_connected == connected) + return; + + qCDebug(dcZigbeeNode()) << "Connected changed" << connected; + m_connected = connected; + emit connectedChanged(m_connected); +} + //void ZigbeeNode::identify() //{ @@ -380,14 +395,14 @@ void ZigbeeNode::setPowerLevel(ZigbeeNode::PowerLevel powerLevel) void ZigbeeNode::setClusterAttribute(Zigbee::ClusterId clusterId, const ZigbeeClusterAttribute &attribute) { - //qCDebug(dcZigbeeNode()) << this << "cluster attribute changed" << clusterId << attribute; + qCDebug(dcZigbeeNode()) << this << "cluster attribute changed" << clusterId << attribute; ZigbeeCluster *cluster = m_outputClusters.value(clusterId); // Note: create the cluster if not there yet bool clusterCreated = false; if (!cluster) { cluster = new ZigbeeCluster(clusterId, this); - qCWarning(dcZigbeeNode()) << "Created cluster" << cluster; + qCDebug(dcZigbeeNode()) << "Created cluster" << cluster; connect(cluster, &ZigbeeCluster::attributeChanged, this, &ZigbeeNode::onClusterAttributeChanged); m_outputClusters.insert(clusterId, cluster); clusterCreated = true; diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index 9cdbe5f..9d79253 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -72,6 +72,7 @@ public: Q_ENUM(PowerLevel) State state() const; + bool connected() const; quint16 shortAddress() const; ZigbeeAddress extendedAddress() const; @@ -129,6 +130,7 @@ public: private: ZigbeeNode(QObject *parent = nullptr); + bool m_connected = false; State m_state = StateUninitialized; QHash m_inputClusters; @@ -181,6 +183,7 @@ private: protected: void setState(State state); + void setConnected(bool connected); void setShortAddress(const quint16 &shortAddress); void setExtendedAddress(const ZigbeeAddress &extendedAddress); @@ -213,6 +216,7 @@ protected: signals: void stateChanged(State state); + void connectedChanged(bool connected); void clusterAdded(ZigbeeCluster *cluster); void clusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute);