Merge PR #65: Complete the OTA cluster implementation

pull/74/head
jenkins 2022-10-17 11:19:55 +02:00
commit bbbbbbc521
10 changed files with 229 additions and 41 deletions

View File

@ -48,7 +48,6 @@ ZigbeeClusterPowerConfiguration::BatteryAlarmMask ZigbeeClusterPowerConfiguratio
void ZigbeeClusterPowerConfiguration::setAttribute(const ZigbeeClusterAttribute &attribute)
{
qCDebug(dcZigbeeCluster()) << "Update attribute" << m_node << m_endpoint << this << static_cast<Attribute>(attribute.id()) << attribute.dataType();
ZigbeeCluster::setAttribute(attribute);
if (attribute.id() == AttributeBatteryPercentageRemaining) {

View File

@ -39,19 +39,156 @@ ZigbeeClusterOta::ZigbeeClusterOta(ZigbeeNetwork *network, ZigbeeNode *node, Zig
}
ZigbeeClusterReply *ZigbeeClusterOta::sendImageNotify(PayloadType payloadType, quint8 queryJitter, quint16 manufacturerCode, quint16 imageType, quint32 newFileVersion)
{
if (queryJitter > 100) {
queryJitter = 100;
}
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(payloadType);
stream << queryJitter;
if (payloadType >= PayloadTypeQueryJitterAndManufacturerCode) {
stream << manufacturerCode;
}
if (payloadType >= PayloadTypeQueryJitterAndManufacturerCodeAndImageType) {
stream << imageType;
}
if (payloadType >= PayloadTypeQueryJitterAndManufacturerCodeAndImageTypeAndNewFileVersion) {
stream << newFileVersion;
}
ZigbeeClusterReply *reply = executeClusterCommand(CommandImageNotify, payload, ZigbeeClusterLibrary::DirectionServerToClient);
return reply;
}
ZigbeeClusterReply *ZigbeeClusterOta::sendQueryNextImageResponse(quint8 transactionSequenceNumber, StatusCode statusCode, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion, quint32 imageSize)
{
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(statusCode);
if (statusCode == StatusCodeSuccess) {
stream << manufacturerCode;
stream << imageType;
stream << fileVersion;
stream << imageSize;
}
ZigbeeClusterReply *reply = sendClusterServerResponse(CommandQueryNextImageResponse, transactionSequenceNumber, payload);
connect(reply, &ZigbeeClusterReply::finished, this, [reply](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeCluster()) << "OTA: Error sending query next image response:" << reply->error();
return;
}
qCDebug(dcZigbeeCluster()) << "OTA: Query image response sent successfully.";
});
return reply;
}
ZigbeeClusterReply *ZigbeeClusterOta::sendImageBlockResponse(quint8 transactionSequenceNumber, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion, quint32 fileOffset, const QByteArray &imageData)
{
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(StatusCodeSuccess);
stream << manufacturerCode;
stream << imageType;
stream << fileVersion;
stream << fileOffset;
stream << static_cast<quint8>(imageData.length());
stream.writeRawData(imageData.data(), imageData.length());
ZigbeeClusterReply *reply = sendClusterServerResponse(CommandImageBlockResponse, transactionSequenceNumber, payload);
connect(reply, &ZigbeeClusterReply::finished, this, [reply](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeCluster()) << "OTA: Error sending image block response:" << reply->error();
return;
}
qCDebug(dcZigbeeCluster()) << "OTA: Image block response sent successfully.";
});
return reply;
}
ZigbeeClusterReply *ZigbeeClusterOta::sendAbortImageBlockResponse(quint8 transactionSequenceNumber)
{
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(StatusCodeAbort);
ZigbeeClusterReply *reply = sendClusterServerResponse(CommandImageBlockResponse, transactionSequenceNumber, payload);
connect(reply, &ZigbeeClusterReply::finished, this, [reply](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeCluster()) << "OTA: Error sending abort image block response:" << reply->error();
return;
}
qCDebug(dcZigbeeCluster()) << "OTA: Abort image block response sent successfully.";
});
return reply;
}
ZigbeeClusterReply *ZigbeeClusterOta::sendDelayImageBlockResponse(quint8 transactionSequenceNumber, const QDateTime &requestTime, quint16 minimumBlockPeriod)
{
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(StatusCodeWaitForData);
stream << static_cast<quint32>(requestTime.toMSecsSinceEpoch() / 1000);
stream << minimumBlockPeriod;
ZigbeeClusterReply *reply = sendClusterServerResponse(CommandImageBlockResponse, transactionSequenceNumber, payload);
connect(reply, &ZigbeeClusterReply::finished, this, [reply](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeCluster()) << "OTA: Error sending delay image block response:" << reply->error();
return;
}
qCDebug(dcZigbeeCluster()) << "OTA: Delay image block response sent successfully.";
});
return reply;
}
ZigbeeClusterReply *ZigbeeClusterOta::sendUpgradeEndResponse(quint8 transactionSequenceNumber, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion, quint32 serverTime, quint32 requestTime)
{
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
stream.setByteOrder(QDataStream::LittleEndian);
stream << manufacturerCode;
stream << imageType;
stream << fileVersion;
stream << serverTime;
stream << requestTime;
ZigbeeClusterReply *reply = sendClusterServerResponse(CommandUpgradeEndResponse, transactionSequenceNumber, payload);
connect(reply, &ZigbeeClusterReply::finished, this, [reply](){
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
qCWarning(dcZigbeeCluster()) << "OTA: Error sending image block response:" << reply->error();
return;
}
qCDebug(dcZigbeeCluster()) << "OTA: Query image block data successfully.";
});
return reply;
}
ZigbeeClusterReply *ZigbeeClusterOta::sendAbortUpgradeEndResponse(quint8 transactionSequenceNumber)
{
return sendDefaultResponse(transactionSequenceNumber, ZigbeeClusterLibrary::CommandDefaultResponse, ZigbeeClusterLibrary::StatusSuccess);
}
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);
fileVersion.applicationRelease = static_cast<quint8>(fileVersionValue >> 24);
fileVersion.applicationBuild = static_cast<quint8>(fileVersionValue >> 16);
fileVersion.stackRelease = static_cast<quint8>(fileVersionValue >> 8);
fileVersion.stackBuild = static_cast<quint8>(fileVersionValue);
return fileVersion;
}
void ZigbeeClusterOta::processDataIndication(ZigbeeClusterLibrary::Frame frame)
{
qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame;
qCDebug(dcZigbeeCluster()) << "OTA: Processing cluster frame" << m_node << m_endpoint << this << frame << frame.payload.toHex();
switch (m_direction) {
case Client:
@ -70,37 +207,64 @@ void ZigbeeClusterOta::processDataIndication(ZigbeeClusterLibrary::Frame frame)
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");
FileVersion currentFileVersion = parseFileVersion(currentVersion);
qCDebug(dcZigbeeCluster()) << "OTA image request:" << (fieldControl == 0x00 ? "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: Current file version" << ZigbeeUtils::convertUint32ToHexString(currentVersion) << currentFileVersion;
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.";
emit queryNextImageRequestReceived(frame.header.transactionSequenceNumber, manufacturerCode, imageType, currentVersion, hardwareVersion);
break;
}
case CommandImageBlockRequest: {
quint8 fieldControl;
quint16 manufacturerCode;
quint16 imageType;
quint32 fileVersion;
quint32 fileOffset;
quint8 maximumDataSize;
quint64 requestNodeAddress = 0;
quint16 minimumBlockPerdiod = 0;
QByteArray payload;
QDataStream stream(&payload, QIODevice::WriteOnly);
QDataStream stream(frame.payload);
stream.setByteOrder(QDataStream::LittleEndian);
stream << static_cast<quint8>(StatuCodeNoImageAvailable);
// Note: if there would be an image available, the response would be success, followed by manufacturer code, image type, file version of image and file size
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.";
});
stream >> fieldControl >> manufacturerCode >> imageType >> fileVersion >> fileOffset >> maximumDataSize;
if (fieldControl & 0x01) {
stream >> requestNodeAddress;
}
if (fieldControl & 0x02) {
stream >> minimumBlockPerdiod;
}
qCDebug(dcZigbeeCluster()) << "OTA: Image block request receved. FieldControl:" << fieldControl << "ManufacturerCode:" << manufacturerCode << "ImageType:" << imageType << "File version:" << fileVersion << "Offset:" << fileOffset << "Max size:" << maximumDataSize << "Request Address:" << ZigbeeAddress(requestNodeAddress) << "Min block period:" << minimumBlockPerdiod;
emit imageBlockRequestReceived(frame.header.transactionSequenceNumber, manufacturerCode, imageType, fileVersion, fileOffset, maximumDataSize, ZigbeeAddress(requestNodeAddress), minimumBlockPerdiod);
break;
}
case CommandUpgradeEndRequest: {
quint8 status;
quint16 manufacturerCode;
quint16 imageType;
quint32 fileVersion;
QDataStream stream(frame.payload);
stream.setByteOrder(QDataStream::LittleEndian);
stream >> status >> manufacturerCode >> imageType >> fileVersion;
emit upgradeEndRequestReceived(frame.header.transactionSequenceNumber, static_cast<StatusCode>(status), manufacturerCode, imageType, fileVersion);
break;
}
default:
qCWarning(dcZigbeeCluster()) << "Received command" << command << "which is not implemented yet from" << m_node << m_endpoint << this;
qCWarning(dcZigbeeCluster()) << "Received unhandled command" << command << "from" << m_node << m_endpoint << this;
break;
}
}
break;
case Server:
qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame;
if (frame.header.frameControl.direction == ZigbeeClusterLibrary::DirectionServerToClient) {
Command command = static_cast<Command>(frame.header.command);
qCDebug(dcZigbeeCluster()) << "Received" << command << "from" << m_node << m_endpoint << this;
} else {
qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame;
}
break;
}
}

