380 lines
19 KiB
C++
380 lines
19 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* nymea-zigbee
|
|
* Zigbee integration module for nymea
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-zigbee.
|
|
*
|
|
* nymea-zigbee is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation, either version 3
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* nymea-zigbee 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 nymea-zigbee. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "zigbeenodenxp.h"
|
|
#include "loggingcategory.h"
|
|
#include "zigbeeutils.h"
|
|
#include "zigbeenodeendpointnxp.h"
|
|
|
|
#include <QDataStream>
|
|
|
|
/* Node initialisation steps (sequentially)
|
|
* - Authenticate
|
|
* - Node descriptor
|
|
* - Power descriptor
|
|
* - Active endpoints
|
|
* - for each endpoint do:
|
|
* - Simple descriptor request
|
|
* - for each endpoint
|
|
* - read basic cluster
|
|
*/
|
|
|
|
|
|
ZigbeeNodeNxp::ZigbeeNodeNxp(ZigbeeBridgeControllerNxp *controller, QObject *parent):
|
|
ZigbeeNode(parent),
|
|
m_controller(controller)
|
|
{
|
|
|
|
}
|
|
|
|
void ZigbeeNodeNxp::leaveNetworkRequest(bool rejoin, bool removeChildren)
|
|
{
|
|
ZigbeeInterfaceReply *reply = m_controller->commandManagementLeaveRequest(shortAddress(), extendedAddress(), rejoin, removeChildren);
|
|
connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){
|
|
reply->deleteLater();
|
|
|
|
if (reply->status() != ZigbeeInterfaceReply::Success) {
|
|
qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
|
|
// TODO: check error handling
|
|
//return;
|
|
}
|
|
|
|
qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully";
|
|
});
|
|
}
|
|
|
|
void ZigbeeNodeNxp::setInitState(ZigbeeNodeNxp::InitState initState)
|
|
{
|
|
m_initState = initState;
|
|
|
|
switch (m_initState) {
|
|
case InitStateNone:
|
|
m_initStateRetry = 0;
|
|
break;
|
|
case InitStateError:
|
|
|
|
break;
|
|
case InitStateNodeDescriptor: {
|
|
qCDebug(dcZigbeeNode()) << "Request node descriptor for" << this;
|
|
ZigbeeInterfaceReply *reply = m_controller->commandNodeDescriptorRequest(shortAddress());
|
|
connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){
|
|
reply->deleteLater();
|
|
|
|
if (reply->status() != ZigbeeInterfaceReply::Success) {
|
|
qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
|
|
m_initStateRetry++;
|
|
if (m_initStateRetry > 3) {
|
|
qCWarning(dcZigbeeNode()) << "Failed to get node descriptor after 3 retries. This node will not be added to the network.";
|
|
setInitState(InitStateError);
|
|
return;
|
|
} else {
|
|
setInitState(InitStateNodeDescriptor);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_initStateRetry = 0;
|
|
setNodeDescriptorRawData(reply->additionalMessage().data());
|
|
setInitState(InitStatePowerDescriptor);
|
|
});
|
|
break;
|
|
}
|
|
case InitStatePowerDescriptor: {
|
|
qCDebug(dcZigbeeNode()) << "Request power descriptor for" << this;
|
|
ZigbeeInterfaceReply *reply = m_controller->commandPowerDescriptorRequest(shortAddress());
|
|
connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){
|
|
reply->deleteLater();
|
|
|
|
if (reply->status() != ZigbeeInterfaceReply::Success) {
|
|
qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
|
|
m_initStateRetry++;
|
|
if (m_initStateRetry > 3) {
|
|
qCWarning(dcZigbeeNode()) << "Failed to get power descriptor after 3 retries. This node will not be added to the network.";
|
|
setInitState(InitStateError);
|
|
return;
|
|
} else {
|
|
setInitState(InitStatePowerDescriptor);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_initStateRetry = 0;
|
|
|
|
QByteArray data = reply->additionalMessage().data();
|
|
quint8 sequenceNumber = 0;
|
|
quint8 status = 0;
|
|
quint16 powerDescriptorFlag = 0;
|
|
|
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
|
stream >> sequenceNumber >> status >> powerDescriptorFlag;
|
|
setPowerDescriptorFlag(powerDescriptorFlag);
|
|
setInitState(InitStateActiveEndpoints);
|
|
});
|
|
break;
|
|
}
|
|
case InitStateActiveEndpoints: {
|
|
qCDebug(dcZigbeeNode()) << "Request active endpoints for" << this;
|
|
ZigbeeInterfaceReply *reply = m_controller->commandActiveEndpointsRequest(shortAddress());
|
|
connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){
|
|
reply->deleteLater();
|
|
|
|
if (reply->status() != ZigbeeInterfaceReply::Success) {
|
|
qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
|
|
m_initStateRetry++;
|
|
if (m_initStateRetry > 3) {
|
|
qCWarning(dcZigbeeNode()) << "Failed to get active endpoints after 3 retries. This node will not be added to the network.";
|
|
setInitState(InitStateError);
|
|
return;
|
|
} else {
|
|
setInitState(InitStateActiveEndpoints);
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_initStateRetry = 0;
|
|
|
|
QByteArray data = reply->additionalMessage().data();
|
|
quint8 sequenceNumber = 0;
|
|
quint8 status = 0;
|
|
quint16 shortAddress = 0;
|
|
quint8 endpointCount = 0;
|
|
|
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
|
stream >> sequenceNumber >> status >> shortAddress >> endpointCount;
|
|
|
|
qCDebug(dcZigbeeNode()) << "Active endpoint list received:";
|
|
qCDebug(dcZigbeeNode()) << "Sequence number" << sequenceNumber;
|
|
qCDebug(dcZigbeeNode()) << "Status:" << status;
|
|
qCDebug(dcZigbeeNode()) << "Short address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress);
|
|
qCDebug(dcZigbeeNode()) << "Endpoint count:" << endpointCount;
|
|
for (int i = 0; i < endpointCount; i++) {
|
|
quint8 endpointId = 0;
|
|
stream >> endpointId;
|
|
m_uninitializedEndpoints.append(endpointId);
|
|
qCDebug(dcZigbeeNode()) << " - " << ZigbeeUtils::convertByteToHexString(endpointId);
|
|
}
|
|
setInitState(InitStateSimpleDescriptors);
|
|
});
|
|
break;
|
|
}
|
|
case InitStateSimpleDescriptors:
|
|
for (int i = 0; i < m_uninitializedEndpoints.count(); i++) {
|
|
quint8 endpointId = m_uninitializedEndpoints.at(i);
|
|
qCDebug(dcZigbeeNode()) << "Request active endpoints for" << this << ZigbeeUtils::convertByteToHexString(endpointId);
|
|
ZigbeeInterfaceReply *reply = m_controller->commandSimpleDescriptorRequest(shortAddress(), endpointId);
|
|
connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply, endpointId](){
|
|
reply->deleteLater();
|
|
if (reply->status() != ZigbeeInterfaceReply::Success) {
|
|
qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
|
|
} else {
|
|
// Create endpoint
|
|
|
|
qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully";
|
|
qCDebug(dcZigbeeController()) << reply->additionalMessage();
|
|
|
|
QByteArray data = reply->additionalMessage().data();
|
|
|
|
quint8 sequenceNumber = 0;
|
|
quint8 status = 0;
|
|
quint16 shortAddress = 0;
|
|
quint8 length = 0;
|
|
quint8 endpointId = 0;
|
|
quint16 profileId = 0;
|
|
quint16 deviceId = 0;
|
|
quint8 bitField = 0;
|
|
quint8 inputClusterCount = 0;
|
|
quint8 outputClusterCount = 0;
|
|
|
|
QDataStream stream(&data, QIODevice::ReadOnly);
|
|
stream >> sequenceNumber >> status >> shortAddress >> length;
|
|
stream >> endpointId >> profileId >> deviceId >> bitField;
|
|
|
|
qCDebug(dcZigbeeNetwork()) << "Node endpoint simple descriptor:";
|
|
qCDebug(dcZigbeeNetwork()) << " Sequence number:" << ZigbeeUtils::convertByteToHexString(sequenceNumber);
|
|
qCDebug(dcZigbeeNetwork()) << " Status:" << ZigbeeUtils::convertByteToHexString(status);
|
|
qCDebug(dcZigbeeNetwork()) << " Nwk address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress);
|
|
qCDebug(dcZigbeeNetwork()) << " Lenght:" << ZigbeeUtils::convertByteToHexString(length);
|
|
qCDebug(dcZigbeeNetwork()) << " End Point:" << ZigbeeUtils::convertByteToHexString(endpointId);
|
|
qCDebug(dcZigbeeNetwork()) << " Profile:" << ZigbeeUtils::profileIdToString(static_cast<Zigbee::ZigbeeProfile>(profileId));
|
|
if (profileId == Zigbee::ZigbeeProfileLightLink) {
|
|
qCDebug(dcZigbeeNetwork()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast<Zigbee::LightLinkDevice>(deviceId);
|
|
} else if (profileId == Zigbee::ZigbeeProfileHomeAutomation) {
|
|
qCDebug(dcZigbeeNetwork()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast<Zigbee::HomeAutomationDevice>(deviceId);
|
|
} else if (profileId == Zigbee::ZigbeeProfileGreenPower) {
|
|
qCDebug(dcZigbeeNetwork()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast<Zigbee::GreenPowerDevice>(deviceId);
|
|
}
|
|
|
|
quint8 deviceVersion = (bitField >> 4);
|
|
qCDebug(dcZigbeeNetwork()) << " Bit field:" << ZigbeeUtils::convertByteToHexString(bitField) << deviceVersion;
|
|
|
|
ZigbeeNodeEndpointNxp *endpoint = qobject_cast<ZigbeeNodeEndpointNxp *>(getEndpoint(endpointId));
|
|
if (!endpoint) {
|
|
endpoint = new ZigbeeNodeEndpointNxp(m_controller, this, endpointId, this);
|
|
m_endpoints.append(endpoint);
|
|
}
|
|
|
|
endpoint->setProfile(static_cast<Zigbee::ZigbeeProfile>(profileId));
|
|
endpoint->setDeviceId(deviceId);
|
|
endpoint->setDeviceVersion(deviceVersion);
|
|
|
|
stream >> inputClusterCount;
|
|
qCDebug(dcZigbeeNetwork()) << " Input clusters: (" << inputClusterCount << ")";
|
|
for (int i = 0; i < inputClusterCount; i+=1) {
|
|
quint16 clusterId = 0;
|
|
stream >> clusterId;
|
|
if (!endpoint->hasInputCluster(static_cast<Zigbee::ClusterId>(clusterId))) {
|
|
endpoint->addInputCluster(new ZigbeeCluster(static_cast<Zigbee::ClusterId>(clusterId), ZigbeeCluster::Input, endpoint));
|
|
}
|
|
qCDebug(dcZigbeeNetwork()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast<Zigbee::ClusterId>(clusterId));
|
|
}
|
|
|
|
stream >> outputClusterCount;
|
|
qCDebug(dcZigbeeNetwork()) << " Output clusters: (" << outputClusterCount << ")";
|
|
for (int i = 0; i < outputClusterCount; i+=1) {
|
|
if (stream.atEnd()) {
|
|
qCWarning(dcZigbeeNode()) << "Data stream already at the end but more data expected. Looks like the firmware doesn't provide more data.";
|
|
break;
|
|
}
|
|
|
|
quint16 clusterId = 0;
|
|
stream >> clusterId;
|
|
if (!endpoint->hasOutputCluster(static_cast<Zigbee::ClusterId>(clusterId))) {
|
|
endpoint->addOutputCluster(new ZigbeeCluster(static_cast<Zigbee::ClusterId>(clusterId), ZigbeeCluster::Output, endpoint));
|
|
}
|
|
qCDebug(dcZigbeeNetwork()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast<Zigbee::ClusterId>(clusterId));
|
|
}
|
|
}
|
|
|
|
m_uninitializedEndpoints.removeAll(endpointId);
|
|
if (m_uninitializedEndpoints.isEmpty()) {
|
|
qCDebug(dcZigbeeNode()) << "All endpoints fetched.";
|
|
setInitState(InitStateReadBasicClusterAttributes);
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
case InitStateReadBasicClusterAttributes:
|
|
if (shortAddress() == 0x0000) {
|
|
qCDebug(dcZigbeeNode()) << "No need to read the endpoint baisc clusters of the coordinator node";
|
|
setState(StateInitialized);
|
|
break;
|
|
}
|
|
|
|
foreach (ZigbeeNodeEndpoint *endpoint, m_endpoints) {
|
|
|
|
ZigbeeNodeEndpointNxp *endpointNxp = qobject_cast<ZigbeeNodeEndpointNxp *>(endpoint);
|
|
|
|
// Read basic cluster
|
|
qCDebug(dcZigbeeNode()) << "Read basic cluster for endpoint" << endpoint;
|
|
|
|
ZigbeeCluster *basicCluster = endpoint->getInputCluster(Zigbee::ClusterIdBasic);
|
|
if (!basicCluster) {
|
|
qCDebug(dcZigbeeNode()) << "This endpoint has no basic cluster" << endpoint;
|
|
continue;
|
|
}
|
|
|
|
m_uninitializedEndpoints.clear();
|
|
m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributeZclVersion);
|
|
m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributeManufacturerName);
|
|
// Note: some devices inform about the model identifier trough attribute report and the cluster contains different information
|
|
// Read the model identifier only if we don't have it yet. This is out of spec but required for some strange endpoints.
|
|
if (!basicCluster->hasAttribute(ZigbeeCluster::BasicAttributeModelIdentifier))
|
|
m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributeModelIdentifier);
|
|
|
|
m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributePowerSource);
|
|
m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributeSwBuildId);
|
|
|
|
// Note: for having smoother flow request each attribute sequentially, not all at once
|
|
|
|
for (int i = 0; i < m_uninitalizedBasicClusterAttributes.count(); i++) {
|
|
quint16 attributeId = m_uninitalizedBasicClusterAttributes.at(i);
|
|
ZigbeeInterfaceReply *reply = m_controller->commandReadAttributeRequest(0x02, shortAddress(),
|
|
0x01, endpoint->endpointId(),
|
|
basicCluster,
|
|
{ attributeId },
|
|
false,
|
|
manufacturerCode());
|
|
|
|
connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply, endpointNxp, attributeId](){
|
|
reply->deleteLater();
|
|
|
|
if (reply->status() != ZigbeeInterfaceReply::Success) {
|
|
qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage();
|
|
} else {
|
|
ZigbeeClusterAttributeReport report = ZigbeeUtils::parseAttributeReport(reply->additionalMessage().data());
|
|
qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully for" << endpointNxp << report;
|
|
if (report.attributeStatus == Zigbee::ZigbeeStatusSuccess) {
|
|
if (attributeId == ZigbeeCluster::BasicAttributeManufacturerName) {
|
|
endpointNxp->setManufacturerName(QString::fromUtf8(report.data));
|
|
} else if (attributeId == ZigbeeCluster::BasicAttributeModelIdentifier) {
|
|
endpointNxp->setModelIdentifier(QString::fromUtf8(report.data));
|
|
} else if (attributeId == ZigbeeCluster::BasicAttributeSwBuildId) {
|
|
endpointNxp->setSoftwareBuildId(QString::fromUtf8(report.data));
|
|
}
|
|
}
|
|
}
|
|
|
|
m_uninitalizedBasicClusterAttributes.removeAll(attributeId);
|
|
if (m_uninitalizedBasicClusterAttributes.isEmpty()) {
|
|
setState(StateInitialized);
|
|
}
|
|
});
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ZigbeeNodeNxp::startInitialization()
|
|
{
|
|
qCDebug(dcZigbeeNode()) << "Start initialization" << this;
|
|
setState(StateInitializing);
|
|
setInitState(InitStateNodeDescriptor);
|
|
}
|
|
|
|
ZigbeeNodeEndpoint *ZigbeeNodeNxp::createNodeEndpoint(quint8 endpointId, QObject *parent)
|
|
{
|
|
return new ZigbeeNodeEndpointNxp(m_controller, this, endpointId, parent);
|
|
}
|
|
|
|
void ZigbeeNodeNxp::setClusterAttributeReport(const ZigbeeClusterAttributeReport &report)
|
|
{
|
|
if (report.attributeStatus != Zigbee::ZigbeeStatusSuccess) {
|
|
qCWarning(dcZigbeeNode()) << this << "Got incalid status report" << report;
|
|
return;
|
|
}
|
|
|
|
ZigbeeNodeEndpointNxp *endpoint = qobject_cast<ZigbeeNodeEndpointNxp *>(getEndpoint(report.endpointId));
|
|
if (!endpoint) {
|
|
qCDebug(dcZigbeeNetwork()) << "Recived attribute report but there is no endpoint on this node yet. Create it...";
|
|
endpoint = new ZigbeeNodeEndpointNxp(m_controller, this, report.endpointId, this);
|
|
m_endpoints.append(endpoint);
|
|
}
|
|
|
|
endpoint->setClusterAttribute(report.clusterId, ZigbeeClusterAttribute(report.attributeId, report.dataType, report.data));
|
|
}
|