diff --git a/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp b/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp index 40e1bd1..e479263 100644 --- a/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp +++ b/libnymea-zigbee/nxp/interface/zigbeeinterface.cpp @@ -143,7 +143,10 @@ void ZigbeeInterface::onReadyRead() m_lengthValue = 0; m_escapeDetected = false; m_data.clear(); - + if (!m_unhandledBuffer.isEmpty()) { + qCDebug(dcZigbeeInterfaceTraffic()) << "Controller debug message:" << QString::fromUtf8(m_unhandledBuffer); + m_unhandledBuffer.clear(); + } setReadingState(WaitForTypeMsb); break; case 0x02: @@ -185,7 +188,8 @@ void ZigbeeInterface::onReadyRead() // Read data bytes depending on the reading state switch (m_readingState) { case WaitForStart: - qCWarning(dcZigbeeInterfaceTraffic()) << "Wait for start but reviced data:" << byte; + qCDebug(dcZigbeeInterfaceTraffic()) << "Wait for start but reviced data:" << byte; + m_unhandledBuffer.append(static_cast(byte)); break; case WaitForTypeMsb: m_messageTypeValue = byte; diff --git a/libnymea-zigbee/nxp/interface/zigbeeinterface.h b/libnymea-zigbee/nxp/interface/zigbeeinterface.h index 8bdc5e5..a59f653 100644 --- a/libnymea-zigbee/nxp/interface/zigbeeinterface.h +++ b/libnymea-zigbee/nxp/interface/zigbeeinterface.h @@ -60,6 +60,7 @@ private: QTimer *m_reconnectTimer = nullptr; QSerialPort *m_serialPort = nullptr; QByteArray m_messageBuffer; + QByteArray m_unhandledBuffer; bool m_available = false; // Message parsing diff --git a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp index 373819b..22ad7ce 100644 --- a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp +++ b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.cpp @@ -381,6 +381,23 @@ ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandFactoryResetNode(quint16 return sendRequest(request); } +ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandManagementLeaveRequest(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, bool rejoin, bool removeChildren) +{ + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << shortAddress; + stream << ieeeAddress.toUInt64(); + stream << (rejoin ? static_cast(0x01) : static_cast(0x00)); + stream << (removeChildren ? static_cast(0x01) : static_cast(0x00)); + + ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeManagementLeaveRequest, data)); + request.setExpectedAdditionalMessageType(Zigbee::MessageTypeManagementLeaveResponse); + request.setDescription("Leave node request " + ZigbeeUtils::convertUint16ToHexString(shortAddress)); + request.setTimoutIntervall(10000); + + return sendRequest(request); +} + ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandReadAttributeRequest(quint8 addressMode, quint16 shortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, ZigbeeCluster *cluster, QList attributes, bool manufacturerSpecific, quint16 manufacturerId) { // Address mode: TODO @@ -413,7 +430,9 @@ ZigbeeInterfaceReply *ZigbeeBridgeControllerNxp::commandReadAttributeRequest(qui } ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeReadAttributeRequest, data)); - //request.setExpectedAdditionalMessageType(Zigbee::MessageTypeReadAttributeResponse); + if (attributes.count() == 1) { + request.setExpectedAdditionalMessageType(Zigbee::MessageTypeReadAttributeResponse); + } request.setDescription("Read attribute request for " + ZigbeeUtils::convertUint16ToHexString(shortAddress)); request.setTimoutIntervall(12000); diff --git a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h index e5e8385..5d0274c 100644 --- a/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h +++ b/libnymea-zigbee/nxp/zigbeebridgecontrollernxp.h @@ -78,6 +78,7 @@ public: ZigbeeInterfaceReply *commandUserDescriptorRequest(quint16 shortAddress, quint16 address); ZigbeeInterfaceReply *commandFactoryResetNode(quint16 shortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint); + ZigbeeInterfaceReply *commandManagementLeaveRequest(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, bool rejoin = false, bool removeChildren = false); ZigbeeInterfaceReply *commandReadAttributeRequest(quint8 addressMode, quint16 shortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, ZigbeeCluster *cluster, QList attributes, bool manufacturerSpecific, quint16 manufacturerId); ZigbeeInterfaceReply *commandConfigureReportingRequest(quint8 addressMode, quint16 shortAddress, quint8 sourceEndpoint, quint8 destinationEndpoint, ZigbeeCluster *cluster, quint8 direction, bool manufacturerSpecific, quint16 manufacturerId, QList reportConfigurations); diff --git a/libnymea-zigbee/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/nxp/zigbeenetworknxp.cpp index 21588d7..fb9aa08 100644 --- a/libnymea-zigbee/nxp/zigbeenetworknxp.cpp +++ b/libnymea-zigbee/nxp/zigbeenetworknxp.cpp @@ -695,81 +695,14 @@ void ZigbeeNetworkNxp::processAttributeReport(const ZigbeeInterfaceMessage &mess void ZigbeeNetworkNxp::processReadAttributeResponse(const ZigbeeInterfaceMessage &message) { - QByteArray data = message.data(); + ZigbeeClusterAttributeReport attributeReport = ZigbeeUtils::parseAttributeReport(message.data()); - quint8 sequenceNumber = 0; - quint16 sourceAddress = 0; - quint8 endpointId = 0; - quint16 clusterId = 0; - quint16 attributeId = 0; - quint8 attributeStatus = 0; - quint8 attributDataType = 0; - quint16 dataSize = 0; - - QDataStream stream(&data, QIODevice::ReadOnly); - stream >> sequenceNumber >> sourceAddress >> endpointId >> clusterId >> attributeId >> attributeStatus >> attributDataType >> dataSize; - - Zigbee::DataType dataType = static_cast(attributDataType); - QByteArray attributeData = data.right(dataSize); - - if (attributeData.length() != dataSize) { - qCWarning(dcZigbeeNetwork()) << "HACK" << attributeData.length() << "!=" << dataSize; - // Note: the NXP firmware for JN5169 has a bug here and does not send the attributeStatus. - // Repars data without attribute status - sequenceNumber = 0; - sourceAddress = 0; - endpointId = 0; - clusterId = 0; - attributeId = 0; - attributeStatus = 0; - attributDataType = 0; - dataSize = 0; - - QDataStream alternativeStream(&data, QIODevice::ReadOnly); - alternativeStream >> sequenceNumber >> sourceAddress >> endpointId >> clusterId >> attributeId >> attributDataType >> dataSize; - - dataType = static_cast(attributDataType); - attributeData = data.right(dataSize); - } - - qCDebug(dcZigbeeNetwork()) << "Attribute read response:"; - qCDebug(dcZigbeeNetwork()) << " SQN:" << ZigbeeUtils::convertByteToHexString(sequenceNumber); - qCDebug(dcZigbeeNetwork()) << " Source address:" << ZigbeeUtils::convertUint16ToHexString(sourceAddress); - qCDebug(dcZigbeeNetwork()) << " End point:" << ZigbeeUtils::convertByteToHexString(endpointId); - qCDebug(dcZigbeeNetwork()) << " Cluster:" << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); - qCDebug(dcZigbeeNetwork()) << " Attribut id:" << ZigbeeUtils::convertUint16ToHexString(attributeId); - qCDebug(dcZigbeeNetwork()) << " Attribut status:" << static_cast(attributeStatus); - qCDebug(dcZigbeeNetwork()) << " Attribut data type:" << dataType; - qCDebug(dcZigbeeNetwork()) << " Attribut size:" << dataSize; - qCDebug(dcZigbeeNetwork()) << " Data:" << ZigbeeUtils::convertByteArrayToHexString(attributeData); - - switch (dataType) { - case Zigbee::CharString: - qCDebug(dcZigbeeNetwork()) << " Data(converted)" << QString::fromUtf8(attributeData); - break; - case Zigbee::Bool: - qCDebug(dcZigbeeNetwork()) << " Data(converted)" << static_cast(attributeData.at(0)); - break; - default: - break; - } - - - ZigbeeNodeNxp *node = qobject_cast(getZigbeeNode(sourceAddress)); + ZigbeeNodeNxp *node = qobject_cast(getZigbeeNode(attributeReport.sourceAddress)); if (!node) { qCWarning(dcZigbeeNode()) << "Received an attribute report from an unknown node. Ignoring data."; return; } - ZigbeeClusterAttributeReport attributeReport; - attributeReport.sourceAddress = sourceAddress; - attributeReport.endpointId = endpointId; - attributeReport.clusterId = static_cast(clusterId); - attributeReport.attributeId = attributeId; - attributeReport.attributeStatus = static_cast(attributeStatus); - attributeReport.dataType = dataType; - attributeReport.data = attributeData; - node->setClusterAttributeReport(attributeReport); } diff --git a/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp b/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp index 045a963..f88e1a7 100644 --- a/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp +++ b/libnymea-zigbee/nxp/zigbeenodeendpointnxp.cpp @@ -43,7 +43,7 @@ ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::readAttribute(ZigbeeCluster *cluster, ZigbeeInterfaceReply *reply = m_controller->commandReadAttributeRequest(0x02, node()->shortAddress(), 0x01, endpointId(), cluster, attributes, false, node()->manufacturerCode()); - connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){ + connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { @@ -52,6 +52,10 @@ ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::readAttribute(ZigbeeCluster *cluster, } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; + if (!reply->additionalMessage().data().isEmpty()) { + ZigbeeClusterAttributeReport report = ZigbeeUtils::parseAttributeReport(reply->additionalMessage().data()); + setClusterAttribute(report.clusterId, ZigbeeClusterAttribute(report.attributeId, report.dataType, report.data)); + } }); return nullptr; @@ -60,8 +64,11 @@ ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::readAttribute(ZigbeeCluster *cluster, ZigbeeNetworkReply *ZigbeeNodeEndpointNxp::configureReporting(ZigbeeCluster *cluster, QList reportConfigurations) { qCDebug(dcZigbeeNode()) << "Configure reporting" << node(); + + // FIXME: check the report configuration and the direction field according to specs + ZigbeeInterfaceReply *reply = m_controller->commandConfigureReportingRequest(0x02, node()->shortAddress(), 0x01, - endpointId(), cluster, 0x01, + endpointId(), cluster, 0x00, false, node()->manufacturerCode(), reportConfigurations); connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){ reply->deleteLater(); @@ -336,4 +343,5 @@ void ZigbeeNodeEndpointNxp::setClusterAttribute(Zigbee::ClusterId clusterId, con } cluster->setAttribute(attribute); emit clusterAttributeChanged(cluster, attribute); + } diff --git a/libnymea-zigbee/nxp/zigbeenodenxp.cpp b/libnymea-zigbee/nxp/zigbeenodenxp.cpp index 3adee74..30ad436 100644 --- a/libnymea-zigbee/nxp/zigbeenodenxp.cpp +++ b/libnymea-zigbee/nxp/zigbeenodenxp.cpp @@ -51,6 +51,22 @@ ZigbeeNodeNxp::ZigbeeNodeNxp(ZigbeeBridgeControllerNxp *controller, QObject *par } +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; @@ -170,8 +186,10 @@ void ZigbeeNodeNxp::setInitState(ZigbeeNodeNxp::InitState initState) qCDebug(dcZigbeeNetwork()) << " Profile:" << ZigbeeUtils::profileIdToString(static_cast(profileId)); if (profileId == Zigbee::ZigbeeProfileLightLink) { qCDebug(dcZigbeeNetwork()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast(deviceId); - } else { + } else if (profileId == Zigbee::ZigbeeProfileHomeAutomation) { qCDebug(dcZigbeeNetwork()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast(deviceId); + } else if (profileId == Zigbee::ZigbeeProfileGreenPower) { + qCDebug(dcZigbeeNetwork()) << " Device ID:" << ZigbeeUtils::convertUint16ToHexString(deviceId) << static_cast(deviceId); } quint8 deviceVersion = (bitField >> 4); @@ -208,7 +226,7 @@ void ZigbeeNodeNxp::setInitState(ZigbeeNodeNxp::InitState initState) quint16 clusterId = 0; stream >> clusterId; - if (!endpoint->hasInputCluster(static_cast(clusterId))) { + if (!endpoint->hasOutputCluster(static_cast(clusterId))) { endpoint->addOutputCluster(new ZigbeeCluster(static_cast(clusterId), ZigbeeCluster::Output, endpoint)); } qCDebug(dcZigbeeNetwork()) << " Cluster ID:" << ZigbeeUtils::convertUint16ToHexString(clusterId) << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); @@ -218,20 +236,22 @@ void ZigbeeNodeNxp::setInitState(ZigbeeNodeNxp::InitState initState) m_uninitializedEndpoints.removeAll(endpointId); if (m_uninitializedEndpoints.isEmpty()) { qCDebug(dcZigbeeNode()) << "All endpoints fetched."; - setInitState(InitStateReadClusterAttributes); + setInitState(InitStateReadBasicClusterAttributes); } }); } break; - case InitStateReadClusterAttributes: -// if (shortAddress() == 0x0000) { -// qCDebug(dcZigbeeNode()) << "No need to read the endpoint baisc clusters of the coordinator node"; -// setState(StateInitialized); -// 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(endpoint); + // Read basic cluster qCDebug(dcZigbeeNode()) << "Read basic cluster for endpoint" << endpoint; @@ -242,34 +262,53 @@ void ZigbeeNodeNxp::setInitState(ZigbeeNodeNxp::InitState initState) return; } - QList attributes; - attributes.append(ZigbeeCluster::BasicAttributeZclVersion); - attributes.append(ZigbeeCluster::BasicAttributeManufacturerName); + 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. + // 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)) - attributes.append(ZigbeeCluster::BasicAttributeModelIdentifier); + m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributeModelIdentifier); - attributes.append(ZigbeeCluster::BasicAttributePowerSource); - attributes.append(ZigbeeCluster::BasicAttributeSwBuildId); - ZigbeeInterfaceReply *reply = m_controller->commandReadAttributeRequest(0x02, shortAddress(), - 0x01, endpoint->endpointId(), - basicCluster, - attributes, - false, - manufacturerCode()); + m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributePowerSource); + m_uninitalizedBasicClusterAttributes.append(ZigbeeCluster::BasicAttributeSwBuildId); - connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply, endpoint](){ - reply->deleteLater(); - if (reply->status() != ZigbeeInterfaceReply::Success) { - qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); - } + // Note: for having smoother flow request each attribute sequentially, not all at once - qCDebug(dcZigbeeNode()) << "Reading basic cluster attributes finished successfully for" << endpoint; - qCDebug(dcZigbeeNode()) << "The device should response with multiple attribute read notifications."; - setState(StateInitialized); - }); + 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; } } @@ -290,7 +329,7 @@ ZigbeeNodeEndpoint *ZigbeeNodeNxp::createNodeEndpoint(quint8 endpointId, QObject void ZigbeeNodeNxp::setClusterAttributeReport(const ZigbeeClusterAttributeReport &report) { if (report.attributeStatus != Zigbee::ZigbeeStatusSuccess) { - qCWarning(dcZigbeeNode()) << this << "Got incalid status report" << report.endpointId << report.clusterId << report.attributeId << report.attributeStatus; + qCWarning(dcZigbeeNode()) << this << "Got incalid status report" << report; return; } diff --git a/libnymea-zigbee/nxp/zigbeenodenxp.h b/libnymea-zigbee/nxp/zigbeenodenxp.h index 0b1709d..85c96e4 100644 --- a/libnymea-zigbee/nxp/zigbeenodenxp.h +++ b/libnymea-zigbee/nxp/zigbeenodenxp.h @@ -47,17 +47,20 @@ public: InitStatePowerDescriptor, InitStateActiveEndpoints, InitStateSimpleDescriptors, - InitStateReadClusterAttributes + InitStateReadBasicClusterAttributes }; Q_ENUM(InitState) explicit ZigbeeNodeNxp(ZigbeeBridgeControllerNxp *controller, QObject *parent = nullptr); + void leaveNetworkRequest(bool rejoin = false, bool removeChildren = false) override; + private: ZigbeeBridgeControllerNxp *m_controller = nullptr; InitState m_initState = InitStateNone; QList m_uninitializedEndpoints; + QList m_uninitalizedBasicClusterAttributes; void setInitState(InitState initState); void setClusterAttributeReport(const ZigbeeClusterAttributeReport &report) override; diff --git a/libnymea-zigbee/zigbee.h b/libnymea-zigbee/zigbee.h index faf3043..0170157 100644 --- a/libnymea-zigbee/zigbee.h +++ b/libnymea-zigbee/zigbee.h @@ -47,7 +47,8 @@ public: ZigbeeProfileTelecomAutomation = 0x0107, ZigbeeProfilePersonalHomeHospitalCare = 0x0108, ZigbeeProfileAdvancedMetering = 0x0109, - ZigbeeProfileLightLink = 0xC05E + ZigbeeProfileLightLink = 0xC05E, + ZigbeeProfileGreenPower = 0xA1E0 }; Q_ENUM(ZigbeeProfile) @@ -306,6 +307,10 @@ public: // Over the air uppgrade (OTA) ClusterIdOtaUpgrade = 0x0019, + // Poll controll + ClusterIdPollControl = 0x0020, + + // Closures ClusterIdShadeConfiguration = 0x0100, @@ -342,9 +347,6 @@ public: ClusterIdLoadControl = 0x0701, ClusterIdSimpleMetering = 0x0702, - // Electrical Measurement - ClusterIdElectricalMeasurement = 0x0B04, - // ZLL ClusterIdTouchlinkCommissioning = 0x1000, @@ -352,7 +354,15 @@ public: ClusterIdApplianceControl = 0x001B, ClusterIdApplianceIdentification = 0x0B00, ClusterIdApplianceEventsAlerts = 0x0B02, - ClusterIdApplianceStatistics = 0x0B03 + ClusterIdApplianceStatistics = 0x0B03, + + // Electrical Measurement + ClusterIdElectricalMeasurement = 0x0B04, + ClusterIdDiagnostics = 0x0B05, + + // Zigbee green power + ClusterIdGreenPower = 0x0021 + }; Q_ENUM(ClusterId) @@ -451,6 +461,18 @@ public: }; Q_ENUM(HomeAutomationDevice) + + enum GreenPowerDevice { + GreenPowerDeviceProxy = 0x0060, + GreenPowerDeviceProxyMinimum = 0x0061, + GreenPowerDeviceProxyTargetPlus = 0x0062, + GreenPowerDeviceProxyTarget = 0x0063, + GreenPowerDeviceProxyCommissioningTool = 0x0064, + GreenPowerDeviceProxyCombo = 0x0065, + GreenPowerDeviceProxyComboMinimum = 0x0066 + }; + Q_ENUM(GreenPowerDevice) + enum DataType { NoData = 0x00, Data8 = 0x08, diff --git a/libnymea-zigbee/zigbeecluster.cpp b/libnymea-zigbee/zigbeecluster.cpp index ef9a34c..54db161 100644 --- a/libnymea-zigbee/zigbeecluster.cpp +++ b/libnymea-zigbee/zigbeecluster.cpp @@ -93,3 +93,16 @@ QDebug operator<<(QDebug debug, ZigbeeCluster *cluster) return debug.space(); } + +QDebug operator<<(QDebug debug, const ZigbeeClusterAttributeReport &attributeReport) +{ + debug.nospace().noquote() << "AttributeReport(" + << attributeReport.clusterId << ", " + << attributeReport.attributeId << ", " + << attributeReport.attributeStatus << ", " + << attributeReport.dataType << ", " + << attributeReport.data << ", " + << ")"; + + return debug.space(); +} diff --git a/libnymea-zigbee/zigbeecluster.h b/libnymea-zigbee/zigbeecluster.h index bcfaa26..518e484 100644 --- a/libnymea-zigbee/zigbeecluster.h +++ b/libnymea-zigbee/zigbeecluster.h @@ -43,7 +43,7 @@ struct ZigbeeClusterReportConfigurationRecord { quint8 change; }; -struct ZigbeeClusterAttributeReport { +typedef struct ZigbeeClusterAttributeReport { quint16 sourceAddress; quint8 endpointId; Zigbee::ClusterId clusterId; @@ -51,8 +51,7 @@ struct ZigbeeClusterAttributeReport { Zigbee::ZigbeeStatus attributeStatus; Zigbee::DataType dataType; QByteArray data; -}; - +} ZigbeeClusterAttributeReport; class ZigbeeCluster : public QObject { @@ -218,5 +217,6 @@ signals: }; QDebug operator<<(QDebug debug, ZigbeeCluster *cluster); +QDebug operator<<(QDebug debug, const ZigbeeClusterAttributeReport &attributeReport); #endif // ZIGBEECLUSTER_H diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 6f2b177..65ae72d 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -280,7 +280,9 @@ void ZigbeeNetwork::loadNetwork() endpoint->m_profile = static_cast(settings.value("profile", 0).toUInt()); endpoint->m_deviceId = static_cast(settings.value("deviceId", 0).toUInt()); endpoint->m_deviceVersion = static_cast(settings.value("deviceId", 0).toUInt()); - //qCDebug(dcZigbeeNetwork()) << "Created" << endpoint; + endpoint->m_manufacturerName = settings.value("manufacturerName").toString(); + endpoint->m_modelIdentifier = settings.value("modelIdentifier").toString(); + endpoint->m_softwareBuildId = settings.value("softwareBuildId").toString(); int inputClustersCount = settings.beginReadArray("inputClusters"); for (int n = 0; n < inputClustersCount; n ++) { @@ -339,11 +341,6 @@ void ZigbeeNetwork::saveNode(ZigbeeNode *node) QSettings settings(m_settingsFileName, QSettings::IniFormat, this); settings.beginGroup("Nodes"); - // Clear settings for this node before storing it -// settings.beginGroup(node->extendedAddress().toString()); -// settings.remove(""); -// settings.endGroup(); - // Save this node settings.beginGroup(node->extendedAddress().toString()); settings.setValue("nwkAddress", node->shortAddress()); @@ -359,6 +356,9 @@ void ZigbeeNetwork::saveNode(ZigbeeNode *node) settings.setValue("profile", endpoint->profile()); settings.setValue("deviceId", endpoint->deviceId()); settings.setValue("deviceVersion", endpoint->deviceVersion()); + settings.setValue("manufacturerName", endpoint->manufacturerName()); + settings.setValue("modelIdentifier", endpoint->modelIdentifier()); + settings.setValue("softwareBuildId", endpoint->softwareBuildId()); settings.beginWriteArray("inputClusters"); for (int n = 0; n < endpoint->inputClusters().count(); n++) { diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 5d9db26..1d614d8 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -238,42 +238,6 @@ void ZigbeeNode::setConnected(bool connected) emit connectedChanged(m_connected); } - -//void ZigbeeNode::identify() -//{ -// QByteArray data; -// QDataStream stream(&data, QIODevice::WriteOnly); -// stream << m_shortAddress; -// stream << static_cast(0); - -// ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeManagementLqiRequest, data)); -// request.setExpectedAdditionalMessageType(Zigbee::MessageTypeManagementLqiResponse); -// request.setDescription("Node link quality request for " + ZigbeeUtils::convertUint16ToHexString(m_shortAddress)); -// request.setTimoutIntervall(10000); - -// ZigbeeInterfaceReply *reply = controller()->sendRequest(request); - // connect(reply, &ZigbeeInterfaceReply::finished, this, &ZigbeeNode::onRequestLinkQuality); -//} - -//void ZigbeeNode::toggle(int addressMode) -//{ -// QByteArray data; -// QDataStream stream(&data, QIODevice::WriteOnly); -// stream << static_cast(addressMode); // adress mode -// stream << m_shortAddress; -// stream << static_cast(1); // source endpoint -// stream << static_cast(1); // destination endpoint -// stream << static_cast(2); // command toggle - -// ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeCluserOnOff, data)); -// request.setDescription("Toggle request for " + ZigbeeUtils::convertUint16ToHexString(m_shortAddress)); - -// ZigbeeInterfaceReply *reply = controller()->sendRequest(request); -// connect(reply, &ZigbeeInterfaceReply::finished, this, &ZigbeeNode::onToggleFinished); -//} - - - void ZigbeeNode::setShortAddress(const quint16 &shortAddress) { m_shortAddress = shortAddress; @@ -587,6 +551,7 @@ QDebug operator<<(QDebug debug, ZigbeeNode *node) { debug.nospace().noquote() << "ZigbeeNode(" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress()); debug.nospace().noquote() << ", " << node->extendedAddress().toString(); + debug.nospace().noquote() << ", " << node->nodeType(); debug.nospace().noquote() << ")"; return debug.space(); } diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index e07f9c8..40fc968 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -147,6 +147,8 @@ public: QList availablePowerSources() const; PowerLevel powerLevel() const; + virtual void leaveNetworkRequest(bool rejoin = false, bool removeChildren = false) = 0; + private: bool m_connected = false; State m_state = StateUninitialized; diff --git a/libnymea-zigbee/zigbeenodeendpoint.cpp b/libnymea-zigbee/zigbeenodeendpoint.cpp index 80815b7..b7ac06f 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.cpp +++ b/libnymea-zigbee/zigbeenodeendpoint.cpp @@ -70,6 +70,21 @@ void ZigbeeNodeEndpoint::setDeviceVersion(quint8 deviceVersion) m_deviceVersion = deviceVersion; } +QString ZigbeeNodeEndpoint::manufacturerName() const +{ + return m_manufacturerName; +} + +QString ZigbeeNodeEndpoint::modelIdentifier() const +{ + return m_modelIdentifier; +} + +QString ZigbeeNodeEndpoint::softwareBuildId() const +{ + return m_softwareBuildId; +} + QList ZigbeeNodeEndpoint::inputClusters() const { return m_inputClusters.values(); @@ -151,8 +166,10 @@ QDebug operator<<(QDebug debug, ZigbeeNodeEndpoint *endpoint) debug.nospace().noquote() << ", " << endpoint->profile(); if (endpoint->profile() == Zigbee::ZigbeeProfileLightLink) { debug.nospace().noquote() << ", " << static_cast(endpoint->deviceId()); - } else { + } else if (endpoint->profile() == Zigbee::ZigbeeProfileHomeAutomation) { debug.nospace().noquote() << ", " << static_cast(endpoint->deviceId()); + } else if (endpoint->profile() == Zigbee::ZigbeeProfileGreenPower) { + debug.nospace().noquote() << ", " << static_cast(endpoint->deviceId()); } debug.nospace().noquote() << ")"; diff --git a/libnymea-zigbee/zigbeeutils.cpp b/libnymea-zigbee/zigbeeutils.cpp index 51bb0f2..8b74a24 100644 --- a/libnymea-zigbee/zigbeeutils.cpp +++ b/libnymea-zigbee/zigbeeutils.cpp @@ -420,3 +420,81 @@ QColor ZigbeeUtils::interpolateColorFromColorTemperature(int colorTemperature, i // FIXME: interpolate between the selected index and the next color for more accuracy if required return colorTemperatureScale.at(closestColorIndex); } + +ZigbeeClusterAttributeReport ZigbeeUtils::parseAttributeReport(const QByteArray &data) +{ + QByteArray dataCopy = data; + quint8 sequenceNumber = 0; + quint16 sourceAddress = 0; + quint8 endpointId = 0; + quint16 clusterId = 0; + quint16 attributeId = 0; + quint8 attributeStatus = 0; + quint8 attributDataType = 0; + quint16 dataSize = 0; + + QDataStream stream(&dataCopy, QIODevice::ReadOnly); + stream >> sequenceNumber >> sourceAddress >> endpointId >> clusterId >> attributeId >> attributeStatus >> attributDataType >> dataSize; + + Zigbee::DataType dataType = static_cast(attributDataType); + QByteArray attributeData = data.right(dataSize); + + if (attributeData.length() != dataSize) { + //qCWarning(dcZigbeeNetwork()) << "HACK" << attributeData.length() << "!=" << dataSize; + // Note: the NXP firmware for JN5169 has a bug here and does not send the attributeStatus. + // Repars data without attribute status + sequenceNumber = 0; + sourceAddress = 0; + endpointId = 0; + clusterId = 0; + attributeId = 0; + attributeStatus = 0; + attributDataType = 0; + dataSize = 0; + + QDataStream alternativeStream(&dataCopy, QIODevice::ReadOnly); + alternativeStream >> sequenceNumber >> sourceAddress >> endpointId >> clusterId >> attributeId >> attributDataType >> dataSize; + + dataType = static_cast(attributDataType); + attributeData = data.right(dataSize); + } + +// qCDebug(dcZigbeeNetwork()) << "Attribute read response:"; +// qCDebug(dcZigbeeNetwork()) << " SQN:" << ZigbeeUtils::convertByteToHexString(sequenceNumber); +// qCDebug(dcZigbeeNetwork()) << " Source address:" << ZigbeeUtils::convertUint16ToHexString(sourceAddress); +// qCDebug(dcZigbeeNetwork()) << " End point:" << ZigbeeUtils::convertByteToHexString(endpointId); +// qCDebug(dcZigbeeNetwork()) << " Cluster:" << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); +// qCDebug(dcZigbeeNetwork()) << " Attribut id:" << ZigbeeUtils::convertUint16ToHexString(attributeId); +// qCDebug(dcZigbeeNetwork()) << " Attribut status:" << static_cast(attributeStatus); +// qCDebug(dcZigbeeNetwork()) << " Attribut data type:" << dataType; +// qCDebug(dcZigbeeNetwork()) << " Attribut size:" << dataSize; +// qCDebug(dcZigbeeNetwork()) << " Data:" << ZigbeeUtils::convertByteArrayToHexString(attributeData); + +// switch (dataType) { +// case Zigbee::CharString: +// qCDebug(dcZigbeeNetwork()) << " Data(converted)" << QString::fromUtf8(attributeData); +// break; +// case Zigbee::Bool: +// qCDebug(dcZigbeeNetwork()) << " Data(converted)" << static_cast(attributeData.at(0)); +// break; +// default: +// break; +// } + + +// ZigbeeNodeNxp *node = qobject_cast(getZigbeeNode(sourceAddress)); +// if (!node) { +// qCWarning(dcZigbeeNode()) << "Received an attribute report from an unknown node. Ignoring data."; +// return; +// } + + ZigbeeClusterAttributeReport attributeReport; + attributeReport.sourceAddress = sourceAddress; + attributeReport.endpointId = endpointId; + attributeReport.clusterId = static_cast(clusterId); + attributeReport.attributeId = attributeId; + attributeReport.attributeStatus = static_cast(attributeStatus); + attributeReport.dataType = dataType; + attributeReport.data = attributeData; + return attributeReport; +} diff --git a/libnymea-zigbee/zigbeeutils.h b/libnymea-zigbee/zigbeeutils.h index 46416da..29fb23b 100644 --- a/libnymea-zigbee/zigbeeutils.h +++ b/libnymea-zigbee/zigbeeutils.h @@ -36,6 +36,7 @@ #include #include "zigbee.h" +#include "zigbeecluster.h" class ZigbeeUtils { @@ -73,6 +74,8 @@ public: // Color temperature interpolation static QColor interpolateColorFromColorTemperature(int colorTemperature, int minValue, int maxValue); + static ZigbeeClusterAttributeReport parseAttributeReport(const QByteArray &data); + }; #endif // ZIGBEEUTILS_H