diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index a7299c0..d68a20c 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -8,9 +8,11 @@ SOURCES += \ backends/deconz/interface/zigbeeinterfacedeconzreply.cpp \ backends/deconz/zigbeebridgecontrollerdeconz.cpp \ backends/deconz/zigbeenetworkdeconz.cpp \ + zcl/general/zigbeeclusteridentify.cpp \ zcl/general/zigbeeclusteronoff.cpp \ zcl/measurement/zigbeeclusterrelativehumiditymeasurement.cpp \ zcl/measurement/zigbeeclustertemperaturemeasurement.cpp \ + zcl/security/zigbeeclusteriaszone.cpp \ zcl/zigbeecluster.cpp \ zcl/zigbeeclusterattribute.cpp \ zcl/zigbeeclusterlibrary.cpp \ @@ -52,9 +54,11 @@ HEADERS += \ backends/deconz/interface/zigbeeinterfacedeconzreply.h \ backends/deconz/zigbeebridgecontrollerdeconz.h \ backends/deconz/zigbeenetworkdeconz.h \ + zcl/general/zigbeeclusteridentify.h \ zcl/general/zigbeeclusteronoff.h \ zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h \ zcl/measurement/zigbeeclustertemperaturemeasurement.h \ + zcl/security/zigbeeclusteriaszone.h \ zcl/zigbeecluster.h \ zcl/zigbeeclusterattribute.h \ zcl/zigbeeclusterlibrary.h \ diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp b/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp new file mode 100644 index 0000000..a164184 --- /dev/null +++ b/libnymea-zigbee/zcl/general/zigbeeclusteridentify.cpp @@ -0,0 +1,217 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeclusteridentify.h" +#include "zigbeenetworkreply.h" +#include "loggingcategory.h" +#include "zigbeenetwork.h" + +#include + +ZigbeeClusterIdentify::ZigbeeClusterIdentify(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, ZigbeeCluster::Direction direction, QObject *parent) : + ZigbeeCluster(network, node, endpoint, Zigbee::ClusterIdIdentify, direction, parent) +{ + +} + +ZigbeeClusterReply *ZigbeeClusterIdentify::identify(quint16 seconds) +{ + ZigbeeNetworkRequest request = createGeneralRequest(); + + // Build ZCL frame + + ZigbeeClusterLibrary::FrameControl frameControl; + frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific; + frameControl.manufacturerSpecific = false; + frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer; + frameControl.disableDefaultResponse = false; + + // ZCL header + ZigbeeClusterLibrary::Header header; + header.frameControl = frameControl; + header.command = ZigbeeClusterIdentify::CommandIdentify; + header.transactionSequenceNumber = m_transactionSequenceNumber++; + + // Note: the identify time unit is 0.5 seconds + QByteArray payload = ZigbeeDataType(seconds * 2).data(); + + // Put them together + 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); + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){ + if (!verifyNetworkError(zclReply, networkReply)) { + qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request" + << m_node << networkReply->error() + << networkReply->zigbeeApsStatus(); + finishZclReply(zclReply); + return; + } + + // The request was successfully sent to the device + // Now check if the expected indication response received already + if (zclReply->isComplete()) { + finishZclReply(zclReply); + return; + } + }); + + return zclReply; +} + +ZigbeeClusterReply *ZigbeeClusterIdentify::identifyQuery() +{ + ZigbeeNetworkRequest request = createGeneralRequest(); + + // Build ZCL frame + + ZigbeeClusterLibrary::FrameControl frameControl; + frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific; + frameControl.manufacturerSpecific = false; + frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer; + frameControl.disableDefaultResponse = false; + + // ZCL header + ZigbeeClusterLibrary::Header header; + header.frameControl = frameControl; + header.command = ZigbeeClusterIdentify::CommandIdentifyQuery; + header.transactionSequenceNumber = m_transactionSequenceNumber++; + + // No payload + + // Put them together + ZigbeeClusterLibrary::Frame frame; + frame.header = header; + + request.setTxOptions(Zigbee::ZigbeeTxOptions(Zigbee::ZigbeeTxOptionAckTransmission)); + request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame)); + + ZigbeeClusterReply *zclReply = createClusterReply(request, frame); + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){ + if (!verifyNetworkError(zclReply, networkReply)) { + qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request" + << m_node << networkReply->error() + << networkReply->zigbeeApsStatus(); + finishZclReply(zclReply); + return; + } + + // The request was successfully sent to the device + // Now check if the expected indication response received already + if (zclReply->isComplete()) { + finishZclReply(zclReply); + return; + } + }); + + return zclReply; +} + +ZigbeeClusterReply *ZigbeeClusterIdentify::triggerEffect(ZigbeeClusterIdentify::Effect effect, quint8 effectVariant) +{ + ZigbeeNetworkRequest request = createGeneralRequest(); + + // Build ZCL frame + + ZigbeeClusterLibrary::FrameControl frameControl; + frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific; + frameControl.manufacturerSpecific = false; + frameControl.direction = ZigbeeClusterLibrary::DirectionClientToServer; + frameControl.disableDefaultResponse = false; + + // ZCL header + ZigbeeClusterLibrary::Header header; + header.frameControl = frameControl; + header.command = ZigbeeClusterIdentify::CommandTriggerEffect; + header.transactionSequenceNumber = m_transactionSequenceNumber++; + + QByteArray payload; + QDataStream stream(&payload, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(effect); + stream << static_cast(effectVariant); + + // Put them together + 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); + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zclReply](){ + if (!verifyNetworkError(zclReply, networkReply)) { + qCWarning(dcZigbeeClusterLibrary()) << "Failed to send request" + << m_node << networkReply->error() + << networkReply->zigbeeApsStatus(); + finishZclReply(zclReply); + return; + } + + // The request was successfully sent to the device + // Now check if the expected indication response received already + if (zclReply->isComplete()) { + finishZclReply(zclReply); + return; + } + }); + + return zclReply; +} + +void ZigbeeClusterIdentify::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); + } +} + +void ZigbeeClusterIdentify::processDataIndication(ZigbeeClusterLibrary::Frame frame) +{ + qCDebug(dcZigbeeCluster()) << "Processing cluster frame" << m_node << m_endpoint << this << frame; + + // TODO: implement identify query response + + // 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; +} diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteridentify.h b/libnymea-zigbee/zcl/general/zigbeeclusteridentify.h new file mode 100644 index 0000000..4d08fa6 --- /dev/null +++ b/libnymea-zigbee/zcl/general/zigbeeclusteridentify.h @@ -0,0 +1,86 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEECLUSTERIDENTIFY_H +#define ZIGBEECLUSTERIDENTIFY_H + +#include + +#include "zcl/zigbeecluster.h" +#include "zcl/zigbeeclusterreply.h" + +class ZigbeeNode; +class ZigbeeNetwork; +class ZigbeeNodeEndpoint; +class ZigbeeNetworkReply; + +class ZigbeeClusterIdentify : public ZigbeeCluster +{ + Q_OBJECT + + friend class ZigbeeNode; + friend class ZigbeeNetwork; + +public: + + enum Attribute { + AttributeIdentifyTime = 0x0000 + }; + Q_ENUM(Attribute) + + enum Command { + CommandIdentify = 0x00, + CommandIdentifyQuery = 0x01, + CommandTriggerEffect = 0x40 + }; + Q_ENUM(Command) + + enum Effect { + EffectBlink = 0x00, + EffectBreath = 0x01, + EffectOkay = 0x02, + EffectChannelChange = 0x0b, + EffectFinishEffect = 0xfe, + EffectStopEffect = 0xff + }; + Q_ENUM(Effect) + + explicit ZigbeeClusterIdentify(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); + + ZigbeeClusterReply *identify(quint16 seconds); + ZigbeeClusterReply *identifyQuery(); + ZigbeeClusterReply *triggerEffect(Effect effect, quint8 effectVariant = 0x00); + +private: + void setAttribute(const ZigbeeClusterAttribute &attribute) override; + +protected: + void processDataIndication(ZigbeeClusterLibrary::Frame frame) override; + +}; + +#endif // ZIGBEECLUSTERIDENTIFY_H diff --git a/libnymea-zigbee/zcl/security/zigbeeclusteriaszone.cpp b/libnymea-zigbee/zcl/security/zigbeeclusteriaszone.cpp new file mode 100644 index 0000000..7afd29e --- /dev/null +++ b/libnymea-zigbee/zcl/security/zigbeeclusteriaszone.cpp @@ -0,0 +1,99 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeclusteriaszone.h" +#include "zigbeenetworkreply.h" +#include "loggingcategory.h" +#include "zigbeenetwork.h" +#include "zigbeeutils.h" + +#include + +ZigbeeClusterIasZone::ZigbeeClusterIasZone(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, ZigbeeCluster::Direction direction, QObject *parent) : + ZigbeeCluster(network, node, endpoint, Zigbee::ClusterIdIasZone, direction, parent) +{ + +} + +void ZigbeeClusterIasZone::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); + } +} + +void ZigbeeClusterIasZone::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: + // TODO: handle client frames + break; + case Server: + // If the client cluster sends data to a server cluster (independent which), the command was executed on the device like button pressed + if (frame.header.frameControl.direction == ZigbeeClusterLibrary::DirectionServerToClient) { + // Read the payload which is + ClientCommand command = static_cast(frame.header.command); + qCDebug(dcZigbeeCluster()) << "Command received from" << m_node << m_endpoint << this << command; + switch (command) { + case ClientCommandStatusChangedNotification: { + QDataStream stream(frame.payload); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 zoneStatus = 0; quint8 extendedStatus = 0; quint8 zoneId = 0xff; quint16 delay = 0; + stream >> zoneStatus >> extendedStatus >> zoneId >> delay; + qCDebug(dcZigbeeCluster()) << "IAS zone status notification from" << m_node << m_endpoint << this + << ZoneStatusFlags(zoneStatus) << "Extended status:" << ZigbeeUtils::convertByteToHexString(extendedStatus) + << "Zone ID:" << ZigbeeUtils::convertByteToHexString(zoneId) << "Delay:" << delay << "[s/4]"; + emit zoneStatusChanged(ZoneStatusFlags(zoneStatus), extendedStatus, zoneId, delay); + break; + } + case ClientCommandZoneEnrollRequest: { + QDataStream stream(frame.payload); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 zoneTypeInt = 0; quint16 manufacturerCode = 0; + stream >> zoneTypeInt >> manufacturerCode; + ZoneType zoneType = static_cast(zoneTypeInt); + qCDebug(dcZigbeeCluster()) << "IAS zone enroll request from" << m_node << m_endpoint << this + << zoneType << "Manufacturer code:" << ZigbeeUtils::convertUint16ToHexString(manufacturerCode); + emit zoneEnrollRequest(zoneType, manufacturerCode); + break; + } + } + } + break; + } + +} diff --git a/libnymea-zigbee/zcl/security/zigbeeclusteriaszone.h b/libnymea-zigbee/zcl/security/zigbeeclusteriaszone.h new file mode 100644 index 0000000..6650581 --- /dev/null +++ b/libnymea-zigbee/zcl/security/zigbeeclusteriaszone.h @@ -0,0 +1,142 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEECLUSTERIASZONE_H +#define ZIGBEECLUSTERIASZONE_H + +#include + +#include "zcl/zigbeecluster.h" +#include "zcl/zigbeeclusterreply.h" + +class ZigbeeNode; +class ZigbeeNetwork; +class ZigbeeNodeEndpoint; +class ZigbeeNetworkReply; + +class ZigbeeClusterIasZone : public ZigbeeCluster +{ + Q_OBJECT + + friend class ZigbeeNode; + friend class ZigbeeNetwork; + +public: + + enum Attribute { + // Zone information attribute set + AttributeZoneState = 0x0000, + AttributeZoneType = 0x0001, + AttributeZoneStatus = 0x0002, + // Zone settings attribute set + AttributeCieAddress = 0x0010, + AttributeZoneId = 0x0011, + AttributeNumberOfZoneSensitivityLevelsSupported = 0x0012, + AttributeCurrentZoneSensitivityLevel = 0x0013 + }; + Q_ENUM(Attribute) + + enum ZoneState { + ZoneStateNotEnrolled = 0x00, + ZoneStateEnrolled = 0x01 + }; + Q_ENUM(ZoneState) + + enum ZoneType { + ZoneTypeStandardCIE = 0x0000, + ZoneTypeMotionSensor = 0x000d, + ZoneTypeContactSwitch = 0x0015, + ZoneTypeFireSensor = 0x0028, + ZoneTypeWaterSensor = 0x002a, + ZoneTypeCarbonMonoxideSensor = 0x002b, + ZoneTypePersonalEmergencyDevice = 0x002c, + ZoneTypeVibrationMovementSensor = 0x002d, + ZoneTypeRemoteControl = 0x010f, + ZoneTypeKeyFob = 0x0115, + ZoneTypeKeypad = 0x021d, + ZoneTypeStandardWarningDevice = 0x0225, + ZoneTypeGlassBreakSensor = 0x0226, + ZoneTypeSecurityRepater = 0x0229, + ZoneTypeInvalidZone = 0xffff + }; + Q_ENUM(ZoneType) + + enum ZoneStatusFlag { + ZoneStatusFlagAlarm1 = 0x0001, + ZoneStatusFlagAlarm2 = 0x0002, + ZoneStatusFlagTamper = 0x0004, + ZoneStatusFlagBattery = 0x0008, + ZoneStatusFlagSupervisionReports = 0x0010, + ZoneStatusFlagRestoreReports = 0x0020, + ZoneStatusFlagTrouble = 0x0040, + ZoneStatusFlagAcMains = 0x0080, + ZoneStatusFlagTest = 0x0100, + ZoneStatusFlagBatteryDefect = 0x0200 + }; + Q_ENUM(ZoneStatusFlag) + Q_DECLARE_FLAGS(ZoneStatusFlags, ZoneStatusFlag) + + enum EnrollResponseCode { + EnrollResponseCodeSuccess = 0x00, + EnrollResponseCodeNotSupported = 0x01, + EnrollResponseCodeNoEnrollPermit = 0x02, + EnrollResponseCodeToManyZones = 0x03 + }; + Q_ENUM(EnrollResponseCode) + + enum ClientCommand { + ClientCommandStatusChangedNotification = 0x00, // M + ClientCommandZoneEnrollRequest = 0x01 // M + }; + Q_ENUM(ClientCommand) + + enum ServerCommand { + ServerCommandEnrollResponse = 0x00, // M + ServerCommandInitNormalOperationMode = 0x01, // O + ServerCommandInitTestMode = 0x02 // O + }; + Q_ENUM(ServerCommand) + + explicit ZigbeeClusterIasZone(ZigbeeNetwork *network, ZigbeeNode *node, ZigbeeNodeEndpoint *endpoint, Direction direction, QObject *parent = nullptr); + + // TODO: write server commands + +private: + void setAttribute(const ZigbeeClusterAttribute &attribute) override; + +protected: + void processDataIndication(ZigbeeClusterLibrary::Frame frame) override; + +signals: + void zoneStatusChanged(ZoneStatusFlags zoneStatus, quint8 extendedStatus, quint8 zoneId, quint16 delay); + void zoneEnrollRequest(ZoneType zoneType, quint16 manufacturerCode); + +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(ZigbeeClusterIasZone::ZoneStatusFlags) + +#endif // ZIGBEECLUSTERIASZONE_H diff --git a/libnymea-zigbee/zcl/zigbeecluster.cpp b/libnymea-zigbee/zcl/zigbeecluster.cpp index 162f1f7..f4f9d00 100644 --- a/libnymea-zigbee/zcl/zigbeecluster.cpp +++ b/libnymea-zigbee/zcl/zigbeecluster.cpp @@ -218,6 +218,8 @@ void ZigbeeCluster::processDataIndication(ZigbeeClusterLibrary::Frame frame) { // Increase the tsn for continuouse id increasing on both sides m_transactionSequenceNumber = frame.header.transactionSequenceNumber; + + // Warn about the unhandled cluster indication, you can override this method in cluster implementations qCWarning(dcZigbeeCluster()) << "Unhandled ZCL indication in" << m_node << m_endpoint << this << frame; } @@ -246,6 +248,9 @@ void ZigbeeCluster::processApsDataIndication(const QByteArray &asdu, const Zigbe } } + // Increase the tsn for continuouse id increasing on both sides + m_transactionSequenceNumber = frame.header.transactionSequenceNumber; + return; } @@ -261,11 +266,14 @@ void ZigbeeCluster::processApsDataIndication(const QByteArray &asdu, const Zigbe stream >> attributeId >> type; ZigbeeDataType dataType = ZigbeeClusterLibrary::readDataType(&stream, static_cast(type)); setAttribute(ZigbeeClusterAttribute(attributeId, dataType)); + + // Increase the tsn for continuouse id increasing on both sides + m_transactionSequenceNumber = frame.header.transactionSequenceNumber; + return; } } - // Not for a reply or not an attribute report, let the cluster process this message internally processDataIndication(frame); } diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 8296345..64c964d 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -352,8 +352,7 @@ void ZigbeeNetwork::loadNetwork() settings.setArrayIndex(n); Zigbee::ClusterId clusterId = static_cast(settings.value("clusterId", 0).toUInt()); ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Server); - //qCDebug(dcZigbeeNetwork()) << "Created" << cluster; - endpoint->m_inputClusters.insert(clusterId, cluster); + endpoint->addInputCluster(cluster); } settings.endArray(); // inputClusters @@ -362,12 +361,11 @@ void ZigbeeNetwork::loadNetwork() settings.setArrayIndex(n); Zigbee::ClusterId clusterId = static_cast(settings.value("clusterId", 0).toUInt()); ZigbeeCluster *cluster = endpoint->createCluster(clusterId, ZigbeeCluster::Client); - //qCDebug(dcZigbeeNetwork()) << "Created" << cluster; - endpoint->m_outputClusters.insert(clusterId, cluster); + endpoint->addOutputCluster(cluster); } settings.endArray(); // outputClusters - node->m_endpoints.append(endpoint); + node->m_endpoints.insert(endpoint->endpointId(), endpoint); } settings.endArray(); // endpoints diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index b9ef143..4cfc1e4 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -68,23 +68,17 @@ ZigbeeAddress ZigbeeNode::extendedAddress() const QList ZigbeeNode::endpoints() const { - return m_endpoints; + return m_endpoints.values(); } bool ZigbeeNode::hasEndpoint(quint8 endpointId) const { - return getEndpoint(endpointId) != nullptr; + return m_endpoints.keys().contains(endpointId); } ZigbeeNodeEndpoint *ZigbeeNode::getEndpoint(quint8 endpointId) const { - foreach (ZigbeeNodeEndpoint *endpoint, m_endpoints) { - if (endpoint->endpointId() == endpointId) { - return endpoint; - } - } - - return nullptr; + return m_endpoints.value(endpointId); } ZigbeeNode::NodeType ZigbeeNode::nodeType() const @@ -641,7 +635,7 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) ZigbeeNodeEndpoint *endpoint = nullptr; if (!hasEndpoint(endpointId)) { endpoint = new ZigbeeNodeEndpoint(m_network, this, endpointId, this); - m_endpoints.append(endpoint); + m_endpoints.insert(endpoint->endpointId(), endpoint); } else { endpoint = getEndpoint(endpointId); } @@ -694,7 +688,7 @@ void ZigbeeNode::initEndpoint(quint8 endpointId) void ZigbeeNode::initBasicCluster() { // FIXME: check if we want to read from all endpoints the basic cluster information or only from the first - ZigbeeClusterBasic *basicCluster = m_endpoints.first()->inputCluster(Zigbee::ClusterIdBasic); + ZigbeeClusterBasic *basicCluster = endpoints().first()->inputCluster(Zigbee::ClusterIdBasic); if (!basicCluster) { qCWarning(dcZigbeeNode()) << "Could not find basic cluster on" << this << "Set the node to initialized anyways."; // Set the device initialized any ways since this ist just for convinience @@ -718,7 +712,7 @@ void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster) bool valueOk = false; QString manufacturerName = basicCluster->attribute(attributeId).dataType().toString(&valueOk); if (valueOk) { - m_endpoints.first()->m_manufacturerName = manufacturerName; + endpoints().first()->m_manufacturerName = manufacturerName; } else { qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << basicCluster->attribute(attributeId).dataType(); } @@ -742,7 +736,7 @@ void ZigbeeNode::readManufacturerName(ZigbeeClusterBasic *basicCluster) bool valueOk = false; QString manufacturerName = attributeStatusRecord.dataType.toString(&valueOk); if (valueOk) { - m_endpoints.first()->m_manufacturerName = manufacturerName; + endpoints().first()->m_manufacturerName = manufacturerName; } else { qCWarning(dcZigbeeNode()) << "Could not convert manufacturer name attribute data to string" << attributeStatusRecord.dataType; } @@ -766,7 +760,7 @@ void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster) bool valueOk = false; QString modelIdentifier = basicCluster->attribute(attributeId).dataType().toString(&valueOk); if (valueOk) { - m_endpoints.first()->m_modelIdentifier= modelIdentifier; + endpoints().first()->m_modelIdentifier= modelIdentifier; } else { qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << basicCluster->attribute(attributeId).dataType(); } @@ -790,7 +784,7 @@ void ZigbeeNode::readModelIdentifier(ZigbeeClusterBasic *basicCluster) bool valueOk = false; QString modelIdentifier = attributeStatusRecord.dataType.toString(&valueOk); if (valueOk) { - m_endpoints.first()->m_modelIdentifier = modelIdentifier; + endpoints().first()->m_modelIdentifier = modelIdentifier; } else { qCWarning(dcZigbeeNode()) << "Could not convert model identifier attribute data to string" << attributeStatusRecord.dataType; } @@ -820,7 +814,7 @@ void ZigbeeNode::readSoftwareBuildId(ZigbeeClusterBasic *basicCluster) bool valueOk = false; QString softwareBuildId = attributeStatusRecord.dataType.toString(&valueOk); if (valueOk) { - m_endpoints.first()->m_softwareBuildId = softwareBuildId; + endpoints().first()->m_softwareBuildId = softwareBuildId; } else { qCWarning(dcZigbeeNode()) << "Could not convert software build id attribute data to string" << attributeStatusRecord.dataType; } @@ -845,7 +839,7 @@ void ZigbeeNode::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataInd endpoint = new ZigbeeNodeEndpoint(m_network, this, indication.sourceEndpoint, this); endpoint->setProfile(static_cast(indication.profileId)); // Note: the endpoint is not initializd yet, but keep it anyways - m_endpoints.append(endpoint); + m_endpoints.insert(endpoint->endpointId(), endpoint); } endpoint->handleZigbeeClusterLibraryIndication(indication); diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index c2971db..fadf600 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -199,7 +199,7 @@ private: ZigbeeNetwork *m_network; ZigbeeDeviceObject *m_deviceObject = nullptr; - QList m_endpoints; + QHash m_endpoints; // Node descriptor information QByteArray m_nodeDescriptorRawData; @@ -277,6 +277,7 @@ private: // Init methods int m_requestRetry = 0; QList m_uninitializedEndpoints; + void initNodeDescriptor(); void initPowerDescriptor(); void initEndpoints(); diff --git a/libnymea-zigbee/zigbeenodeendpoint.cpp b/libnymea-zigbee/zigbeenodeendpoint.cpp index 2fc32f8..904d926 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.cpp +++ b/libnymea-zigbee/zigbeenodeendpoint.cpp @@ -112,6 +112,11 @@ QList ZigbeeNodeEndpoint::outputClusters() const return m_outputClusters.values(); } +ZigbeeCluster *ZigbeeNodeEndpoint::getOutputCluster(Zigbee::ClusterId clusterId) const +{ + return m_outputClusters.value(clusterId); +} + bool ZigbeeNodeEndpoint::hasOutputCluster(Zigbee::ClusterId clusterId) const { return m_outputClusters.keys().contains(clusterId); @@ -161,18 +166,27 @@ void ZigbeeNodeEndpoint::setSoftwareBuildId(const QString &softwareBuildId) ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(Zigbee::ClusterId clusterId, ZigbeeCluster::Direction direction) { switch (clusterId) { + // General case Zigbee::ClusterIdBasic: return new ZigbeeClusterBasic(m_network, m_node, this, direction, this); break; case Zigbee::ClusterIdOnOff: return new ZigbeeClusterOnOff(m_network, m_node, this, direction, this); break; + case Zigbee::ClusterIdIdentify: + return new ZigbeeClusterIdentify(m_network, m_node, this, direction, this); + break; + // Measurement case Zigbee::ClusterIdTemperatureMeasurement: return new ZigbeeClusterTemperatureMeasurement(m_network, m_node, this, direction, this); break; case Zigbee::ClusterIdRelativeHumidityMeasurement: return new ZigbeeClusterRelativeHumidityMeasurement(m_network, m_node, this, direction, this); break; + // Security + case Zigbee::ClusterIdIasZone: + return new ZigbeeClusterIasZone(m_network, m_node, this, direction, this); + break; default: // Return a default cluster since we have no special implementation for this cluster return new ZigbeeCluster(m_network, m_node, this, clusterId, direction, this); @@ -182,11 +196,13 @@ ZigbeeCluster *ZigbeeNodeEndpoint::createCluster(Zigbee::ClusterId clusterId, Zi void ZigbeeNodeEndpoint::addInputCluster(ZigbeeCluster *cluster) { m_inputClusters.insert(cluster->clusterId(), cluster); + emit inputClusterAdded(cluster); } void ZigbeeNodeEndpoint::addOutputCluster(ZigbeeCluster *cluster) { m_outputClusters.insert(cluster->clusterId(), cluster); + emit outputClusterAdded(cluster); } void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication) @@ -203,7 +219,7 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd 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; - m_outputClusters.insert(cluster->clusterId(), cluster); + addOutputCluster(cluster); } break; case ZigbeeClusterLibrary::DirectionServerToClient: @@ -212,7 +228,7 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd 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; - m_inputClusters.insert(cluster->clusterId(), cluster); + addInputCluster(cluster); } break; } @@ -220,11 +236,6 @@ void ZigbeeNodeEndpoint::handleZigbeeClusterLibraryIndication(const Zigbee::Apsd cluster->processApsDataIndication(indication.asdu, frame); } -ZigbeeCluster *ZigbeeNodeEndpoint::getOutputCluster(Zigbee::ClusterId clusterId) const -{ - return m_outputClusters.value(clusterId); -} - QDebug operator<<(QDebug debug, ZigbeeNodeEndpoint *endpoint) { debug.nospace().noquote() << "ZigbeeNodeEndpoint(" << ZigbeeUtils::convertByteToHexString(endpoint->endpointId()); diff --git a/libnymea-zigbee/zigbeenodeendpoint.h b/libnymea-zigbee/zigbeenodeendpoint.h index 7208c31..b77b2f1 100644 --- a/libnymea-zigbee/zigbeenodeendpoint.h +++ b/libnymea-zigbee/zigbeenodeendpoint.h @@ -36,11 +36,16 @@ // Import all implemented cluster types #include "zcl/zigbeecluster.h" + #include "zcl/general/zigbeeclusterbasic.h" #include "zcl/general/zigbeeclusteronoff.h" +#include "zcl/general/zigbeeclusteridentify.h" + #include "zcl/measurement/zigbeeclustertemperaturemeasurement.h" #include "zcl/measurement/zigbeeclusterrelativehumiditymeasurement.h" +#include "zcl/security/zigbeeclusteriaszone.h" + class ZigbeeNode; class ZigbeeNetwork; @@ -82,6 +87,7 @@ public: ZigbeeCluster *getOutputCluster(Zigbee::ClusterId clusterId) const; bool hasOutputCluster(Zigbee::ClusterId clusterId) const; + // Convinience cast methods for getting a specific cluster object template inline T* inputCluster(Zigbee::ClusterId clusterId) { @@ -123,9 +129,6 @@ private: void setModelIdentifier(const QString &modelIdentifier); void setSoftwareBuildId(const QString &softwareBuildId); - // Cluster commands - //virtual void setClusterAttribute(Zigbee::ClusterId clusterId, const ZigbeeClusterAttribute &attribute = ZigbeeClusterAttribute()) = 0; - ZigbeeCluster *createCluster(Zigbee::ClusterId clusterId, ZigbeeCluster::Direction direction); void addInputCluster(ZigbeeCluster *cluster); @@ -134,12 +137,14 @@ private: void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication); signals: + void inputClusterAdded(ZigbeeCluster *cluster); + void outputClusterAdded(ZigbeeCluster *cluster); + void clusterAttributeChanged(ZigbeeCluster *cluster, const ZigbeeClusterAttribute &attribute); void manufacturerNameChanged(const QString &manufacturerName); void modelIdentifierChanged(const QString &modelIdentifier); void softwareBuildIdChanged(const QString &softwareBuildId); - }; QDebug operator<<(QDebug debug, ZigbeeNodeEndpoint *endpoint);