diff --git a/libnymea-zigbee/backends/nxp/interface/nxp.h b/libnymea-zigbee/backends/nxp/interface/nxp.h index 9869aa1..5c80df3 100644 --- a/libnymea-zigbee/backends/nxp/interface/nxp.h +++ b/libnymea-zigbee/backends/nxp/interface/nxp.h @@ -57,6 +57,7 @@ public: NotificationNetworkStarted = 0x7E, NotificationApsDataConfirm = 0x80, NotificationApsDataIndication = 0x81, + NotificationApsDataAck = 0x82, NotificationNodeJoined = 0x90, NotificationNodeLeft = 0x91, NotificationDebugMessage = 0xFE diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp index 925bfc5..df01fea 100644 --- a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp @@ -511,11 +511,25 @@ void ZigbeeBridgeControllerNxp::onInterfacePackageReceived(const QByteArray &pac stream >> confirm.sourceEndpoint >> confirm.destinationEndpoint >> confirm.zigbeeStatusCode; qCDebug(dcZigbeeController()) << confirm; - qCDebug(dcZigbeeAps()) << "APSDE-DATA.confirm" << confirm; + qCDebug(dcZigbeeAps()) << confirm; emit apsDataConfirmReceived(confirm); break; } + case Nxp::NotificationApsDataAck: { + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + Zigbee::ApsdeDataAck acknowledgement; + stream >> acknowledgement.requestId >> acknowledgement.zigbeeStatusCode >> acknowledgement.sourceEndpoint; + stream >> acknowledgement.destinationAddressMode >> acknowledgement.destinationAddress >> acknowledgement.destinationEndpoint; + stream >> acknowledgement.profileId >> acknowledgement.clusterId; + + qCDebug(dcZigbeeController()) << acknowledgement; + qCDebug(dcZigbeeAps()) << acknowledgement; + + emit apsDataAckReceived(acknowledgement); + break; + } case Nxp::NotificationApsDataIndication: { QDataStream stream(&payload, QIODevice::ReadOnly); stream.setByteOrder(QDataStream::LittleEndian); diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp index e9f37d1..ae14c96 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp @@ -535,7 +535,7 @@ void ZigbeeNetworkNxp::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm & { ZigbeeNetworkReply *reply = m_pendingReplies.value(confirm.requestId); if (!reply) { - qCDebug(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation"; + qCDebug(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation" << confirm; return; } @@ -554,6 +554,15 @@ void ZigbeeNetworkNxp::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndica handleZigbeeClusterLibraryIndication(indication); } +void ZigbeeNetworkNxp::onApsDataAckReceived(const Zigbee::ApsdeDataAck &acknowledgement) +{ + ZigbeeNetworkReply *reply = m_pendingReplies.value(acknowledgement.requestId); + if (reply && reply->buffered()) { + qCDebug(dcZigbeeNetwork()) << "Buffered frame from network request has been acknowledged" << acknowledgement; + setReplyResponseError(reply, static_cast(acknowledgement.zigbeeStatusCode)); + } +} + void ZigbeeNetworkNxp::onNodeLeftIndication(const ZigbeeAddress &ieeeAddress, bool rejoining) { qCDebug(dcZigbeeNetwork()) << "Received node left indication" << ieeeAddress.toString() << "rejoining:" << rejoining; diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h index 433a070..5c39c27 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h @@ -71,6 +71,7 @@ private slots: void onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); void onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); + void onApsDataAckReceived(const Zigbee::ApsdeDataAck &acknowledgement); void onNodeLeftIndication(const ZigbeeAddress &ieeeAddress, bool rejoining); diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index 0575a11..c40284a 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -37,6 +37,7 @@ SOURCES += \ zcl/general/zigbeeclustermultistatevalue.cpp \ zcl/general/zigbeeclusteronoff.cpp \ zcl/general/zigbeeclusterpowerconfiguration.cpp \ + zcl/general/zigbeeclusterscenes.cpp \ zcl/hvac/zigbeeclusterthermostat.cpp \ zcl/lighting/zigbeeclustercolorcontrol.cpp \ zcl/measurement/zigbeeclusterilluminancemeasurment.cpp \ @@ -103,6 +104,7 @@ HEADERS += \ zcl/general/zigbeeclustermultistatevalue.h \ zcl/general/zigbeeclusteronoff.h \ zcl/general/zigbeeclusterpowerconfiguration.h \ + zcl/general/zigbeeclusterscenes.h \ zcl/hvac/zigbeeclusterthermostat.h \ zcl/lighting/zigbeeclustercolorcontrol.h \ zcl/measurement/zigbeeclusterilluminancemeasurment.h \ diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterlevelcontrol.h b/libnymea-zigbee/zcl/general/zigbeeclusterlevelcontrol.h index 62b2bd6..0f6f346 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusterlevelcontrol.h +++ b/libnymea-zigbee/zcl/general/zigbeeclusterlevelcontrol.h @@ -71,7 +71,7 @@ public: enum MoveMode { MoveModeUp = 0x00, - ModeModeDown = 0x01 + MoveModeDown = 0x01 }; Q_ENUM(MoveMode) diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterscenes.cpp b/libnymea-zigbee/zcl/general/zigbeeclusterscenes.cpp new file mode 100644 index 0000000..3d04521 --- /dev/null +++ b/libnymea-zigbee/zcl/general/zigbeeclusterscenes.cpp @@ -0,0 +1,65 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeclusterscenes.h" +#include "loggingcategory.h" +#include "zigbeeutils.h" + +#include + +ZigbeeClusterScenes::ZigbeeClusterScenes(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, ZigbeeCluster::Direction direction, QObject *parent) : + ZigbeeCluster(network, node, endpoint, ZigbeeClusterLibrary::ClusterIdScenes, direction, parent) +{ + +} + +void ZigbeeClusterScenes::setAttribute(const ZigbeeClusterAttribute &attribute) +{ + qCDebug(dcZigbeeCluster()) << "Update attribute" << m_node << m_endpoint << this << static_cast(attribute.id()) << attribute.dataType(); + updateOrAddAttribute(attribute); +} + +void ZigbeeClusterScenes::processDataIndication(ZigbeeClusterLibrary::Frame frame) +{ + qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame; + + // Increase the tsn for continuous id increasing on both sides + m_transactionSequenceNumber = frame.header.transactionSequenceNumber; + + switch (m_direction) { + case Client: { + // Read the payload which is + Command command = static_cast(frame.header.command); + qCDebug(dcZigbeeCluster()) << "Received" << command << "from" << m_node << m_endpoint << this << ZigbeeUtils::convertByteArrayToHexString(frame.payload); + emit commandSent(frame.header.command, frame.payload); + break; + } + case Server: + qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame; + break; + } +} diff --git a/libnymea-zigbee/zcl/general/zigbeeclusterscenes.h b/libnymea-zigbee/zcl/general/zigbeeclusterscenes.h new file mode 100644 index 0000000..c169247 --- /dev/null +++ b/libnymea-zigbee/zcl/general/zigbeeclusterscenes.h @@ -0,0 +1,76 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEECLUSTERSCENES_H +#define ZIGBEECLUSTERSCENES_H + +#include + +#include "zcl/zigbeecluster.h" + +class ZigbeeClusterScenes : public ZigbeeCluster +{ + Q_OBJECT +public: + enum Attribute { + AttributeSceneCount = 0x0000, + AttributeCurrentScene = 0x0001, + AttributeCurrentGroup = 0x0002, + AttributeSceneValid = 0x0003, + AttributeNameSupported = 0x0004, + AttributeLastConfiguredBy = 0x0005 // Optional + }; + Q_ENUM(Attribute) + + enum Command { + CommandAddScene = 0x00, + CommandViewScene = 0x01, + CommandRemoveScene = 0x02, + CommandRemoveAllScenes = 0x03, + CommandStoreScene = 0x04, + CommandRecallScene = 0x05, + CommandGetSceneMembership = 0x06, + CommandEnhancedAddScene= 0x40, // O + CommandEnhancedViewScene= 0x41, // O + CommandCopyScene= 0x42 // O + }; + Q_ENUM(Command) + + explicit ZigbeeClusterScenes(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); + +signals: + void commandSent(quint8 command, const QByteArray &payload); + +private: + void setAttribute(const ZigbeeClusterAttribute &attribute) override; + +protected: + void processDataIndication(ZigbeeClusterLibrary::Frame frame) override; + +}; + +#endif // ZIGBEECLUSTERSCENES_H diff --git a/libnymea-zigbee/zigbee.cpp b/libnymea-zigbee/zigbee.cpp index 9f6a789..9ad5998 100644 --- a/libnymea-zigbee/zigbee.cpp +++ b/libnymea-zigbee/zigbee.cpp @@ -32,7 +32,7 @@ QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataConfirm &confirm) { debug.nospace() << "APSDE-DATA.confirm("; - debug.nospace() << "Request ID: " << confirm.requestId << ", "; + debug.nospace() << "SQN: " << confirm.requestId << ", "; if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeGroup) debug.nospace() << "Group address:" << ZigbeeUtils::convertUint16ToHexString(confirm.destinationShortAddress) << ", "; @@ -84,3 +84,25 @@ QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataIndication &indication) debug.nospace() << "RSSI: " << indication.rssi << "dBm)"; return debug.space(); } + +QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataAck &acknowledgement) +{ + debug.nospace() << "APSDE-DATA.acknowledgement("; + debug.nospace() << "SQN: " << acknowledgement.requestId << ", "; + if (acknowledgement.destinationAddressMode == Zigbee::DestinationAddressModeGroup) { + debug.nospace() << "Group address:" << ZigbeeUtils::convertUint16ToHexString(acknowledgement.destinationAddress) << ", "; + } else { + debug.nospace() << "Network address:" << ZigbeeUtils::convertUint16ToHexString(acknowledgement.destinationAddress) << ", "; + } + debug.nospace() << "Destination EP:" << ZigbeeUtils::convertByteToHexString(acknowledgement.destinationEndpoint) << ", "; + debug.nospace() << "Source EP:" << ZigbeeUtils::convertByteToHexString(acknowledgement.sourceEndpoint) << ", "; + debug.nospace() << static_cast(acknowledgement.profileId) << ", "; + if (acknowledgement.profileId == static_cast(Zigbee::ZigbeeProfileDevice)) { + debug.nospace() << static_cast(acknowledgement.clusterId) << ", "; + } else { + debug.nospace() << static_cast(acknowledgement.clusterId) << ", "; + } + debug.nospace() << static_cast(acknowledgement.zigbeeStatusCode); + debug.nospace() << ")"; + return debug.space(); +} diff --git a/libnymea-zigbee/zigbee.h b/libnymea-zigbee/zigbee.h index 639169f..56f9a8d 100644 --- a/libnymea-zigbee/zigbee.h +++ b/libnymea-zigbee/zigbee.h @@ -320,11 +320,12 @@ public: ZigbeeNwkLayerStatusMaxFrmCntr = 0xcc, ZigbeeNwkLayerStatusNoKey = 0xcd, ZigbeeNwkLayerStatusBadCcmOutput = 0xce, + ZigbeeNwkLayerStatusNoRoutingCapacity = 0xef, // Resource problem on the hardware ZigbeeNwkLayerStatusRouteDiscoveryFailed = 0xd0, ZigbeeNwkLayerStatusRouteError = 0xd1, ZigbeeNwkLayerStatusBtTableFull = 0xd2, ZigbeeNwkLayerStatusFrameNotBuffered = 0xd3, - ZigbeeNwkLayerStatusFrameBuffered = 0xd4 + ZigbeeNwkLayerStatusFrameBuffered = 0xd4 // Buffered until route discovery has been done }; Q_ENUM(ZigbeeNwkLayerStatus) @@ -377,6 +378,16 @@ public: qint8 rssi = 0; } ApsdeDataIndication; + typedef struct ApsdeDataAck { + quint8 requestId = 0; + quint8 destinationAddressMode = Zigbee::DestinationAddressModeShortAddress; + quint16 destinationAddress = 0; + quint8 destinationEndpoint = 0; + quint8 sourceEndpoint = 0; + quint16 profileId = 0; + quint16 clusterId = 0; + quint8 zigbeeStatusCode = 0; + } ApsdeDataAck; ///* Manufacturer Codes */ ///* Codes less than 0x1000 were issued for RF4CE */ @@ -1069,11 +1080,11 @@ public: //#define ZBEE_MFG_SWANN "Swann Communications" //#define ZBEE_MFG_TI "Texas Instruments" - }; QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataConfirm &confirm); QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataIndication &indication); +QDebug operator<<(QDebug debug, const Zigbee::ApsdeDataAck &acknowledgement); Q_DECLARE_OPERATORS_FOR_FLAGS(Zigbee::ZigbeeChannels) Q_DECLARE_OPERATORS_FOR_FLAGS(Zigbee::ZigbeeTxOptions) diff --git a/libnymea-zigbee/zigbeebridgecontroller.h b/libnymea-zigbee/zigbeebridgecontroller.h index e01af22..37f99b1 100644 --- a/libnymea-zigbee/zigbeebridgecontroller.h +++ b/libnymea-zigbee/zigbeebridgecontroller.h @@ -83,6 +83,7 @@ signals: // APS notifications void apsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); void apsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); + void apsDataAckReceived(const Zigbee::ApsdeDataAck &acknowledgement); }; diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 9335e5b..b782ed7 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -843,9 +843,16 @@ void ZigbeeNetwork::setReplyResponseError(ZigbeeNetworkReply *reply, Zigbee::Zig finishNetworkReply(reply); } else { // There has been an error while transporting the request to the device - // Note: if the APS status is >= 0xc1, it has to interpreted as NWK layer error if (zigbeeApsStatus >= 0xc1 && zigbeeApsStatus <= 0xd4) { reply->m_zigbeeNwkStatus = static_cast(static_cast(zigbeeApsStatus)); + if (reply->zigbeeNwkStatus() == Zigbee::ZigbeeNwkLayerStatusFrameBuffered) { + // The frame has been buffered and will be sent once the route has been discovered. + // If the ACK will arrive, the frame was sent successfully, otherwise on timeout the request failed + reply->m_buffered = true; + // Restart the timer and wait for ack + reply->m_timer->start(); + return; + } finishNetworkReply(reply, ZigbeeNetworkReply::ErrorZigbeeNwkStatusError); } else if (zigbeeApsStatus >= 0xE0 && zigbeeApsStatus <= 0xF4) { reply->m_zigbeeMacStatus = static_cast(static_cast(zigbeeApsStatus)); diff --git a/libnymea-zigbee/zigbeenetworkreply.cpp b/libnymea-zigbee/zigbeenetworkreply.cpp index 4003eaa..f1f907e 100644 --- a/libnymea-zigbee/zigbeenetworkreply.cpp +++ b/libnymea-zigbee/zigbeenetworkreply.cpp @@ -47,6 +47,11 @@ Zigbee::ZigbeeApsStatus ZigbeeNetworkReply::zigbeeApsStatus() const return m_zigbeeApsStatus; } +bool ZigbeeNetworkReply::buffered() const +{ + return m_buffered; +} + Zigbee::ZigbeeNwkLayerStatus ZigbeeNetworkReply::zigbeeNwkStatus() const { return m_zigbeeNwkStatus; @@ -60,7 +65,13 @@ ZigbeeNetworkReply::ZigbeeNetworkReply(const ZigbeeNetworkRequest &request, QObj m_timer->setSingleShot(true); m_timer->setInterval(10000); connect(m_timer, &QTimer::timeout, this, [this](){ - m_error = ErrorTimeout; + if (m_buffered) { + // We did not receive any reply from the buffered message, assuming the route could not be discovered to the device + m_zigbeeNwkStatus = Zigbee::ZigbeeNwkLayerStatusRouteDiscoveryFailed; + m_error = ErrorZigbeeNwkStatusError; + } else { + m_error = ErrorTimeout; + } emit finished(); }); } diff --git a/libnymea-zigbee/zigbeenetworkreply.h b/libnymea-zigbee/zigbeenetworkreply.h index 1f310ff..df91b8d 100644 --- a/libnymea-zigbee/zigbeenetworkreply.h +++ b/libnymea-zigbee/zigbeenetworkreply.h @@ -60,10 +60,13 @@ public: Zigbee::ZigbeeNwkLayerStatus zigbeeNwkStatus() const; Zigbee::ZigbeeApsStatus zigbeeApsStatus() const; + bool buffered() const; + private: explicit ZigbeeNetworkReply(const ZigbeeNetworkRequest &request, QObject *parent = nullptr); ZigbeeNetworkRequest m_request; QTimer *m_timer = nullptr; + bool m_buffered = false; Error m_error = ErrorNoError; Zigbee::ZigbeeMacLayerStatus m_zigbeeMacStatus = Zigbee::ZigbeeMacLayerStatusSuccess; diff --git a/libnymea-zigbee/zigbeenodeendpoint.cpp b/libnymea-zigbee/zigbeenodeendpoint.cpp index 35e85d3..a657a89 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.cpp +++ b/libnymea-zigbee/zigbeenodeendpoint.cpp @@ -177,6 +177,8 @@ ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(ZigbeeClusterLibrary::ClusterId return new ZigbeeClusterLevelControl(m_network, m_node, this, direction, this); case ZigbeeClusterLibrary::ClusterIdGroups: return new ZigbeeClusterGroups(m_network, m_node, this, direction, this); + case ZigbeeClusterLibrary::ClusterIdScenes: + return new ZigbeeClusterScenes(m_network, m_node, this, direction, this); case ZigbeeClusterLibrary::ClusterIdAnalogInput: return new ZigbeeClusterAnalogInput(m_network, m_node, this, direction, this); case ZigbeeClusterLibrary::ClusterIdAnalogOutput: diff --git a/libnymea-zigbee/zigbeenodeendpoint.h b/libnymea-zigbee/zigbeenodeendpoint.h index 14de96b..919417b 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.h +++ b/libnymea-zigbee/zigbeenodeendpoint.h @@ -43,6 +43,7 @@ #include "zcl/general/zigbeeclusterlevelcontrol.h" #include "zcl/general/zigbeeclusterpowerconfiguration.h" #include "zcl/general/zigbeeclustergroups.h" +#include "zcl/general/zigbeeclusterscenes.h" #include "zcl/general/zigbeeclusteranaloginput.h" #include "zcl/general/zigbeeclusteranalogoutput.h" #include "zcl/general/zigbeeclusteranalogvalue.h"