View File

@ -29,6 +29,7 @@
#define ZIGBEECLUSTEROTA_H
#include <QObject>
#include <QDateTime>
#include "zcl/zigbeecluster.h"
#include "zcl/zigbeeclusterreply.h"
@ -76,18 +77,26 @@ public:
Q_ENUM(Command)
enum StatusCode {
StatuCodeSuccess = 0x00,
StatuCodeAbort = 0x95,
StatuCodeNotAuthorized = 0x7E,
StatuCodeInvalidImage = 0x96,
StatuCodeWaitForData = 0x97,
StatuCodeNoImageAvailable = 0x98,
StatuCodeMalformedCommand = 0x80,
StatuCodeUnsupportedClusterCommand = 0x81,
StatuCodeRequireMoreImage = 0x99
StatusCodeSuccess = 0x00,
StatusCodeAbort = 0x95,
StatusCodeNotAuthorized = 0x7E,
StatusCodeInvalidImage = 0x96,
StatusCodeWaitForData = 0x97,
StatusCodeNoImageAvailable = 0x98,
StatusCodeMalformedCommand = 0x80,
StatusCodeUnsupportedClusterCommand = 0x81,
StatusCodeRequireMoreImage = 0x99
};
Q_ENUM(StatusCode)
enum PayloadType {
PayloadTypeQueryJitter = 0x00,
PayloadTypeQueryJitterAndManufacturerCode = 0x01,
PayloadTypeQueryJitterAndManufacturerCodeAndImageType = 0x02,
PayloadTypeQueryJitterAndManufacturerCodeAndImageTypeAndNewFileVersion = 0x03
};
Q_ENUM(PayloadType)
typedef struct FileVersion {
quint8 applicationRelease;
quint8 applicationBuild;
@ -95,13 +104,28 @@ public:
quint8 stackBuild;
} FileVersion;
explicit ZigbeeClusterOta(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr);
ZigbeeClusterReply *sendImageNotify(PayloadType payloadType = PayloadTypeQueryJitter, quint8 queryJitter = 100, quint16 manufacturerCode = 0, quint16 imageType = 0, quint32 newFileVersion = 0);
ZigbeeClusterReply *sendQueryNextImageResponse(quint8 transactionSequenceNumber, StatusCode statusCode = StatusCodeNoImageAvailable, quint16 manufacturerCode = 0, quint16 imageType = 0, quint32 fileVersion = 0, quint32 imageSize = 0);
ZigbeeClusterReply *sendImageBlockResponse(quint8 transactionSequenceNumber, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion, quint32 fileOffset, const QByteArray &imageData);
ZigbeeClusterReply *sendAbortImageBlockResponse(quint8 transactionSequenceNumber);
ZigbeeClusterReply *sendDelayImageBlockResponse(quint8 transactionSequenceNumber, const QDateTime &requestTime, quint16 minimumBlockPeriod);
ZigbeeClusterReply *sendUpgradeEndResponse(quint8 transactionSequenceNumber, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion, quint32 serverTime = 0, quint32 requestTime = 1);
ZigbeeClusterReply *sendAbortUpgradeEndResponse(quint8 transactionSequenceNumber);
static FileVersion parseFileVersion(quint32 fileVersionValue);
signals:
void queryNextImageRequestReceived(quint8 transactionSequenceNumber, quint16 manufactuerCode, quint16 imageType, quint32 fileVersion, quint16 hardwareVersion);
void imageBlockRequestReceived(quint8 transactionSequenceNumber, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion, quint32 fileOffset, quint8 maximumDataSize, const ZigbeeAddress &requestNodeAddress, quint16 minimumBlockPeriod);
void upgradeEndRequestReceived(quint8 transactionSequenceNumber, StatusCode statusCode, quint16 manufacturerCode, quint16 imageType, quint32 fileVersion);
protected:
void processDataIndication(ZigbeeClusterLibrary::Frame frame) override;
private:
FileVersion parseFileVersion(quint32 fileVersionValue);
};
QDebug operator<<(QDebug debug, const ZigbeeClusterOta::FileVersion &fileVersion);

