From 69ed7e3496b38ac2a8ab22a60d26228f9dc4c0a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Thu, 28 May 2020 18:03:33 +0200 Subject: [PATCH] Implement new clusters and restructure ZCL frame flow and implement attribute reports --- .../deconz/zigbeebridgecontrollerdeconz.cpp | 62 +---- .../deconz/zigbeebridgecontrollerdeconz.h | 34 +-- .../backends/deconz/zigbeenetworkdeconz.cpp | 99 ++----- .../backends/deconz/zigbeenetworkdeconz.h | 15 +- libnymea-zigbee/libnymea-zigbee.pro | 4 + libnymea-zigbee/loggingcategory.cpp | 5 +- libnymea-zigbee/loggingcategory.h | 5 +- .../zcl/general/zigbeeclusterbasic.cpp | 2 + .../zcl/general/zigbeeclusterbasic.h | 2 + .../zcl/general/zigbeeclusteronoff.cpp | 82 ++++++ .../zcl/general/zigbeeclusteronoff.h | 5 +- ...gbeeclusterrelativehumiditymeasurement.cpp | 6 + ...zigbeeclusterrelativehumiditymeasurement.h | 16 ++ .../zigbeeclustertemperaturemeasurement.cpp | 60 ++++ .../zigbeeclustertemperaturemeasurement.h | 69 +++++ libnymea-zigbee/zcl/zigbeecluster.cpp | 24 +- libnymea-zigbee/zcl/zigbeecluster.h | 8 +- libnymea-zigbee/zdo/zigbeedeviceobject.cpp | 14 +- libnymea-zigbee/zdo/zigbeedeviceobject.h | 13 +- libnymea-zigbee/zigbee.cpp | 57 ++++ libnymea-zigbee/zigbee.h | 237 +++------------- libnymea-zigbee/zigbeenetwork.cpp | 4 +- libnymea-zigbee/zigbeenode.cpp | 257 +++++++++++++----- libnymea-zigbee/zigbeenode.h | 15 +- libnymea-zigbee/zigbeenodeendpoint.cpp | 45 +++ libnymea-zigbee/zigbeenodeendpoint.h | 12 +- 26 files changed, 660 insertions(+), 492 deletions(-) create mode 100644 libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp create mode 100644 libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h create mode 100644 libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.cpp create mode 100644 libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.h diff --git a/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.cpp b/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.cpp index 1718d9b..906bbff 100644 --- a/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.cpp +++ b/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.cpp @@ -814,7 +814,7 @@ void ZigbeeBridgeControllerDeconz::processDataIndication(const QByteArray &data) stream.setByteOrder(QDataStream::LittleEndian); quint16 payloadLenght = 0; quint8 deviceStateFlag = 0; quint8 reserved = 0; quint16 asduLength = 0; - DeconzApsDataIndication indication; + Zigbee::ApsdeDataIndication indication; stream >> payloadLenght >> deviceStateFlag; stream >> indication.destinationAddressMode; Zigbee::DestinationAddressMode destinationAddressMode = static_cast(indication.destinationAddressMode); @@ -844,8 +844,8 @@ void ZigbeeBridgeControllerDeconz::processDataIndication(const QByteArray &data) stream >> reserved >> reserved >> indication.lqi >> reserved >> reserved >> reserved >> reserved >> indication.rssi; // Print the information for debugging - qCDebug(dcZigbeeAps()) << "APSDE-DATA.indication" << indication; qCDebug(dcZigbeeController()) << indication; + qCDebug(dcZigbeeAps()) << "APSDE-DATA.indication" << indication; emit apsDataIndicationReceived(indication); @@ -860,7 +860,7 @@ void ZigbeeBridgeControllerDeconz::processDataConfirm(const QByteArray &data) { QDataStream stream(data); stream.setByteOrder(QDataStream::LittleEndian); - DeconzApsDataConfirm confirm; + Zigbee::ApsdeDataConfirm confirm; quint16 payloadLenght = 0; quint8 deviceStateFlag = 0; stream >> payloadLenght >> deviceStateFlag; stream >> confirm.requestId >> confirm.destinationAddressMode; @@ -1006,62 +1006,6 @@ QDebug operator<<(QDebug debug, const DeconzDeviceState &deviceState) return debug.space(); } -QDebug operator<<(QDebug debug, const DeconzApsDataConfirm &confirm) -{ - debug.nospace() << "APSDE-DATA.confirm("; - debug.nospace() << "Request ID: " << confirm.requestId << ", "; - - if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeGroup) - debug.nospace() << "Group address:" << ZigbeeUtils::convertUint16ToHexString(confirm.destinationShortAddress) << ", "; - - if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) - debug.nospace() << "NWK address:" << ZigbeeUtils::convertUint16ToHexString(confirm.destinationShortAddress) << ", "; - - if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) - debug.nospace() << "IEEE address:" << ZigbeeAddress(confirm.destinationIeeeAddress).toString() << ", "; - - debug.nospace() << "Destination EP:" << ZigbeeUtils::convertByteToHexString(confirm.destinationEndpoint) << ", "; - debug.nospace() << "Source EP:" << ZigbeeUtils::convertByteToHexString(confirm.sourceEndpoint) << ", "; - debug.nospace() << static_cast(confirm.zigbeeStatusCode); - debug.nospace() << ")"; - - return debug.space(); -} - -QDebug operator<<(QDebug debug, const DeconzApsDataIndication &indication) -{ - debug.nospace() << "APSDE-DATA.indication("; - if (indication.destinationAddressMode == Zigbee::DestinationAddressModeGroup) - debug.nospace() << "Group address:" << ZigbeeUtils::convertUint16ToHexString(indication.destinationShortAddress) << ", "; - - if (indication.destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) - debug.nospace() << "NWK address:" << ZigbeeUtils::convertUint16ToHexString(indication.destinationShortAddress) << ", "; - - if (indication.destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) - debug.nospace() << "IEEE address:" << ZigbeeAddress(indication.destinationIeeeAddress).toString() << ", "; - - debug.nospace() << "Destination EP:" << ZigbeeUtils::convertByteToHexString(indication.destinationEndpoint) << ", "; - debug.nospace() << "Source EP:" << ZigbeeUtils::convertByteToHexString(indication.sourceEndpoint) << ", "; - - if (indication.sourceAddressMode == Zigbee::SourceAddressModeShortAddress || indication.sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) - debug.nospace() << "Source NWK address:" << ZigbeeUtils::convertUint16ToHexString(indication.sourceShortAddress) << ", "; - - if (indication.sourceAddressMode == Zigbee::SourceAddressModeIeeeAddress || indication.sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) - debug.nospace() << "Source IEEE address:" << ZigbeeAddress(indication.sourceIeeeAddress).toString() << ", "; - - debug.nospace() << static_cast(indication.profileId) << ", "; - if (indication.profileId == static_cast(Zigbee::ZigbeeProfileDevice)) { - debug.nospace() << static_cast(indication.clusterId) << ", "; - } else { - debug.nospace() << static_cast(indication.clusterId) << ", "; - } - - debug.nospace() << "ASDU: " << ZigbeeUtils::convertByteArrayToHexString(indication.asdu) << ", "; - debug.nospace() << "LQI: " << indication.lqi << ", "; - debug.nospace() << "RSSI: " << indication.rssi << "dBm)"; - return debug.space(); -} - QDebug operator<<(QDebug debug, const DeconzNetworkConfiguration &configuration) { debug.nospace() << "Network configuration:" << endl; diff --git a/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h b/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h index 70856f4..d433164 100644 --- a/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h +++ b/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h @@ -70,34 +70,6 @@ typedef struct DeconzDeviceState { bool apsDataRequestFreeSlots = false; } DeconzDeviceState; - -// Basic struct for interface data. Default Response -typedef struct DeconzApsDataConfirm { - quint8 requestId = 0; - quint8 destinationAddressMode = Zigbee::DestinationAddressModeShortAddress; - quint16 destinationShortAddress = 0; - quint64 destinationIeeeAddress; - quint8 destinationEndpoint = 0; - quint8 sourceEndpoint = 0; - quint8 zigbeeStatusCode = 0; -} DeconzApsDataConfirm; - -typedef struct DeconzApsDataIndication { - quint8 destinationAddressMode = 0; - quint16 destinationShortAddress = 0; - quint64 destinationIeeeAddress = 0; - quint8 destinationEndpoint = 0; - quint8 sourceAddressMode = 0; - quint16 sourceShortAddress = 0; - quint64 sourceIeeeAddress = 0; - quint8 sourceEndpoint = 0; - quint16 profileId = 0; - quint16 clusterId = 0; - QByteArray asdu; - quint8 lqi = 0; - qint8 rssi = 0; -} DeconzApsDataIndication; - class ZigbeeBridgeControllerDeconz : public ZigbeeBridgeController { Q_OBJECT @@ -164,8 +136,8 @@ signals: void networkStateChanged(Deconz::NetworkState networkState); void networkConfigurationParameterChanged(const DeconzNetworkConfiguration &networkConfiguration); - void apsDataConfirmReceived(const DeconzApsDataConfirm &confirm); - void apsDataIndicationReceived(const DeconzApsDataIndication &indication); + void apsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); + void apsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); private slots: void onInterfaceAvailableChanged(bool available); @@ -179,8 +151,6 @@ public slots: }; QDebug operator<<(QDebug debug, const DeconzDeviceState &deviceState); -QDebug operator<<(QDebug debug, const DeconzApsDataConfirm &confirm); -QDebug operator<<(QDebug debug, const DeconzApsDataIndication &indication); QDebug operator<<(QDebug debug, const DeconzNetworkConfiguration &configuration); diff --git a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp index d4542c3..ae3e3a6 100644 --- a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp +++ b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp @@ -36,7 +36,6 @@ ZigbeeNetworkDeconz::ZigbeeNetworkDeconz(QObject *parent) : ZigbeeNetwork(parent) { m_controller = new ZigbeeBridgeControllerDeconz(this); - //connect(m_controller, &ZigbeeBridgeControllerDeconz::messageReceived, this, &ZigbeeNetworkDeconz::onMessageReceived); connect(m_controller, &ZigbeeBridgeControllerDeconz::availableChanged, this, &ZigbeeNetworkDeconz::onControllerAvailableChanged); connect(m_controller, &ZigbeeBridgeControllerDeconz::apsDataConfirmReceived, this, &ZigbeeNetworkDeconz::onApsDataConfirmReceived); connect(m_controller, &ZigbeeBridgeControllerDeconz::apsDataIndicationReceived, this, &ZigbeeNetworkDeconz::onApsDataIndicationReceived); @@ -54,11 +53,10 @@ ZigbeeNetworkDeconz::ZigbeeNetworkDeconz(QObject *parent) : ZigbeeBridgeController *ZigbeeNetworkDeconz::bridgeController() const { - if (m_controller) { - return qobject_cast(m_controller); - } + if (!m_controller) + return nullptr; - return nullptr; + return qobject_cast(m_controller); } ZigbeeNetworkReply *ZigbeeNetworkDeconz::sendRequest(const ZigbeeNetworkRequest &request) @@ -308,8 +306,10 @@ void ZigbeeNetworkDeconz::setCreateNetworkState(ZigbeeNetworkDeconz::CreateNetwo } } -void ZigbeeNetworkDeconz::handleZigbeeDeviceProfileIndication(const DeconzApsDataIndication &indication) +void ZigbeeNetworkDeconz::handleZigbeeDeviceProfileIndication(const Zigbee::ApsdeDataIndication &indication) { + //qCDebug(dcZigbeeNetwork()) << "Handle ZDP indication" << indication; + // Check if this is a device announcement if (indication.clusterId == ZigbeeDeviceProfile::DeviceAnnounce) { QDataStream stream(indication.asdu); @@ -320,77 +320,40 @@ void ZigbeeNetworkDeconz::handleZigbeeDeviceProfileIndication(const DeconzApsDat return; } + if (indication.destinationShortAddress == Zigbee::BroadcastAddressAllNodes || + indication.destinationShortAddress == Zigbee::BroadcastAddressAllRouters || + indication.destinationShortAddress == Zigbee::BroadcastAddressAllNonSleepingNodes) { + qCDebug(dcZigbeeNetwork()) << "Received unhandled broadcast ZDO indication" << indication; + + // FIXME: check what we can do with such messages like permit join + return; + } + ZigbeeNode *node = getZigbeeNode(indication.sourceShortAddress); if (!node) { qCWarning(dcZigbeeNetwork()) << "Received a ZDO indication for an unrecognized node. There is no such node in the system. Ignoring indication" << indication; + // FIXME: check if we want to create it since the device definitly exists within the network return; } - node->deviceObject()->processApsDataIndication(indication.destinationEndpoint, indication.sourceEndpoint, indication.clusterId, indication.asdu, indication.lqi, indication.rssi); + // Let the node device object handle this (ZDP) + node->deviceObject()->processApsDataIndication(indication); } -void ZigbeeNetworkDeconz::handleZigbeeLightLinkIndication(const DeconzApsDataIndication &indication) +void ZigbeeNetworkDeconz::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication) { ZigbeeClusterLibrary::Frame frame = ZigbeeClusterLibrary::parseFrameData(indication.asdu); - //qCDebug(dcZigbeeNetwork()) << "ZCL ZLL" << indication << frame; + //qCDebug(dcZigbeeNetwork()) << "Handle ZCL indication" << indication << frame; // Get the node ZigbeeNode *node = getZigbeeNode(indication.sourceShortAddress); if (!node) { qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized node. There is no such node in the system. Ignoring indication" << indication; + // FIXME: maybe create and init the node, since it is in the network, but not recognized return; } - // Get the endpoint - ZigbeeNodeEndpoint *endpoint = node->getEndpoint(indication.sourceEndpoint); - if (!endpoint) { - qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized endpoint. There is no such endpoint on" << node << ". Ignoring indication" << indication; - return; - } - - // Get the cluster - ZigbeeCluster *cluster = endpoint->getOutputCluster(static_cast(indication.clusterId)); - if (!cluster) { - cluster = endpoint->getInputCluster(static_cast(indication.clusterId)); - if (!cluster) { - qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized cluster. There is no such cluster on" << node << endpoint << "in the system. Ignoring indication" << indication; - return; - } - } - - cluster->processApsDataIndication(indication.asdu); -} - -void ZigbeeNetworkDeconz::handleZigbeeHomeAutomationIndication(const DeconzApsDataIndication &indication) -{ - ZigbeeClusterLibrary::Frame frame = ZigbeeClusterLibrary::parseFrameData(indication.asdu); - //qCDebug(dcZigbeeNetwork()) << "ZCL HA" << indication << frame; - - // Get the node - ZigbeeNode *node = getZigbeeNode(indication.sourceShortAddress); - if (!node) { - qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized node. There is no such node in the system. Ignoring indication" << indication; - return; - } - - // Get the endpoint - ZigbeeNodeEndpoint *endpoint = node->getEndpoint(indication.sourceEndpoint); - if (!endpoint) { - qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized endpoint. There is no such endpoint on" << node << ". Ignoring indication" << indication; - return; - } - - // Get the cluster - ZigbeeCluster *cluster = endpoint->getOutputCluster(static_cast(indication.clusterId)); - if (!cluster) { - cluster = endpoint->getInputCluster(static_cast(indication.clusterId)); - if (!cluster) { - qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized cluster. There is no such cluster on" << node << endpoint << "in the system. Ignoring indication" << indication; - return; - } - } - - cluster->processApsDataIndication(indication.asdu); + node->handleZigbeeClusterLibraryIndication(indication); } void ZigbeeNetworkDeconz::setPermitJoiningInternal(bool permitJoining) @@ -632,7 +595,7 @@ void ZigbeeNetworkDeconz::onPermitJoinRefreshTimout() setPermitJoiningInternal(true); } -void ZigbeeNetworkDeconz::onApsDataConfirmReceived(const DeconzApsDataConfirm &confirm) +void ZigbeeNetworkDeconz::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm) { ZigbeeNetworkReply *reply = m_pendingReplies.value(confirm.requestId); if (!reply) { @@ -643,7 +606,7 @@ void ZigbeeNetworkDeconz::onApsDataConfirmReceived(const DeconzApsDataConfirm &c setReplyResponseError(reply, static_cast(confirm.zigbeeStatusCode)); } -void ZigbeeNetworkDeconz::onApsDataIndicationReceived(const DeconzApsDataIndication &indication) +void ZigbeeNetworkDeconz::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication) { // Check if this indocation is related to any pending reply if (indication.profileId == Zigbee::ZigbeeProfileDevice) { @@ -651,18 +614,8 @@ void ZigbeeNetworkDeconz::onApsDataIndicationReceived(const DeconzApsDataIndicat return; } - if (indication.profileId == Zigbee::ZigbeeProfileLightLink) { - - } - - if (indication.profileId == Zigbee::ZigbeeProfileHomeAutomation) { - handleZigbeeHomeAutomationIndication(indication); - return; - } - - // FIXME: handle it - - qCDebug(dcZigbeeNetwork()) << "Unhandled indication" << indication; + // Else let the node handle this indication + handleZigbeeClusterLibraryIndication(indication); } void ZigbeeNetworkDeconz::onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress ieeeAddress, quint8 macCapabilities) diff --git a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.h b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.h index 721f792..5b3c25b 100644 --- a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.h +++ b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.h @@ -72,15 +72,10 @@ private: void setCreateNetworkState(CreateNetworkState state); // ZDO - void handleZigbeeDeviceProfileIndication(const DeconzApsDataIndication &indication); + void handleZigbeeDeviceProfileIndication(const Zigbee::ApsdeDataIndication &indication); - // ZZL - void handleZigbeeLightLinkIndication(const DeconzApsDataIndication &indication); - - // HA - void handleZigbeeHomeAutomationIndication(const DeconzApsDataIndication &indication); - - // GP + // ZCL + void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication); protected: void setPermitJoiningInternal(bool permitJoining) override; @@ -91,8 +86,8 @@ private slots: void onPollNetworkStateTimeout(); void onPermitJoinRefreshTimout(); - void onApsDataConfirmReceived(const DeconzApsDataConfirm &confirm); - void onApsDataIndicationReceived(const DeconzApsDataIndication &indication); + void onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); + void onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); void onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress ieeeAddress, quint8 macCapabilities); diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index a760db5..a7299c0 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -9,6 +9,8 @@ SOURCES += \ backends/deconz/zigbeebridgecontrollerdeconz.cpp \ backends/deconz/zigbeenetworkdeconz.cpp \ zcl/general/zigbeeclusteronoff.cpp \ + zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp \ + zcl/measurement/zigbeeclustertemperaturemeasurement.cpp \ zcl/zigbeecluster.cpp \ zcl/zigbeeclusterattribute.cpp \ zcl/zigbeeclusterlibrary.cpp \ @@ -51,6 +53,8 @@ HEADERS += \ backends/deconz/zigbeebridgecontrollerdeconz.h \ backends/deconz/zigbeenetworkdeconz.h \ zcl/general/zigbeeclusteronoff.h \ + zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h \ + zcl/measurement/zigbeeclustertemperaturemeasurement.h \ zcl/zigbeecluster.h \ zcl/zigbeeclusterattribute.h \ zcl/zigbeeclusterlibrary.h \ diff --git a/libnymea-zigbee/loggingcategory.cpp b/libnymea-zigbee/loggingcategory.cpp index c20bea5..1fb1848 100644 --- a/libnymea-zigbee/loggingcategory.cpp +++ b/libnymea-zigbee/loggingcategory.cpp @@ -27,10 +27,11 @@ #include "loggingcategory.h" -Q_LOGGING_CATEGORY(dcZigbeeNetwork, "ZigbeeNetwork") -Q_LOGGING_CATEGORY(dcZigbeeNode, "ZigbeeNode") Q_LOGGING_CATEGORY(dcZigbeeAps, "ZigbeeAps") +Q_LOGGING_CATEGORY(dcZigbeeNode, "ZigbeeNode") +Q_LOGGING_CATEGORY(dcZigbeeNetwork, "ZigbeeNetwork") Q_LOGGING_CATEGORY(dcZigbeeCluster, "ZigbeeCluster") +Q_LOGGING_CATEGORY(dcZigbeeEndpoint, "ZigbeeEndpoint") Q_LOGGING_CATEGORY(dcZigbeeInterface, "ZigbeeInterface") Q_LOGGING_CATEGORY(dcZigbeeController, "ZigbeeController") Q_LOGGING_CATEGORY(dcZigbeeDeviceObject, "ZigbeeDeviceObject") diff --git a/libnymea-zigbee/loggingcategory.h b/libnymea-zigbee/loggingcategory.h index 5510b20..c32de56 100644 --- a/libnymea-zigbee/loggingcategory.h +++ b/libnymea-zigbee/loggingcategory.h @@ -31,10 +31,11 @@ #include #include -Q_DECLARE_LOGGING_CATEGORY(dcZigbeeNetwork) -Q_DECLARE_LOGGING_CATEGORY(dcZigbeeNode) Q_DECLARE_LOGGING_CATEGORY(dcZigbeeAps) +Q_DECLARE_LOGGING_CATEGORY(dcZigbeeNode) +Q_DECLARE_LOGGING_CATEGORY(dcZigbeeNetwork) Q_DECLARE_LOGGING_CATEGORY(dcZigbeeCluster) +Q_DECLARE_LOGGING_CATEGORY(dcZigbeeEndpoint) Q_DECLARE_LOGGING_CATEGORY(dcZigbeeInterface) Q_DECLARE_LOGGING_CATEGORY(dcZigbeeController) Q_DECLARE_LOGGING_CATEGORY(dcZigbeeDeviceObject) diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp index 4dc59af..07b1ef1 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp +++ b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.cpp @@ -28,6 +28,8 @@ #include "zigbeeclusterbasic.h" #include "loggingcategory.h" +#include + ZigbeeClusterBasic::ZigbeeClusterBasic(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent) : ZigbeeCluster(network, node, endpoint, Zigbee::ClusterIdBasic, direction, parent) { diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h index 270244b..899792b 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h +++ b/libnymea-zigbee/zcl/general/zigbeeclusterbasic.h @@ -199,6 +199,8 @@ public: explicit ZigbeeClusterBasic(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); + // TODO: reset all clusters to factory defaults command 0x00, optional + private: void setAttribute(const ZigbeeClusterAttribute &attribute) override; diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp index 33a0bf9..d17c1b0 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp +++ b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp @@ -138,6 +138,56 @@ ZigbeeClusterReply *ZigbeeClusterOnOff::commandOn() return zclReply; } +ZigbeeClusterReply *ZigbeeClusterOnOff::commandToggle() +{ + ZigbeeNetworkRequest request = createGeneralRequest(); + + // Build ZCL frame + + // Note: for basic commands the frame control files has to be zero accoring to spec ZCL 2.4.1.1 + ZigbeeClusterLibrary::FrameControl frameControl; + frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific; + frameControl.manufacturerSpecific = false; + frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer; + frameControl.disableDefaultResponse = false; + + // ZCL header + ZigbeeClusterLibrary::Header header; + header.frameControl = frameControl; + header.command = ZigbeeClusterOnOff::CommandToggle; + header.transactionSequenceNumber = m_transactionSequenceNumber++; + + // There is no ZCL payload + + // Put them together + ZigbeeClusterLibrary::Frame frame; + frame.header = header; + + request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission)); + request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame)); + + ZigbeeClusterReply *zclReply = createClusterReply(request, frame); + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){ + if (!verifyNetworkError(zclReply, networkReply)) { + qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request" + << m_node << networkReply->error() + << networkReply->zigbeeApsStatus(); + finishZclReply(zclReply); + return; + } + + // The request was successfully sent to the device + // Now check if the expected indication response received already + if (zclReply->isComplete()) { + finishZclReply(zclReply); + return; + } + }); + + return zclReply; +} + void ZigbeeClusterOnOff::setAttribute(const ZigbeeClusterAttribute &attribute) { if (hasAttribute(attribute.id())) { @@ -153,7 +203,39 @@ void ZigbeeClusterOnOff::setAttribute(const ZigbeeClusterAttribute &attribute) void ZigbeeClusterOnOff::processDataIndication(ZigbeeClusterLibrary::Frame frame) { + qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame; + // Increase the tsn for continuouse id increasing on both sides m_transactionSequenceNumber = frame.header.transactionSequenceNumber; + + switch (m_direction) { + case Client: + // If the client cluster sends data to a server cluster (independent which), the command was executed on the device like button pressed + if (frame.header.frameControl.direction == ZigbeeClusterLibrary::DirectionClientToServer) { + // Read the payload which is + Command command = static_cast(frame.header.command); + qCDebug(dcZigbeeCluster()) << "Command sent from" << m_node << m_endpoint << this << command; + switch (command) { + case CommandOn: + emit commandSent(CommandOn); + break; + case CommandOff: + emit commandSent(CommandOff); + break; + case CommandToggle: + emit commandSent(CommandToggle); + break; + default: + qCWarning(dcZigbeeCluster()) << "Unhandled command sent from" << m_node << m_endpoint << this << command; + break; + } + return; + } + break; + case Server: + // keep it unhandled if not parsed yet in order to warn about the handled indication + break; + } + qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame; } diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h index 4bb571c..2d269df 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h +++ b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h @@ -68,7 +68,7 @@ public: ZigbeeClusterReply *commandOff(); ZigbeeClusterReply *commandOn(); -// ZigbeeClusterReply *commandToggle(); + ZigbeeClusterReply *commandToggle(); // ZigbeeClusterReply *commandOffWithEffect(); // ZigbeeClusterReply *commandOnWithRecallGlobalScene(); // ZigbeeClusterReply *commandOnWithTimedOff(); @@ -79,8 +79,9 @@ private: protected: void processDataIndication(ZigbeeClusterLibrary::Frame frame) override; - signals: + // Note: these signals will only be emitted if the cluster is a client + void commandSent(Command command); }; diff --git a/libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp b/libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp new file mode 100644 index 0000000..97acfee --- /dev/null +++ b/libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp @@ -0,0 +1,6 @@ +#include "zigbeeclusterrelativehumiditymeasurement.h" + +ZigbeeClusterRelativeHumidityMeasurement::ZigbeeClusterRelativeHumidityMeasurement(QObject *parent) : QObject(parent) +{ + +} diff --git a/libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h b/libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h new file mode 100644 index 0000000..a8e9167 --- /dev/null +++ b/libnymea-zigbee/zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h @@ -0,0 +1,16 @@ +#ifndef ZIGBEECLUSTERRELATIVEHUMIDITYMEASUREMENT_H +#define ZIGBEECLUSTERRELATIVEHUMIDITYMEASUREMENT_H + +#include + +class ZigbeeClusterRelativeHumidityMeasurement : public QObject +{ + Q_OBJECT +public: + explicit ZigbeeClusterRelativeHumidityMeasurement(QObject *parent = nullptr); + +signals: + +}; + +#endif // ZIGBEECLUSTERRELATIVEHUMIDITYMEASUREMENT_H diff --git a/libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.cpp b/libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.cpp new file mode 100644 index 0000000..d3da9c4 --- /dev/null +++ b/libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.cpp @@ -0,0 +1,60 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea-zigbee. +* This project including source code and documentation is protected by copyright law, and +* remains the property of nymea GmbH. All rights, including reproduction, publication, +* editing and translation, are reserved. The use of this project is subject to the terms of a +* license agreement to be concluded with nymea GmbH in accordance with the terms +* of use of nymea GmbH, available under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the terms of the GNU +* Lesser General Public License as published by the Free Software Foundation; version 3. +* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#include "zigbeeclustertemperaturemeasurement.h" +#include "zigbeenetworkreply.h" +#include "loggingcategory.h" +#include "zigbeenetwork.h" + +ZigbeeClusterTemperatureMeasurement::ZigbeeClusterTemperatureMeasurement(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent) : + ZigbeeCluster(network, node, endpoint, Zigbee::ClusterIdTemperatureMeasurement, direction, parent) +{ + +} + +void ZigbeeClusterTemperatureMeasurement::setAttribute(const ZigbeeClusterAttribute &attribute) +{ + if (hasAttribute(attribute.id())) { + qCDebug(dcZigbeeCluster()) << "Update attribute" << this << static_cast(attribute.id()) << attribute.dataType(); + m_attributes[attribute.id()] = attribute; + emit attributeChanged(attribute); + } else { + qCDebug(dcZigbeeCluster()) << "Add attribute" << this << static_cast(attribute.id()) << attribute.dataType(); + m_attributes.insert(attribute.id(), attribute); + emit attributeChanged(attribute); + } + + if (attribute.id() == AttributeMeasuredValue) { + bool valueOk = false; + quint16 value = attribute.dataType().toUInt16(&valueOk); + if (valueOk) { + double temperature = value / 100.0; + qCDebug(dcZigbeeCluster()) << "Temperature changed" << temperature << "°C"; + emit temperatureChanged(temperature); + } + } +} diff --git a/libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.h b/libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.h new file mode 100644 index 0000000..5ef7fed --- /dev/null +++ b/libnymea-zigbee/zcl/measurement/zigbeeclustertemperaturemeasurement.h @@ -0,0 +1,69 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* Copyright 2013 - 2020, nymea GmbH +* Contact: contact@nymea.io +* +* This file is part of nymea-zigbee. +* This project including source code and documentation is protected by copyright law, and +* remains the property of nymea GmbH. All rights, including reproduction, publication, +* editing and translation, are reserved. The use of this project is subject to the terms of a +* license agreement to be concluded with nymea GmbH in accordance with the terms +* of use of nymea GmbH, available under https://nymea.io/license +* +* GNU Lesser General Public License Usage +* Alternatively, this project may be redistributed and/or modified under the terms of the GNU +* Lesser General Public License as published by the Free Software Foundation; version 3. +* this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; +* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +* See the GNU Lesser General Public License for more details. +* +* You should have received a copy of the GNU Lesser General Public License along with this project. +* If not, see . +* +* For any further details and any questions please contact us under contact@nymea.io +* or see our FAQ/Licensing Information on https://nymea.io/license/faq +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef ZIGBEECLUSTERTEMPERATUREMEASUREMENT_H +#define ZIGBEECLUSTERTEMPERATUREMEASUREMENT_H + +#include + +#include "zcl/zigbeecluster.h" +#include "zcl/zigbeeclusterreply.h" + +class ZigbeeNode; +class ZigbeeNetwork; +class ZigbeeNodeEndpoint; +class ZigbeeNetworkReply; + +class ZigbeeClusterTemperatureMeasurement : public ZigbeeCluster +{ + Q_OBJECT + + friend class ZigbeeNode; + friend class ZigbeeNetwork; + +public: + enum Attribute { + AttributeMeasuredValue = 0x0000, + AttributeMinMeasuredValue = 0x0001, + AttributeMaxMeasuredValue = 0x0002, + AttributeTolerance = 0x0003 + }; + Q_ENUM(Attribute) + + // No commands + + explicit ZigbeeClusterTemperatureMeasurement(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); + +private: + void setAttribute(const ZigbeeClusterAttribute &attribute) override; + +signals: + void temperatureChanged(double temperature); + +}; + +#endif // ZIGBEECLUSTERTEMPERATUREMEASUREMENT_H diff --git a/libnymea-zigbee/zcl/zigbeecluster.cpp b/libnymea-zigbee/zcl/zigbeecluster.cpp index 09f48a0..127e6a7 100644 --- a/libnymea-zigbee/zcl/zigbeecluster.cpp +++ b/libnymea-zigbee/zcl/zigbeecluster.cpp @@ -105,7 +105,7 @@ ZigbeeClusterReply *ZigbeeCluster::readAttributes(QList attributes) ZigbeeClusterLibrary::FrameControl frameControl; frameControl.frameType = ZigbeeClusterLibrary::FrameTypeGlobal; frameControl.manufacturerSpecific = false; - if (m_direction == Direction::Input) { + if (m_direction == Direction::Server) { frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer; } else { frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient; @@ -222,15 +222,14 @@ void ZigbeeCluster::processDataIndication(ZigbeeClusterLibrary::Frame frame) qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame; } -void ZigbeeCluster::processApsDataIndication(QByteArray payload) +void ZigbeeCluster::processApsDataIndication(const QByteArray &asdu, const ZigbeeClusterLibrary::Frame &frame) { - ZigbeeClusterLibrary::Frame frame = ZigbeeClusterLibrary::parseFrameData(payload); qCDebug(dcZigbeeCluster()) << "Received data indication" << this << frame; // Check if this indication is for a pending reply if (m_pendingReplies.contains(frame.header.transactionSequenceNumber)) { ZigbeeClusterReply *reply = m_pendingReplies.value(frame.header.transactionSequenceNumber); - reply->m_responseData = payload; + reply->m_responseData = asdu; reply->m_responseFrame = frame; reply->m_zclIndicationReceived = true; @@ -240,6 +239,23 @@ void ZigbeeCluster::processApsDataIndication(QByteArray payload) return; } + // Check for server clusters and if this is an attribute report + if (m_direction == Server && frame.header.frameControl.frameType == ZigbeeClusterLibrary::FrameTypeGlobal) { + ZigbeeClusterLibrary::Command globalCommand = static_cast(frame.header.command); + if (globalCommand == ZigbeeClusterLibrary::CommandReportAttributes) { + qCDebug(dcZigbeeCluster()) << "Received attributes report received" << this << frame; + // Read the attribute reports and update/set the attributes + QDataStream stream(frame.payload); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 attributeId = 0; quint8 type = 0; + stream >> attributeId >> type; + ZigbeeDataType dataType = ZigbeeClusterLibrary::readDataType(&stream, static_cast(type)); + setAttribute(ZigbeeClusterAttribute(attributeId, dataType)); + return; + } + } + + // Not for a reply, process the indication processDataIndication(frame); } diff --git a/libnymea-zigbee/zcl/zigbeecluster.h b/libnymea-zigbee/zcl/zigbeecluster.h index bf59bdb..ebf8c04 100644 --- a/libnymea-zigbee/zcl/zigbeecluster.h +++ b/libnymea-zigbee/zcl/zigbeecluster.h @@ -69,8 +69,8 @@ class ZigbeeCluster : public QObject public: enum Direction { - Input, - Output + Server, + Client }; Q_ENUM(Direction) @@ -167,7 +167,7 @@ protected: ZigbeeNodeEndpoint *m_endpoint= nullptr; Zigbee::ClusterId m_clusterId = Zigbee::ClusterIdUnknown; - Direction m_direction = Input; + Direction m_direction = Server; QHash m_attributes; ZigbeeNetworkRequest createGeneralRequest(); @@ -187,7 +187,7 @@ signals: void attributeChanged(const ZigbeeClusterAttribute &attribute); public slots: - void processApsDataIndication(QByteArray payload); + void processApsDataIndication(const QByteArray &asdu, const ZigbeeClusterLibrary::Frame &frame); }; diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp index 46fba7b..7afdb3b 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp @@ -335,19 +335,13 @@ void ZigbeeDeviceObject::finishZdoReply(ZigbeeDeviceObjectReply *zdoReply) zdoReply->finished(); } -void ZigbeeDeviceObject::processApsDataIndication(quint8 destinationEndpoint, quint8 sourceEndpoint, quint16 clusterId, QByteArray payload, quint8 lqi, qint8 rssi) +void ZigbeeDeviceObject::processApsDataIndication(const Zigbee::ApsdeDataIndication &indication) { - Q_UNUSED(destinationEndpoint) - Q_UNUSED(sourceEndpoint) - Q_UNUSED(clusterId) - Q_UNUSED(lqi) - Q_UNUSED(rssi) - // Check if we have a waiting ZDO reply for this data - ZigbeeDeviceProfile::Adpu asdu = ZigbeeDeviceProfile::parseAdpu(payload); + ZigbeeDeviceProfile::Adpu asdu = ZigbeeDeviceProfile::parseAdpu(indication.asdu); ZigbeeDeviceObjectReply *zdoReply = m_pendingReplies.value(asdu.transactionSequenceNumber); - if (zdoReply && clusterId == (zdoReply->request().clusterId() | 0x8000)) { - zdoReply->m_responseData = payload; + if (zdoReply && indication.clusterId == (zdoReply->request().clusterId() | 0x8000)) { + zdoReply->m_responseData = indication.asdu; zdoReply->m_responseAdpu = asdu; zdoReply->m_zdpIndicationReceived = true; if (zdoReply->isComplete()) { diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.h b/libnymea-zigbee/zdo/zigbeedeviceobject.h index 7591993..9606542 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.h +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.h @@ -47,6 +47,13 @@ public: ZigbeeDeviceObjectReply *requestPowerDescriptor(); ZigbeeDeviceObjectReply *requestActiveEndpoints(); ZigbeeDeviceObjectReply *requestSimpleDescriptor(quint8 endpointId); + // TODO: implement other device and service discovery methods + + // End device binding +// ZigbeeDeviceObjectReply *requestBindGroup(quint16 clusterId, quint16 groupAddress, quint8 destinationEndpoint); +// ZigbeeDeviceObjectReply *requestBindShortAddress(); +// ZigbeeDeviceObjectReply *requestBindIeeeAddress(); + // Management request ZigbeeDeviceObjectReply *requestMgmtLeaveNetwork(bool rejoin = false, bool removeChildren = false); @@ -60,16 +67,14 @@ private: quint8 m_transactionSequenceNumber = 0; QHash m_pendingReplies; - // Helper methods + // Helper methods for replies ZigbeeNetworkRequest buildZdoRequest(quint16 zdoRequest); ZigbeeDeviceObjectReply *createZigbeeDeviceObjectReply(const ZigbeeNetworkRequest &request, quint8 transactionSequenceNumber); bool verifyNetworkError(ZigbeeDeviceObjectReply *zdoReply, ZigbeeNetworkReply *networkReply); void finishZdoReply(ZigbeeDeviceObjectReply *zdoReply); -signals: - public slots: - void processApsDataIndication(quint8 destinationEndpoint, quint8 sourceEndpoint, quint16 clusterId, QByteArray payload, quint8 lqi, qint8 rssi); + void processApsDataIndication(const Zigbee::ApsdeDataIndication &indication); }; diff --git a/libnymea-zigbee/zigbee.cpp b/libnymea-zigbee/zigbee.cpp index 02f8fd5..ca8a40f 100644 --- a/libnymea-zigbee/zigbee.cpp +++ b/libnymea-zigbee/zigbee.cpp @@ -26,4 +26,61 @@ * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "zigbee.h" +#include "zigbeeutils.h" +#include "zdo/zigbeedeviceprofile.h" +QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataConfirm &confirm) +{ + debug.nospace() << "APSDE-DATA.confirm("; + debug.nospace() << "Request ID: " << confirm.requestId << ", "; + + if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeGroup) + debug.nospace() << "Group address:" << ZigbeeUtils::convertUint16ToHexString(confirm.destinationShortAddress) << ", "; + + if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) + debug.nospace() << "NWK address:" << ZigbeeUtils::convertUint16ToHexString(confirm.destinationShortAddress) << ", "; + + if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) + debug.nospace() << "IEEE address:" << ZigbeeAddress(confirm.destinationIeeeAddress).toString() << ", "; + + debug.nospace() << "Destination EP:" << ZigbeeUtils::convertByteToHexString(confirm.destinationEndpoint) << ", "; + debug.nospace() << "Source EP:" << ZigbeeUtils::convertByteToHexString(confirm.sourceEndpoint) << ", "; + debug.nospace() << static_cast(confirm.zigbeeStatusCode); + debug.nospace() << ")"; + + return debug.space(); +} + +QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataIndication &indication) +{ + debug.nospace() << "APSDE-DATA.indication("; + if (indication.destinationAddressMode == Zigbee::DestinationAddressModeGroup) + debug.nospace() << "Group address:" << ZigbeeUtils::convertUint16ToHexString(indication.destinationShortAddress) << ", "; + + if (indication.destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) + debug.nospace() << "NWK address:" << ZigbeeUtils::convertUint16ToHexString(indication.destinationShortAddress) << ", "; + + if (indication.destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) + debug.nospace() << "IEEE address:" << ZigbeeAddress(indication.destinationIeeeAddress).toString() << ", "; + + debug.nospace() << "Destination EP:" << ZigbeeUtils::convertByteToHexString(indication.destinationEndpoint) << ", "; + debug.nospace() << "Source EP:" << ZigbeeUtils::convertByteToHexString(indication.sourceEndpoint) << ", "; + + if (indication.sourceAddressMode == Zigbee::SourceAddressModeShortAddress || indication.sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) + debug.nospace() << "Source NWK address:" << ZigbeeUtils::convertUint16ToHexString(indication.sourceShortAddress) << ", "; + + if (indication.sourceAddressMode == Zigbee::SourceAddressModeIeeeAddress || indication.sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) + debug.nospace() << "Source IEEE address:" << ZigbeeAddress(indication.sourceIeeeAddress).toString() << ", "; + + debug.nospace() << static_cast(indication.profileId) << ", "; + if (indication.profileId == static_cast(Zigbee::ZigbeeProfileDevice)) { + debug.nospace() << static_cast(indication.clusterId) << ", "; + } else { + debug.nospace() << static_cast(indication.clusterId) << ", "; + } + + debug.nospace() << "ASDU: " << ZigbeeUtils::convertByteArrayToHexString(indication.asdu) << ", "; + debug.nospace() << "LQI: " << indication.lqi << ", "; + debug.nospace() << "RSSI: " << indication.rssi << "dBm)"; + return debug.space(); +} diff --git a/libnymea-zigbee/zigbee.h b/libnymea-zigbee/zigbee.h index 3c83a41..267a9a0 100644 --- a/libnymea-zigbee/zigbee.h +++ b/libnymea-zigbee/zigbee.h @@ -74,212 +74,6 @@ public: Q_ENUM(ZigbeeChannel) Q_DECLARE_FLAGS(ZigbeeChannels, ZigbeeChannel) -// enum InterfaceMessageType { -// // Common Commands -// MessageTypeNone = 0x0000, -// MessageTypeStatus = 0x8000, -// MessageTypeLogging = 0x8001, - -// MessageTypeDataIndication = 0x8002, - -// MessageTypeNodeClusterList = 0x8003, -// MessageTypeNodeAttributeList = 0x8004, -// MessageTypeNodeCommandIdList = 0x8005, -// MessageTypeRestartProvisioned = 0x8006, -// MessageTypeFactoryNewRestart = 0x8007, -// MessageTypeGetVersion = 0x0010, -// MessageTypeVersionList = 0x8010, - -// MessageTypeSetExtendetPanId = 0x0020, -// MessageTypeSetChannelMask = 0x0021, -// MessageTypeSetSecurity = 0x0022, -// MessageTypeSetDeviceType = 0x0023, -// MessageTypeStartNetwork = 0x0024, -// MessageTypeStartScan = 0x0025, -// MessageTypeNetworkJoinedFormed = 0x8024, -// MessageTypeNetworkRemoveDevice = 0x0026, -// MessageTypeNetworkWhitelistEnable = 0x0027, -// MessageTypeAuthenticateDeviceRequest = 0x0028, -// MessageTypeAuthenticateDeviceResponse = 0x8028, -// MessageTypeOutOfBandCommisioningDataRequest = 0x0029, -// MessageTypeOutOfBandCommisioningDataResponse = 0x8029, -// MessageTypeUserDescriptorSet = 0x002B, -// MessageTypeUserDescriptorNotify = 0x802B, -// MessageTypeUserDescriptorRequest = 0x002C, -// MessageTypeUserDescriptorResponse = 0x802C, - -// MessageTypeReset = 0x0011, -// MessageTypeErasePersistentData = 0x0012, -// MessageTypeZllFactoryNew = 0x0013, -// MessageTypeGetPermitJoining = 0x0014, -// MessageTypeGetPermitJoiningResponse = 0x8014, -// MessageTypeBind = 0x0030, -// MessageTypeBindResponse = 0x8030, -// MessageTypeUnbind = 0x0031, -// MessageTypeBindGroup = 0x0032, -// MessageTypeBindGroupResponse = 0x8032, -// MessageTypeUnbindGroup = 0x0033, -// MessageTypeUnbindGroupResponse = 0x8033, - -// MessageTypeUnbindResponse = 0x8031, -// MessageTypeComplexDescriptorRequest = 0x0034, -// MessageTypeComplexDescriptorResponse = 0x8034, - -// MessageTypeNetworkAdressRequest = 0x0040, -// MessageTypeNetworkAdressResponse = 0x8040, -// MessageTypeIeeeAddressResponse = 0x0041, -// MessageTypeIeeeAddressRequest = 0x8041, -// MessageTypeNodeDescriptorRequest = 0x0042, -// MessageTypeNodeDescriptorRsponse = 0x8042, -// MessageTypeSimpleDescriptorRequest = 0x0043, -// MessageTypeSimpleDescriptorResponse = 0x8043, -// MessageTypePowerDescriptorRequest = 0x0044, -// MessageTypePowerDescriptorResponse = 0x8044, -// MessageTypeActiveEndpointRequest = 0x0045, -// MessageTypeActiveEndpointResponse = 0x8045, -// MessageTypeMatchDescriptorRequest = 0x0046, -// MessageTypeMatchDescriptorResponse = 0x8046, -// MessageTypeManagementLeaveRequest = 0x0047, -// MessageTypeManagementLeaveResponse = 0x8047, -// MessageTypeLeaveIndication = 0x8048, -// MessageTypePermitJoiningRequest = 0x0049, -// MessageTypeManagementNetworkUpdateRequest = 0x004A, -// MessageTypeManagementNetworkUpdateResponse = 0x804A, -// MessageTypeSystemServerDiscoveryRequest = 0x004B, -// MessageTypeSystemServerDiscoveryResponse = 0x804B, -// MessageTypeDeviceAnnounce = 0x004D, -// MessageTypeManagementLqiRequest = 0x004E, -// MessageTypeManagementLqiResponse = 0x804E, - -// // Basic cluster -// MessageBasicResetFactoryDefaults = 0x0050, -// MessageBasicResetFactoryDefaultsResponse = 0x8050, - -// // Group Cluster -// MessageTypeAddGroupRequest = 0x0060, -// MessageTypeAddGroupResponse = 0x8060, -// MessageTypeViewGroupRequest = 0x0061, -// MessageTypeViewGroupResponse = 0x8061, -// MessageTypeGetGroupMembershipRequest = 0x0062, -// MessageTypeGetGroupMembershipResponse = 0x8062, -// MessageTypeRemoveGroupRequest = 0x0063, -// MessageTypeRemoveGroupResponse = 0x8063, -// MessageTypeRemoveAllGroups = 0x0064, -// MessageTypeGroupIfIdentify = 0x0065, - -// // Identify Cluster -// MessageTypeIdentifySend = 0x0070, -// MessageTypeIdentifyQuery = 0x0071, - -// // Level Cluster -// MessageTypeMoveToLevel = 0x0080, -// MessageTypeMoveToLevelOnOff = 0x0081, -// MessageTypeMoveStep = 0x0082, -// MessageTypeMoveStopMove = 0x0083, -// MessageTypeMoveStopMoveOnOff = 0x0084, - -// // Scenes Cluster -// MessageTypeViewScene = 0x00A0, -// MessageTypeViewSceneResponse = 0x80A0, -// MessageTypeAddScene = 0x00A1, -// MessageTypeAddSceneResponse = 0x80A1, -// MessageTypeRemoveScene = 0x00A2, -// MessageTypeRemoveSceneResponse = 0x80A2, -// MessageTypeRemoveAllScenes = 0x00A3, -// MessageTypeRemoveAllScenesResponse = 0x80A3, -// MessageTypeStoreScene = 0x00A4, -// MessageTypeStoreSceneResponse = 0x80A4, -// MessageTypeRecallScene = 0x00A5, -// MessageTypeSceneMembershipRequest = 0x00A6, -// MessageTypeSceneMembershipResponse = 0x80A6, - -// //Colour Cluster -// MessageTypeMoveToHue = 0x00B0, -// MessageTypeMoveHue = 0x00B1, -// MessageTypeStepHue = 0x00B2, -// MessageTypeMoveToSaturation = 0x00B3, -// MessageTypeMoveSaturation = 0x00B4, -// MessageTypeStepStaturation = 0x00B5, -// MessageTypeMoveToHueSaturation = 0x00B6, -// MessageTypeMoveToColor = 0x00B7, -// MessageTypeMoveColor = 0x00B8, -// MessageTypeStepColor = 0x00B9, - -// // ZLL Commands -// // Touchlink -// MessageTypeInitiateTouchlink = 0x00D0, -// MessageTypeTouchlinkStatus = 0x00D1, -// MessageTypeTouchlinkFactoryReset = 0x00D2, - -// // Identify Cluster -// MessageTypeIdentifyTriggerEffect = 0x00E0, - -// // On/Off Cluster -// MessageTypeCluserOnOff = 0x0092, -// MessageTypeCluserOnOffTimed = 0x0093, -// MessageTypeCluserOnOffEffects = 0x0094, -// MessageTypeCluserOnOffUpdate = 0x8095, - -// // Scenes Cluster -// MessageTypeAddEnhancedScene = 0x00A7, -// MessageTypeViewEnhancedScene = 0x00A8, -// MessageTypeCopyScene = 0x00A9, - -// // Colour Cluster -// MessageTypeEnhancedMoveToHue = 0x00BA, -// MessageTypeEnhancedMoveHue = 0x00BB, -// MessageTypeEnhancedStepHue = 0x00BC, -// MessageTypeEnhancedMoveToHueSaturation = 0x00BD, -// MessageTypeColourLoopSet = 0x00BE, -// MessageTypeStopMoveStep = 0x00BF, -// MessageTypeMoveToColorTemperature = 0x00C0, -// MessageTypeMoveColorTemperature = 0x00C1, -// MessageTypeStepColorTemperature = 0x00C2, - -// // ZHA Commands -// // Door Lock Cluster -// MessageTypeLockUnlockDoor = 0x00F0, - -// // Attributes -// MessageTypeReadAttributeRequest = 0x0100, -// MessageTypeReadAttributeResponse = 0x8100, -// MessageTypeDefaultResponse = 0x8101, -// MessageTypeAttributeReport = 0x8102, -// MessageTypeWriteAttributeRequest = 0x0110, -// MessageTypeWriteAttributeResponse = 0x8110, -// MessageTypeConfigReportingRequest = 0x0120, -// MessageTypeConfigReportingResponse = 0x8120, -// MessageTypeReportAttributes = 0x8121, -// MessageTypeAttributeDiscoveryRequest = 0x0140, -// MessageTypeAttributeDiscoveryResponse = 0x8140, - -// // Persistant data manager messages -// MessageTypeDataManagerAvailableRequest = 0x0300, -// MessageTypeDataManagerAvailableResponse = 0x8300, -// MessageTypeDataManagerSaveRecordRequest = 0x0200, -// MessageTypeDataManagerSaveRecordResponse = 0x8200, -// MessageTypeDataManagerLoadRecordRequest = 0x0201, -// MessageTypeDataManagerLoadRecordResponse = 0x8201, -// MessageTypeDataManagerDeleteAllRecordsRequest = 0x0202, -// MessageTypeDataManagerDeleteAllRecordsResponse = 0x8202, - -// // Appliance Statistics Cluster 0x0B03 -// // http://www.nxp.com/documents/user_manual/JN-UG-3076.pdf -// MessageTypeStatisticsClusterLogMessage = 0x0301, // Was 0x0500, was 0x0301 -// MessageTypeStatisticsClusterLogMessageResponse = 0x8301, - -// // IAS Cluster -// MessageTypeSendIasZoneEnroolResponse = 0x0400, -// MessageTypeIasZoneStatusChangeNotify = 0x8401, - -// // Extended utils -// MessageTypeRawApsDataRequest = 0x0530, -// MessageTypeRouterDiscoveryConfirm = 0x8701, -// MessageTypeApsDataConfirmFail = 0x8702 -// }; -// Q_ENUM(InterfaceMessageType) - - enum ClusterId { // Basics ClusterIdUnknown = 0xffff, @@ -677,6 +471,34 @@ public: }; Q_ENUM(ZigbeeStatus) + // Basic struct for interface data. + typedef struct ApsdeDataConfirm { + quint8 requestId = 0; + quint8 destinationAddressMode = Zigbee::DestinationAddressModeShortAddress; + quint16 destinationShortAddress = 0; + quint64 destinationIeeeAddress = 0; + quint8 destinationEndpoint = 0; + quint8 sourceEndpoint = 0; + quint8 zigbeeStatusCode = 0; + } ApsdeDataConfirm; + + typedef struct ApsdeDataIndication { + quint8 destinationAddressMode = 0; + quint16 destinationShortAddress = 0; + quint64 destinationIeeeAddress = 0; + quint8 destinationEndpoint = 0; + quint8 sourceAddressMode = 0; + quint16 sourceShortAddress = 0; + quint64 sourceIeeeAddress = 0; + quint8 sourceEndpoint = 0; + quint16 profileId = 0; + quint16 clusterId = 0; + QByteArray asdu; + quint8 lqi = 0; + qint8 rssi = 0; + } ApsdeDataIndication; + + ///* Manufacturer Codes */ ///* Codes less than 0x1000 were issued for RF4CE */ //#define ZBEE_MFG_CODE_PANASONIC_RF4CE 0x0001 @@ -1371,6 +1193,9 @@ public: }; +QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataConfirm &confirm); +QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataIndication &indication); + Q_DECLARE_OPERATORS_FOR_FLAGS(Zigbee::ZigbeeChannels) Q_DECLARE_OPERATORS_FOR_FLAGS(Zigbee::ZigbeeTxOptions) diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 0eb6d61..8296345 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -351,7 +351,7 @@ void ZigbeeNetwork::loadNetwork() for (int n = 0; n < inputClustersCount; n ++) { settings.setArrayIndex(n); Zigbee::ClusterId clusterId = static_cast(settings.value("clusterId", 0).toUInt()); - ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Input); + ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Server); //qCDebug(dcZigbeeNetwork()) << "Created" << cluster; endpoint->m_inputClusters.insert(clusterId, cluster); } @@ -361,7 +361,7 @@ void ZigbeeNetwork::loadNetwork() for (int n = 0; n < outputClustersCount; n ++) { settings.setArrayIndex(n); Zigbee::ClusterId clusterId = static_cast(settings.value("clusterId", 0).toUInt()); - ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Output); + ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Client); //qCDebug(dcZigbeeNetwork()) << "Created" << cluster; endpoint->m_outputClusters.insert(clusterId, cluster); } diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index ab28612..b9ef143 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -396,24 +396,34 @@ void ZigbeeNode::startInitialization() void ZigbeeNode::initNodeDescriptor() { + qCDebug(dcZigbeeNode()) << "Requst node descriptor from" << this; ZigbeeDeviceObjectReply *reply = deviceObject()->requestNodeDescriptor(); connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read node descriptor" << reply->error(); - // FIXME: decide what to do, retry or stop initialization + m_requestRetry++; + if (m_requestRetry < 3) { + qCDebug(dcZigbeeNode()) << "Retry to request node descriptor" << m_requestRetry << "/" << "3"; + initNodeDescriptor(); + } else { + qCWarning(dcZigbeeNode()) << "Failed to read node descriptor from" << this << "after 3 attempts. Giving up."; + m_requestRetry = 0; + // FIXME: decide what to do, remove the node again from network + } return; } + // The request finished, but we received a ZDP error. if (reply->responseAdpu().status != ZigbeeDeviceProfile::StatusSuccess) { qCWarning(dcZigbeeNode()) << this << "failed to read node descriptor" << reply->responseAdpu().status; - // FIXME: decide what to do, retry or stop initialization + // FIXME: decide what to do, remove the node again from network return; } qCDebug(dcZigbeeNode()) << this << "reading node descriptor finished successfully."; + m_requestRetry = 0; // Parse and set the node descriptor FIXME: make it nicer using the data types - QDataStream stream(reply->responseAdpu().payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 typeDescriptorFlag = 0; quint8 frequencyFlag = 0; quint8 macCapabilities = 0; @@ -488,18 +498,27 @@ void ZigbeeNode::initPowerDescriptor() connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read power descriptor" << reply->error(); - // FIXME: decide what to do, retry or stop initialization + if (m_requestRetry < 3) { + m_requestRetry++; + qCDebug(dcZigbeeNode()) << "Retry to request power descriptor from" << this << m_requestRetry << "/" << "3 attempts."; + initPowerDescriptor(); + } else { + qCWarning(dcZigbeeNode()) << "Failed to read power descriptor from" << this << "after 3 attempts. Giving up."; + m_requestRetry = 0; + // FIXME: decide what to do, remove the node again from network or continue with active endpoint request + } return; } ZigbeeDeviceProfile::Adpu adpu = reply->responseAdpu(); if (adpu.status != ZigbeeDeviceProfile::StatusSuccess) { - qCWarning(dcZigbeeNode()) << this << "failed to read node descriptor" << adpu.status; - // FIXME: decide what to do, retry or stop initialization + qCWarning(dcZigbeeNode()) << "Failed to read power descriptor from" << this << adpu.status; + // FIXME: decide what to do, remove the node again from network or continue without powerdescriptor return; } qCDebug(dcZigbeeNode()) << this << "reading power descriptor finished successfully."; + m_requestRetry = 0; QDataStream stream(adpu.payload); stream.setByteOrder(QDataStream::LittleEndian); @@ -518,17 +537,26 @@ void ZigbeeNode::initEndpoints() connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read active endpoints" << reply->error(); - // FIXME: decide what to do, retry or stop initialization + if (m_requestRetry < 3) { + m_requestRetry++; + qCDebug(dcZigbeeNode()) << "Retry to request active endpoints from" << this << m_requestRetry << "/" << "3 attempts."; + initEndpoints(); + } else { + qCWarning(dcZigbeeNode()) << "Failed to read active endpoints from" << this << "after 3 attempts. Giving up."; + m_requestRetry = 0; + // FIXME: decide what to do, remove the node again from network + } return; } if (reply->responseAdpu().status != ZigbeeDeviceProfile::StatusSuccess) { - qCWarning(dcZigbeeNode()) << this << "failed to read active endpoints" << reply->responseAdpu().status; + qCWarning(dcZigbeeNode()) << "Failed to read active endpoints" << reply->responseAdpu().status; // FIXME: decide what to do, retry or stop initialization return; } qCDebug(dcZigbeeNode()) << this << "reading active endpoints finished successfully."; + m_requestRetry = 0; QDataStream stream(reply->responseAdpu().payload); stream.setByteOrder(QDataStream::LittleEndian); @@ -549,25 +577,31 @@ void ZigbeeNode::initEndpoints() // If there a no endpoints or all endpoints have already be initialized, continue with reading the basic cluster information if (m_uninitializedEndpoints.isEmpty()) { initBasicCluster(); + return; } - // Read simple descriptor for each uninitialized endpoint - for (int i = 0; i < m_uninitializedEndpoints.count(); i++) { - quint8 endpointId = m_uninitializedEndpoints.at(i); - qCDebug(dcZigbeeNode()) << "Read simple descriptor of endpoint" << ZigbeeUtils::convertByteToHexString(endpointId); - initEndpoint(endpointId); - } + // Start reading simple descriptors sequentially + initEndpoint(m_uninitializedEndpoints.first()); }); } void ZigbeeNode::initEndpoint(quint8 endpointId) { + qCDebug(dcZigbeeNode()) << "Read simple descriptor of endpoint" << ZigbeeUtils::convertByteToHexString(endpointId); ZigbeeDeviceObjectReply *reply = deviceObject()->requestSimpleDescriptor(endpointId); connect(reply, &ZigbeeDeviceObjectReply::finished, this, [this, reply, endpointId](){ if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read simple descriptor for endpoint" << endpointId << reply->error(); - // FIXME: decide what to do, retry or stop initialization + if (m_requestRetry < 3) { + m_requestRetry++; + qCDebug(dcZigbeeNode()) << "Retry to request simple descriptor from" << this << ZigbeeUtils::convertByteToHexString(endpointId) << m_requestRetry << "/" << "3 attempts."; + initEndpoints(); + } else { + qCWarning(dcZigbeeNode()) << "Failed to read simple descriptor from" << this << ZigbeeUtils::convertByteToHexString(endpointId) << "after 3 attempts. Giving up."; + m_requestRetry = 0; + // FIXME: decide what to do, remove the node again from network + } return; } @@ -578,6 +612,7 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) } qCDebug(dcZigbeeNode()) << this << "reading simple descriptor for endpoint" << endpointId << "finished successfully."; + m_requestRetry = 0; quint8 length = 0; quint8 endpointId = 0; quint16 profileId = 0; quint16 deviceId = 0; quint8 deviceVersion = 0; quint8 inputClusterCount = 0; quint8 outputClusterCount = 0; @@ -614,32 +649,34 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) endpoint->setDeviceId(deviceId); endpoint->setDeviceVersion(deviceVersion); + // Parse and add server clusters qCDebug(dcZigbeeNode()) << " Input clusters: (" << inputClusterCount << ")"; for (int i = 0; i < inputClusterCount; i++) { quint16 clusterId = 0; stream >> clusterId; if (!endpoint->hasInputCluster(static_cast(clusterId))) { - endpoint->addInputCluster(endpoint->createCluster(static_cast(clusterId), ZigbeeCluster::Input)); + endpoint->addInputCluster(endpoint->createCluster(static_cast(clusterId), ZigbeeCluster::Server)); } qCDebug(dcZigbeeNode()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); } + // Parse and add client clusters stream >> outputClusterCount; - qCDebug(dcZigbeeNode()) << " Output clusters: (" << outputClusterCount << ")"; for (int i = 0; i < outputClusterCount; i++) { quint16 clusterId = 0; stream >> clusterId; if (!endpoint->hasOutputCluster(static_cast(clusterId))) { - endpoint->addOutputCluster(endpoint->createCluster(static_cast(clusterId), ZigbeeCluster::Output)); + endpoint->addOutputCluster(endpoint->createCluster(static_cast(clusterId), ZigbeeCluster::Client)); } qCDebug(dcZigbeeNode()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); } m_uninitializedEndpoints.removeAll(endpointId); + endpoint->m_initialized = true; if (m_uninitializedEndpoints.isEmpty()) { - + // Note: if we are initializing the coordinator, we can stop here if (m_shortAddress == 0) { setState(StateInitialized); return; @@ -647,21 +684,49 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) // Continue with the basic cluster attributes initBasicCluster(); + } else { + // Fetch next endpoint + initEndpoint(m_uninitializedEndpoints.first()); } }); } void ZigbeeNode::initBasicCluster() { + // FIXME: check if we want to read from all endpoints the basic cluster information or only from the first ZigbeeClusterBasic *basicCluster = m_endpoints.first()->inputCluster(Zigbee::ClusterIdBasic); - if (!basicCluster) { - qCWarning(dcZigbeeNode()) << this << "could not find basic server cluster"; + qCWarning(dcZigbeeNode()) << "Could not find basic cluster on" << this << "Set the node to initialized anyways."; + // Set the device initialized any ways since this ist just for convinience setState(StateInitialized); return; } + // Start reading basic cluster attributes sequentially + readManufacturerName(basicCluster); +} + +void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster) +{ ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeManufacturerName; + if (basicCluster->hasAttribute(attributeId)) { + // Note: only read the basic cluster information if we don't have them already from an indication. + // Some devices (Lumi/Aquara) send cluster information containing different payload than a read attribute returns. + // This is bad device stack implementation, but we want to make it work either way without destroying the correct + // workflow as specified by the stack. + qCDebug(dcZigbeeNode()) << "The manufacturer name has already been set" << this << "Continue with model identifier"; + bool valueOk = false; + QString manufacturerName = basicCluster->attribute(attributeId).dataType().toString(&valueOk); + if (valueOk) { + m_endpoints.first()->m_manufacturerName = manufacturerName; + } else { + qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << basicCluster->attribute(attributeId).dataType(); + } + + readModelIdentifier(basicCluster); + return; + } + qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ @@ -684,64 +749,106 @@ void ZigbeeNode::initBasicCluster() } } - ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeModelIdentifier; - qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; - ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); - connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ - if (reply->error() != ZigbeeClusterReply::ErrorNoError) { - qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); - } else { - qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; - QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); - if (!attributeStatusRecords.isEmpty()) { - ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); - qCDebug(dcZigbeeNode()) << attributeStatusRecord; - basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); - bool valueOk = false; - QString modelIdentifier = attributeStatusRecord.dataType.toString(&valueOk); - if (valueOk) { - m_endpoints.first()->m_modelIdentifier = modelIdentifier; - } else { - qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << attributeStatusRecord.dataType; - } - } - } - - ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeSwBuildId; - qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; - ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); - connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ - if (reply->error() != ZigbeeClusterReply::ErrorNoError) { - qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); - } else { - qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; - QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); - if (!attributeStatusRecords.isEmpty()) { - ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); - qCDebug(dcZigbeeNode()) << attributeStatusRecord; - basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); - bool valueOk = false; - QString softwareBuildId = attributeStatusRecord.dataType.toString(&valueOk); - if (valueOk) { - m_endpoints.first()->m_softwareBuildId = softwareBuildId; - } else { - qCWarning(dcZigbeeNode()) << "Could not convert software build id attribute data to string" << attributeStatusRecord.dataType; - } - } - } - - // Finished with reading basic cluster, the node is initialized. TODO: read other cluster information - setState(StateInitialized); - }); - }); + // Continue eiterh way with attribute reading + readModelIdentifier(basicCluster); }); } -void ZigbeeNode::onClusterAttributeChanged(const ZigbeeClusterAttribute &attribute) +void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster) { - ZigbeeCluster *cluster = static_cast(sender()); - qCDebug(dcZigbeeNode()) << "Cluster" << cluster << "attribute changed" << attribute; - emit clusterAttributeChanged(cluster, attribute); + ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeModelIdentifier; + if (basicCluster->hasAttribute(attributeId)) { + // Note: only read the basic cluster information if we don't have them already from an indication. + // Some devices (Lumi/Aquara) send cluster information containing different payload than a read attribute returns. + // This is bad device stack implementation, but we want to make it work either way without destroying the correct + // workflow as specified by the stack. + qCDebug(dcZigbeeNode()) << "The model identifier has already been set" << this << "Continue with software build ID."; + bool valueOk = false; + QString modelIdentifier = basicCluster->attribute(attributeId).dataType().toString(&valueOk); + if (valueOk) { + m_endpoints.first()->m_modelIdentifier= modelIdentifier; + } else { + qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << basicCluster->attribute(attributeId).dataType(); + } + + readSoftwareBuildId(basicCluster); + return; + } + + qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; + ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); + connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ + if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); + } else { + qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; + QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); + if (!attributeStatusRecords.isEmpty()) { + ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); + qCDebug(dcZigbeeNode()) << attributeStatusRecord; + basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); + bool valueOk = false; + QString modelIdentifier = attributeStatusRecord.dataType.toString(&valueOk); + if (valueOk) { + m_endpoints.first()->m_modelIdentifier = modelIdentifier; + } else { + qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << attributeStatusRecord.dataType; + } + } + } + + // Continue eiterh way with attribute reading + readSoftwareBuildId(basicCluster); + }); +} + +void ZigbeeNode::readSoftwareBuildId(ZigbeeClusterBasic *basicCluster) +{ + ZigbeeClusterBasic::Attribute attributeId = ZigbeeClusterBasic::AttributeSwBuildId; + qCDebug(dcZigbeeNode()) << "Reading attribute" << attributeId; + ZigbeeClusterReply *reply = basicCluster->readAttributes({static_cast(attributeId)}); + connect(reply, &ZigbeeClusterReply::finished, this, [this, basicCluster, reply, attributeId](){ + if (reply->error() != ZigbeeClusterReply::ErrorNoError) { + qCWarning(dcZigbeeNode()) << "Error occured during initialization of" << this << "Failed to read basic cluster attribute" << attributeId << reply->error(); + } else { + qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully"; + QList attributeStatusRecords = ZigbeeClusterLibrary::parseAttributeStatusRecords(reply->responseFrame().payload); + if (!attributeStatusRecords.isEmpty()) { + ZigbeeClusterLibrary::ReadAttributeStatusRecord attributeStatusRecord = attributeStatusRecords.first(); + qCDebug(dcZigbeeNode()) << attributeStatusRecord; + basicCluster->setAttribute(ZigbeeClusterAttribute(static_cast(attributeId), attributeStatusRecord.dataType)); + bool valueOk = false; + QString softwareBuildId = attributeStatusRecord.dataType.toString(&valueOk); + if (valueOk) { + m_endpoints.first()->m_softwareBuildId = softwareBuildId; + } else { + qCWarning(dcZigbeeNode()) << "Could not convert software build id attribute data to string" << attributeStatusRecord.dataType; + } + } + } + + // Finished with reading basic cluster, the node is initialized. + // TODO: read other interesting cluster information + setState(StateInitialized); + }); +} + +void ZigbeeNode::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication) +{ + qCDebug(dcZigbeeNode()) << "Processing ZCL indication" << indication; + + // Get the endpoint + ZigbeeNodeEndpoint *endpoint = getEndpoint(indication.sourceEndpoint); + if (!endpoint) { + qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized endpoint. There is no such endpoint on" << this << "Creating the uninitialized endpoint"; + // Create the uninitialized endpoint for now and fetch information later + endpoint = new ZigbeeNodeEndpoint(m_network, this, indication.sourceEndpoint, this); + endpoint->setProfile(static_cast(indication.profileId)); + // Note: the endpoint is not initializd yet, but keep it anyways + m_endpoints.append(endpoint); + } + + endpoint->handleZigbeeClusterLibraryIndication(indication); } QDebug operator<<(QDebug debug, ZigbeeNode *node) diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index 9702df2..eb4908c 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -275,14 +275,19 @@ private: //virtual void setClusterAttributeReport(const ZigbeeClusterAttributeReport &report) = 0; // Init methods + int m_requestRetry = 0; + QList m_uninitializedEndpoints; void initNodeDescriptor(); void initPowerDescriptor(); void initEndpoints(); void initEndpoint(quint8 endpointId); - void initBasicCluster(); - QList m_uninitializedEndpoints; - QList m_uninitalizedBasicClusterAttributes; + // For convinience and having base information about the first endpoint + void initBasicCluster(); + void readManufacturerName(ZigbeeClusterBasic *basicCluster); + void readModelIdentifier(ZigbeeClusterBasic *basicCluster); + void readSoftwareBuildId(ZigbeeClusterBasic *basicCluster); + signals: void stateChanged(State state); @@ -290,8 +295,8 @@ signals: void clusterAdded(ZigbeeCluster *cluster); void clusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute); -private slots: - void onClusterAttributeChanged(const ZigbeeClusterAttribute &attribute); +public slots: + void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication); }; diff --git a/libnymea-zigbee/zigbeenodeendpoint.cpp b/libnymea-zigbee/zigbeenodeendpoint.cpp index e0c2053..3e5af01 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.cpp +++ b/libnymea-zigbee/zigbeenodeendpoint.cpp @@ -72,6 +72,11 @@ void ZigbeeNodeEndpoint::setDeviceVersion(quint8 deviceVersion) m_deviceVersion = deviceVersion; } +bool ZigbeeNodeEndpoint::initialized() const +{ + return m_initialized; +} + QString ZigbeeNodeEndpoint::manufacturerName() const { return m_manufacturerName; @@ -118,7 +123,12 @@ ZigbeeNodeEndpoint::ZigbeeNodeEndpoint(ZigbeeNetwork *network, ZigbeeNode *node, m_node(node), m_endpointId(endpointId) { + qCDebug(dcZigbeeEndpoint()) << "Creating endpoint" << m_endpointId << "on" << m_node; +} +ZigbeeNodeEndpoint::~ZigbeeNodeEndpoint() +{ + qCDebug(dcZigbeeEndpoint()) << "Destroy endpoint" << m_endpointId << "on" << m_node; } void ZigbeeNodeEndpoint::setManufacturerName(const QString &manufacturerName) @@ -157,7 +167,11 @@ ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(Zigbee::ClusterId clusterId, Zi case Zigbee::ClusterIdOnOff: return new ZigbeeClusterOnOff(m_network, m_node, this, direction, this); break; + case Zigbee::ClusterIdTemperatureMeasurement: + return new ZigbeeClusterTemperatureMeasurement(m_network, m_node, this, direction, this); + break; default: + // Return a default cluster since we have no special implementation for this cluster return new ZigbeeCluster(m_network, m_node, this, clusterId, direction, this); } } @@ -172,6 +186,37 @@ void ZigbeeNodeEndpoint::addOutputCluster(ZigbeeCluster *cluster) m_outputClusters.insert(cluster->clusterId(), cluster); } +void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication) +{ + ZigbeeClusterLibrary::Frame frame = ZigbeeClusterLibrary::parseFrameData(indication.asdu); + qCDebug(dcZigbeeEndpoint()) << "Processing ZCL indication" << this << indication << frame; + + // Check which kind of cluster sent this inidication, server or client + ZigbeeCluster *cluster = nullptr; + switch (frame.header.frameControl.direction) { + case ZigbeeClusterLibrary::DirectionClientToServer: + // Get the output/client cluster this indication is coming from + cluster = getOutputCluster(static_cast(indication.clusterId)); + if (!cluster) { + cluster = createCluster(static_cast(indication.clusterId), ZigbeeCluster::Client); + qCWarning(dcZigbeeEndpoint()) << "Received a ZCL indication for a cluster which does not exist yet on" << m_node << this << "Creating" << cluster; + m_outputClusters.insert(cluster->clusterId(), cluster); + } + break; + case ZigbeeClusterLibrary::DirectionServerToClient: + // Get the input/server cluster this indication is coming from + cluster = getInputCluster(static_cast(indication.clusterId)); + if (!cluster) { + cluster = createCluster(static_cast(indication.clusterId), ZigbeeCluster::Server); + qCWarning(dcZigbeeEndpoint()) << "Received a ZCL indication for a cluster which does not exist yet on" << m_node << this << "Creating" << cluster; + m_inputClusters.insert(cluster->clusterId(), cluster); + } + break; + } + + cluster->processApsDataIndication(indication.asdu, frame); +} + ZigbeeCluster *ZigbeeNodeEndpoint::getOutputCluster(Zigbee::ClusterId clusterId) const { return m_outputClusters.value(clusterId); diff --git a/libnymea-zigbee/zigbeenodeendpoint.h b/libnymea-zigbee/zigbeenodeendpoint.h index 652a41d..9578488 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.h +++ b/libnymea-zigbee/zigbeenodeendpoint.h @@ -34,9 +34,11 @@ #include "zigbeeaddress.h" #include "zigbeenetworkreply.h" +// Import all implemented cluster types #include "zcl/zigbeecluster.h" #include "zcl/general/zigbeeclusterbasic.h" #include "zcl/general/zigbeeclusteronoff.h" +#include "zcl/measurement/zigbeeclustertemperaturemeasurement.h" class ZigbeeNode; class ZigbeeNetwork; @@ -62,17 +64,19 @@ public: quint8 deviceVersion() const; void setDeviceVersion(quint8 deviceVersion); + bool initialized() const; + // Basic cluster information QString manufacturerName() const; QString modelIdentifier() const; QString softwareBuildId() const; - // Input clusters + // Server clusters QList inputClusters() const; ZigbeeCluster *getInputCluster(Zigbee::ClusterId clusterId) const; bool hasInputCluster(Zigbee::ClusterId clusterId) const; - // Output clusters + // Client clusters QList outputClusters() const; ZigbeeCluster *getOutputCluster(Zigbee::ClusterId clusterId) const; bool hasOutputCluster(Zigbee::ClusterId clusterId) const; @@ -97,6 +101,7 @@ public: private: explicit ZigbeeNodeEndpoint(ZigbeeNetwork *network, ZigbeeNode *node, quint8 endpointId, QObject *parent = nullptr); + ~ZigbeeNodeEndpoint(); ZigbeeNetwork *m_network = nullptr; ZigbeeNode *m_node = nullptr; @@ -104,6 +109,7 @@ private: Zigbee::ZigbeeProfile m_profile = Zigbee::ZigbeeProfileLightLink; quint16 m_deviceId = 0; quint8 m_deviceVersion = 0; + bool m_initialized = false; QHash m_inputClusters; QHash m_outputClusters; @@ -124,6 +130,8 @@ private: void addInputCluster(ZigbeeCluster *cluster); void addOutputCluster(ZigbeeCluster *cluster); + void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication); + signals: void clusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute);