From 0c6924e3f2720a8d1ddf29caccce2c51e66da746 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Fri, 6 Nov 2020 12:59:11 +0100 Subject: [PATCH] Implement command responding over cluster api and implement OTA server for default responses --- libnymea-zigbee/libnymea-zigbee.pro | 2 + .../zcl/general/zigbeeclusteridentify.cpp | 17 ++- .../zcl/general/zigbeeclusteronoff.cpp | 2 +- libnymea-zigbee/zcl/ota/zigbeeclusterota.cpp | 129 ++++++++++++++++++ libnymea-zigbee/zcl/ota/zigbeeclusterota.h | 112 +++++++++++++++ libnymea-zigbee/zcl/zigbeecluster.cpp | 48 ++++++- libnymea-zigbee/zcl/zigbeecluster.h | 3 + libnymea-zigbee/zigbeenode.cpp | 2 +- libnymea-zigbee/zigbeenodeendpoint.cpp | 9 +- libnymea-zigbee/zigbeenodeendpoint.h | 2 + 10 files changed, 315 insertions(+), 11 deletions(-) create mode 100644 libnymea-zigbee/zcl/ota/zigbeeclusterota.cpp create mode 100644 libnymea-zigbee/zcl/ota/zigbeeclusterota.h diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index 6fc6d15..be6b9c8 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -25,6 +25,7 @@ SOURCES += \ zcl/measurement/zigbeeclusteroccupancysensing.cpp \ zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp \ zcl/measurement/zigbeeclustertemperaturemeasurement.cpp \ + zcl/ota/zigbeeclusterota.cpp \ zcl/security/zigbeeclusteriaszone.cpp \ zcl/zigbeecluster.cpp \ zcl/zigbeeclusterattribute.cpp \ @@ -76,6 +77,7 @@ HEADERS += \ zcl/measurement/zigbeeclusteroccupancysensing.h \ zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h \ zcl/measurement/zigbeeclustertemperaturemeasurement.h \ + zcl/ota/zigbeeclusterota.h \ zcl/security/zigbeeclusteriaszone.h \ zcl/zigbeecluster.h \ zcl/zigbeeclusterattribute.h \ diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp b/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp index 03bc2e8..8eda969 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp +++ b/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp @@ -75,12 +75,21 @@ void ZigbeeClusterIdentify::setAttribute(const ZigbeeClusterAttribute &attribute void ZigbeeClusterIdentify::processDataIndication(ZigbeeClusterLibrary::Frame frame) { - qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame; - - // TODO: implement identify query response + Command command = static_cast(frame.header.command); + qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame << command; // Increase the tsn for continuouse id increasing on both sides m_transactionSequenceNumber = frame.header.transactionSequenceNumber; - qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame; + switch (command) { + case CommandIdentifyQuery: + // We are not identifying, we can ignore the command according to the specs + qCDebug(dcZigbeeCluster()) << "Received identify query command. We ignore this request according to specs, since we are not identifying our selfs visually."; + break; + default: + qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame << command; + break; + } + + } diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp index e2ddfd6..3bfb98c 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp +++ b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp @@ -113,7 +113,7 @@ void ZigbeeClusterOnOff::processDataIndication(ZigbeeClusterLibrary::Frame frame 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; + qCDebug(dcZigbeeCluster()) << "Received" << command << "from" << m_node << m_endpoint << this; switch (command) { case CommandOn: emit commandSent(CommandOn); diff --git a/libnymea-zigbee/zcl/ota/zigbeeclusterota.cpp b/libnymea-zigbee/zcl/ota/zigbeeclusterota.cpp new file mode 100644 index 0000000..11f6eab --- /dev/null +++ b/libnymea-zigbee/zcl/ota/zigbeeclusterota.cpp @@ -0,0 +1,129 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeclusterota.h" +#include "zigbeenetworkreply.h" +#include "loggingcategory.h" +#include "zigbeenetwork.h" +#include "zigbeeutils.h" + +#include + +ZigbeeClusterOta::ZigbeeClusterOta(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent) : + ZigbeeCluster(network, node, endpoint, ZigbeeClusterLibrary::ClusterIdOtaUpgrade, direction, parent) +{ + +} + +void ZigbeeClusterOta::setAttribute(const ZigbeeClusterAttribute &attribute) +{ + qCDebug(dcZigbeeCluster()) << "Update attribute" << m_node << m_endpoint << this << static_cast(attribute.id()) << attribute.dataType(); + if (hasAttribute(attribute.id())) { + m_attributes[attribute.id()] = attribute; + emit attributeChanged(attribute); + } else { + m_attributes.insert(attribute.id(), attribute); + emit attributeChanged(attribute); + } +} + +ZigbeeClusterOta::FileVersion ZigbeeClusterOta::parseFileVersion(quint32 fileVersionValue) +{ + FileVersion fileVersion; + fileVersion.applicationRelease = static_cast(fileVersionValue & 0xFF000000); + fileVersion.applicationBuild = static_cast(fileVersionValue & 0x00FF0000); + fileVersion.stackRelease = static_cast(fileVersionValue & 0x0000FF00); + fileVersion.stackBuild = static_cast(fileVersionValue & 0x000000FF); + return fileVersion; +} + +void ZigbeeClusterOta::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 (frame.header.frameControl.direction == ZigbeeClusterLibrary::DirectionClientToServer) { + Command command = static_cast(frame.header.command); + qCDebug(dcZigbeeCluster()) << "Received" << command << "from" << m_node << m_endpoint << this; + switch (command) { + case CommandQueryNextImageRequest: { + // Print the image information + quint8 fieldControl; + quint16 manufacturerCode; + quint16 imageType; + quint32 currentVersion; + quint16 hardwareVersion; + + QDataStream requestStream(&frame.payload, QIODevice::ReadOnly); + requestStream.setByteOrder(QDataStream::LittleEndian); + requestStream >> fieldControl >> manufacturerCode >> imageType >> currentVersion >> hardwareVersion; + qCDebug(dcZigbeeCluster()) << "OTA image request:" << (fieldControl == 0x0000 ? "Hardware version not present" : "Hardware version present"); + qCDebug(dcZigbeeCluster()) << "OTA image request: Manufacturer code" << ZigbeeUtils::convertUint16ToHexString(manufacturerCode); + qCDebug(dcZigbeeCluster()) << "OTA image request: Image type" << ZigbeeUtils::convertUint16ToHexString(imageType); + qCDebug(dcZigbeeCluster()) << "OTA image request: Current file version" << ZigbeeUtils::convertUint32ToHexString(currentVersion) << parseFileVersion(currentVersion); + qCDebug(dcZigbeeCluster()) << "OTA image request: Hardware version" << hardwareVersion; + + // Respond with no image available until we implement the entire cluster for OTA updates + qCDebug(dcZigbeeCluster()) << "OTA mechanism not implemented yet. Tell the node there is no image available."; + + QByteArray payload; + QDataStream stream(&payload, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(StatuCodeNoImageAvailable); + + ZigbeeClusterReply *reply = sendClusterServerResponse(CommandQueryNextImageResponse, frame.header.transactionSequenceNumber, payload); + connect(reply, &ZigbeeClusterReply::finished, this, [](){ + qCDebug(dcZigbeeCluster()) << "OTA image request response for image query sent successfully to requested node."; + }); + + break; + } + default: + qCWarning(dcZigbeeCluster()) << "Received command" << command << "which is not implemented yet from" << m_node << m_endpoint << this; + break; + } + } + break; + case Server: + qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame; + break; + } +} + + +QDebug operator<<(QDebug debug, const ZigbeeClusterOta::FileVersion &fileVersion) +{ + debug.nospace().noquote() << "FileVersion(Application Release: " << fileVersion.applicationRelease + << ", Application Build: " << fileVersion.applicationBuild + << ", Stack Release: " << fileVersion.stackRelease + << ", Stack Build: " << fileVersion.stackBuild << ")"; + return debug.space(); +} diff --git a/libnymea-zigbee/zcl/ota/zigbeeclusterota.h b/libnymea-zigbee/zcl/ota/zigbeeclusterota.h new file mode 100644 index 0000000..b0d91f4 --- /dev/null +++ b/libnymea-zigbee/zcl/ota/zigbeeclusterota.h @@ -0,0 +1,112 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEECLUSTEROTA_H +#define ZIGBEECLUSTEROTA_H + +#include + +#include "zcl/zigbeecluster.h" +#include "zcl/zigbeeclusterreply.h" + +class ZigbeeNode; +class ZigbeeNetwork; +class ZigbeeNodeEndpoint; +class ZigbeeNetworkReply; + +class ZigbeeClusterOta : public ZigbeeCluster +{ + Q_OBJECT + + friend class ZigbeeNode; + friend class ZigbeeNetwork; + +public: + enum Attribute { + AttributeUpgradeServerId = 0x0000, + AttributeFileOffset = 0x0001, + AttributeCurrentFileVersion = 0x0002, + AttributeCurrentZigbeeStackVersion = 0x0003, + AttributeDownloadedFileVersion = 0x0004, + AttributeDownloadedZigbeeStackVersion = 0x0005, + AttributeImageUpgradeStatus = 0x0006, + AttributeManufacturerId = 0x0007, + AttributeImageTypeId = 0x0008, + AttributeMinimumBockPeriod = 0x0009, + AttributeImageStamp = 0x000a + }; + Q_ENUM(Attribute) + + enum Command { + CommandImageNotify = 0x00, + CommandQueryNextImageRequest = 0x01, + CommandQueryNextImageResponse = 0x02, + CommandImageBlockRequest = 0x03, + CommandImagePageRequest = 0x04, + CommandImageBlockResponse = 0x05, + CommandUpgradeEndRequest = 0x06, + CommandUpgradeEndResponse = 0x07, + CommandQueryDeviceSpecificFileRequest = 0x08, + CommandQueryDeviceSpecificFileResponse = 0x09 + }; + Q_ENUM(Command) + + enum StatusCode { + StatuCodeSuccess = 0x00, + StatuCodeAbort = 0x95, + StatuCodeNotAuthorized = 0x7E, + StatuCodeInvalidImage = 0x96, + StatuCodeWaitForData = 0x97, + StatuCodeNoImageAvailable = 0x98, + StatuCodeMalformedCommand = 0x80, + StatuCodeUnsupportedClusterCommand = 0x81, + StatuCodeRequireMoreImage = 0x99 + }; + Q_ENUM(StatusCode) + + typedef struct FileVersion { + quint8 applicationRelease; + quint8 applicationBuild; + quint8 stackRelease; + quint8 stackBuild; + } FileVersion; + + explicit ZigbeeClusterOta(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); + +protected: + void processDataIndication(ZigbeeClusterLibrary::Frame frame) override; + +private: + void setAttribute(const ZigbeeClusterAttribute &attribute) override; + + FileVersion parseFileVersion(quint32 fileVersionValue); +}; + +QDebug operator<<(QDebug debug, const ZigbeeClusterOta::FileVersion &fileVersion); + + +#endif // ZIGBEECLUSTEROTA_H diff --git a/libnymea-zigbee/zcl/zigbeecluster.cpp b/libnymea-zigbee/zcl/zigbeecluster.cpp index 782f591..e3687b8 100644 --- a/libnymea-zigbee/zcl/zigbeecluster.cpp +++ b/libnymea-zigbee/zcl/zigbeecluster.cpp @@ -250,6 +250,50 @@ ZigbeeClusterReply *ZigbeeCluster::executeClusterCommand(quint8 command, const Q return zclReply; } +ZigbeeClusterReply *ZigbeeCluster::sendClusterServerResponse(quint8 command, quint8 transactionSequenceNumber, const QByteArray &payload) +{ + ZigbeeNetworkRequest request = createGeneralRequest(); + + // Build ZCL frame control + ZigbeeClusterLibrary::FrameControl frameControl; + frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific; + frameControl.manufacturerSpecific = false; + frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient; + frameControl.disableDefaultResponse = true; + + // Build ZCL header + ZigbeeClusterLibrary::Header header; + header.frameControl = frameControl; + header.command = command; + header.transactionSequenceNumber = transactionSequenceNumber; + + // Build ZCL frame + ZigbeeClusterLibrary::Frame frame; + frame.header = header; + frame.payload = payload; + + request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission)); + request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame)); + + ZigbeeClusterReply *zclReply = createClusterReply(request, frame); + qCDebug(dcZigbeeCluster()) << "Send command response" << ZigbeeUtils::convertByteToHexString(command) << "TSN:" << ZigbeeUtils::convertByteToHexString(transactionSequenceNumber) << ZigbeeUtils::convertByteArrayToHexString(payload); + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){ + if (!verifyNetworkError(zclReply, networkReply)) { + qCWarning(dcZigbeeClusterLibrary()) << "Failed to send response" + << m_node << networkReply->error() + << networkReply->zigbeeApsStatus(); + finishZclReply(zclReply); + return; + } + + // Note: since this is a response to a request, we don't expect any additional indications and the reply is finished + finishZclReply(zclReply); + }); + + return zclReply; +} + ZigbeeNetworkRequest ZigbeeCluster::createGeneralRequest() { // Build the request @@ -261,7 +305,7 @@ ZigbeeNetworkRequest ZigbeeCluster::createGeneralRequest() request.setClusterId(m_clusterId); request.setSourceEndpoint(0x01); request.setDestinationEndpoint(m_endpoint->endpointId()); - request.setRadius(10); + request.setRadius(0); request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission)); return request; } @@ -320,8 +364,6 @@ void ZigbeeCluster::processDataIndication(ZigbeeClusterLibrary::Frame frame) void ZigbeeCluster::processApsDataIndication(const QByteArray &asdu, const ZigbeeClusterLibrary::Frame &frame) { - 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); diff --git a/libnymea-zigbee/zcl/zigbeecluster.h b/libnymea-zigbee/zcl/zigbeecluster.h index 22c34d3..b96cbc8 100644 --- a/libnymea-zigbee/zcl/zigbeecluster.h +++ b/libnymea-zigbee/zcl/zigbeecluster.h @@ -118,6 +118,9 @@ protected: // Cluster specific ZigbeeClusterReply *createClusterReply(const ZigbeeNetworkRequest &request, ZigbeeClusterLibrary::Frame frame); ZigbeeClusterReply *executeClusterCommand(quint8 command, const QByteArray &payload = QByteArray()); + + ZigbeeClusterReply *sendClusterServerResponse(quint8 command, quint8 transactionSequenceNumber, const QByteArray &payload = QByteArray()); + bool verifyNetworkError(ZigbeeClusterReply *zclReply, ZigbeeNetworkReply *networkReply); void finishZclReply(ZigbeeClusterReply *zclReply); diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 47f8b75..7b00ea9 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -581,7 +581,7 @@ void ZigbeeNode::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataInd // 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"; + qCDebug(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)); diff --git a/libnymea-zigbee/zigbeenodeendpoint.cpp b/libnymea-zigbee/zigbeenodeendpoint.cpp index f97876e..967102d 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.cpp +++ b/libnymea-zigbee/zigbeenodeendpoint.cpp @@ -207,6 +207,11 @@ ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(ZigbeeClusterLibrary::ClusterId return new ZigbeeClusterIasZone(m_network, m_node, this, direction, this); break; + // OTA + case ZigbeeClusterLibrary::ClusterIdOtaUpgrade: + return new ZigbeeClusterOta(m_network, m_node, this, direction, this); + break; + default: // Return a default cluster since we have no special implementation for this cluster, allowing to use generic clusters operations return new ZigbeeCluster(m_network, m_node, this, clusterId, direction, this); @@ -244,7 +249,7 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd 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; + qCDebug(dcZigbeeEndpoint()) << "Received a ZCL indication for a client cluster which does not exist yet on" << m_node << this << "Creating" << cluster; addOutputCluster(cluster); } break; @@ -253,7 +258,7 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd 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; + qCDebug(dcZigbeeEndpoint()) << "Received a ZCL indication for a server cluster which does not exist yet on" << m_node << this << "Creating" << cluster; addInputCluster(cluster); } break; diff --git a/libnymea-zigbee/zigbeenodeendpoint.h b/libnymea-zigbee/zigbeenodeendpoint.h index 341c056..bfdeb74 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.h +++ b/libnymea-zigbee/zigbeenodeendpoint.h @@ -52,6 +52,8 @@ #include "zcl/security/zigbeeclusteriaszone.h" +#include "zcl/ota/zigbeeclusterota.h" + class ZigbeeNode; class ZigbeeNetwork;