View File

@ -212,7 +212,7 @@ ZigbeeClusterReply *ZigbeeCluster::createClusterReply(const ZigbeeNetworkRequest
return zclReply;
}
ZigbeeClusterReply *ZigbeeCluster::executeClusterCommand(quint8 command, const QByteArray &payload)
ZigbeeClusterReply *ZigbeeCluster::executeClusterCommand(quint8 command, const QByteArray &payload, ZigbeeClusterLibrary::Direction direction)
{
ZigbeeNetworkRequest request = createGeneralRequest();
@ -220,7 +220,7 @@ ZigbeeClusterReply *ZigbeeCluster::executeClusterCommand(quint8 command, const Q
ZigbeeClusterLibrary::FrameControl frameControl;
frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
frameControl.manufacturerSpecific = false;
frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer;
frameControl.direction = direction;
frameControl.disableDefaultResponse = false;
// Build ZCL header

View File

@ -112,7 +112,7 @@ protected:
// Cluster specific
ZigbeeClusterReply *createClusterReply(const ZigbeeNetworkRequest &request, ZigbeeClusterLibrary::Frame frame);
ZigbeeClusterReply *executeClusterCommand(quint8 command, const QByteArray &payload = QByteArray());
ZigbeeClusterReply *executeClusterCommand(quint8 command, const QByteArray &payload = QByteArray(), ZigbeeClusterLibrary::Direction direction = ZigbeeClusterLibrary::DirectionClientToServer);
ZigbeeClusterReply *sendClusterServerResponse(quint8 command, quint8 transactionSequenceNumber, const QByteArray &payload = QByteArray());
ZigbeeClusterReply *sendDefaultResponse(quint8 transactionSequenceNumber, quint8 command, quint8 status);

View File

@ -265,8 +265,9 @@ QByteArray ZigbeeClusterLibrary::buildAttributeReportingConfiguration(const Zigb
stream << reportingConfiguration.minReportingInterval;
stream << reportingConfiguration.maxReportingInterval;
for (int i = 0; i < reportingConfiguration.reportableChange.count(); i++)
for (int i = 0; i < reportingConfiguration.reportableChange.count(); i++) {
stream << static_cast<quint8>(reportingConfiguration.reportableChange.at(i));
}
// Note: for reporting the timeoutPeriod is omitted
if (reportingConfiguration.direction == ReportingDirectionReceiving) {

View File

@ -32,7 +32,7 @@ ZigbeeClusterReply::ZigbeeClusterReply(const ZigbeeNetworkRequest &request, Zigb
m_request(request),
m_requestFrame(requestFrame)
{
m_timeoutTimer.setInterval(10000);
m_timeoutTimer.setInterval(20000);
connect(&m_timeoutTimer, &QTimer::timeout, this, [this](){
m_error = ErrorTimeout;
emit finished();

View File

@ -33,7 +33,7 @@ ZigbeeDeviceObjectReply::ZigbeeDeviceObjectReply(const ZigbeeNetworkRequest &req
QObject(parent),
m_request(request)
{
m_timeoutTimer.setInterval(10000);
m_timeoutTimer.setInterval(20000);
connect(&m_timeoutTimer, &QTimer::timeout, this, [this](){
m_error = ErrorTimeout;
emit finished();

View File

@ -410,7 +410,7 @@ void ZigbeeNetwork::addNodeInternally(ZigbeeNode *node)
connect(node, &ZigbeeNode::clusterAdded, this, [this, node](ZigbeeCluster *cluster){
if (node->state() == ZigbeeNode::StateInitialized) {
qCWarning(dcZigbeeNetwork()) << node << cluster << "cluster added but the node has already been initialized. This node is out of spec. Save the node nether the less...";
qCWarning(dcZigbeeNetwork()) << node << "cluster" << cluster << "added on endpoint" << cluster->endpoint()->endpointId() << "but the node has already been initialized. This node is out of spec. Saving the node nethertheless...";
m_database->saveNode(node);
}
});

View File

@ -58,7 +58,7 @@ ZigbeeNetworkReply::ZigbeeNetworkReply(const ZigbeeNetworkRequest &request, QObj
{
m_timer = new QTimer(this);
m_timer->setSingleShot(true);
m_timer->setInterval(10000);
m_timer->setInterval(20000);
connect(m_timer, &QTimer::timeout, this, [this](){
m_error = ErrorTimeout;
emit finished();