Implement command responding over cluster api and implement OTA server for default responses

pull/13/head
Simon Stürz 2020-11-06 12:59:11 +01:00
parent 9381a5dcef
commit 0c6924e3f2
10 changed files with 315 additions and 11 deletions

View File

@ -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 \

View File

@ -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<Command>(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;
}
}

View File

@ -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<Command>(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);

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QDataStream>
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>(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<quint8>(fileVersionValue & 0xFF000000);
fileVersion.applicationBuild = static_cast<quint8>(fileVersionValue & 0x00FF0000);
fileVersion.stackRelease = static_cast<quint8>(fileVersionValue & 0x0000FF00);
fileVersion.stackBuild = static_cast<quint8>(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<Command>(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<quint8>(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();
}

View File

@ -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 <https://www.gnu.org/licenses/>.
*
* 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 <QObject>
#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

View File

@ -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);

View File

@ -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);

View File

@ -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<Zigbee::ZigbeeProfile>(indication.profileId));

View File

@ -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<ZigbeeClusterLibrary::ClusterId>(indication.clusterId));
if (!cluster) {
cluster = createCluster(static_cast<ZigbeeClusterLibrary::ClusterId>(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<ZigbeeClusterLibrary::ClusterId>(indication.clusterId));
if (!cluster) {
cluster = createCluster(static_cast<ZigbeeClusterLibrary::ClusterId>(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;

View File

@ -52,6 +52,8 @@
#include "zcl/security/zigbeeclusteriaszone.h"
#include "zcl/ota/zigbeeclusterota.h"
class ZigbeeNode;
class ZigbeeNetwork;