diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp index d0e915f..146da05 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp +++ b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.cpp @@ -29,6 +29,7 @@ #include "zigbeenetworkreply.h" #include "loggingcategory.h" #include "zigbeenetwork.h" +#include "zigbeeutils.h" #include @@ -124,8 +125,17 @@ void ZigbeeClusterOnOff::processDataIndication(ZigbeeClusterLibrary::Frame frame case CommandToggle: emit commandSent(CommandToggle); break; + case CommandOnWithTimedOff: { + QByteArray payload = frame.payload; + QDataStream payloadStream(&payload, QIODevice::ReadOnly); + payloadStream.setByteOrder(QDataStream::LittleEndian); + quint8 acceptOnlyWhenOnInt = 0; quint16 onTime; quint16 offTime; + payloadStream >> acceptOnlyWhenOnInt >> onTime >> offTime; + emit commandOnWithTimedOffSent(static_cast(acceptOnlyWhenOnInt), onTime, offTime); + break; + } default: - qCWarning(dcZigbeeCluster()) << "Unhandled command sent from" << m_node << m_endpoint << this << command; + qCWarning(dcZigbeeCluster()) << "Unhandled command sent from" << m_node << m_endpoint << this << command << ZigbeeUtils::convertByteArrayToHexString(frame.payload); break; } } diff --git a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h index dd35e1e..1ea8b22 100644 --- a/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h +++ b/libnymea-zigbee/zcl/general/zigbeeclusteronoff.h @@ -95,7 +95,8 @@ signals: // Client cluster signals void commandSent(Command command); - + // On and off time is in 1/10 seconds + void commandOnWithTimedOffSent(bool acceptOnlyWhenOn, quint16 onTime, quint16 offTime); }; #endif // ZIGBEECLUSTERONOFF_H diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp index 7d04e41..9b4a8d6 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp @@ -28,6 +28,8 @@ #include "zigbeedeviceobject.h" #include "zigbeenetwork.h" #include "loggingcategory.h" +#include "zigbeedeviceprofile.h" +#include "zigbeeutils.h" #include #include @@ -41,7 +43,7 @@ ZigbeeDeviceObject::ZigbeeDeviceObject(ZigbeeNetwork *network, ZigbeeNode *node, } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestNodeDescriptor() -{ +{ qCDebug(dcZigbeeDeviceObject()) << "Request node descriptor from" << m_node; // Build APS request @@ -396,7 +398,7 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLqi(quint8 startIndex) QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); - stream << startIndex; + stream << transactionSequenceNumber << startIndex; // Set the ZDO frame as APS request payload request.setAsdu(asdu); @@ -431,6 +433,53 @@ ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLqi(quint8 startIndex) + finishZdoReply(zdoReply); + return; + } + // We received the confirmation but not yet the indication + }); + + return zdoReply; +} + +ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtBind(quint8 startIndex) +{ + qCDebug(dcZigbeeDeviceObject()) << "Request management bind table from" << m_node << "start index" << startIndex; + + // Build APS request + ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtBindRequest); + + // Generate a new transaction sequence number for this device object + quint8 transactionSequenceNumber = m_transactionSequenceNumber++; + + QByteArray asdu; + QDataStream stream(&asdu, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << transactionSequenceNumber << startIndex; + + // Set the ZDO frame as APS request payload + request.setAsdu(asdu); + + // Create the device object reply and wait for the response indication + ZigbeeDeviceObjectReply *zdoReply = createZigbeeDeviceObjectReply(request, transactionSequenceNumber); + + // Send the request, on finished read the confirm information + ZigbeeNetworkReply *networkReply = m_network->sendRequest(request); + connect(networkReply, &ZigbeeNetworkReply::finished, this, [this, networkReply, zdoReply](){ + if (!verifyNetworkError(zdoReply, networkReply)) { + qCWarning(dcZigbeeDeviceObject()) << "Failed to send request" + << static_cast(networkReply->request().clusterId()) + << m_node << networkReply->error() + << networkReply->zigbeeApsStatus(); + finishZdoReply(zdoReply); + return; + } + + // The request was successfully sent to the device + // Now check if the expected indication response received already + if (zdoReply->isComplete()) { + qCDebug(dcZigbeeDeviceObject()) << "Successfully received response for" << static_cast(networkReply->request().clusterId()); + finishZdoReply(zdoReply); return; } @@ -536,5 +585,5 @@ void ZigbeeDeviceObject::processApsDataIndication(const Zigbee::ApsdeDataIndicat return; } - qCWarning(dcZigbeeDeviceObject()) << "Unhandled ZDO indication" << m_node << asdu; + qCWarning(dcZigbeeDeviceObject()) << "Unhandled ZDO indication" << m_node << indication << asdu; } diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.h b/libnymea-zigbee/zdo/zigbeedeviceobject.h index 08d175f..c96f8a8 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.h +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.h @@ -57,7 +57,7 @@ public: // Management request ZigbeeDeviceObjectReply *requestMgmtLeaveNetwork(bool rejoin = false, bool removeChildren = false); ZigbeeDeviceObjectReply *requestMgmtLqi(quint8 startIndex = 0x00); - + ZigbeeDeviceObjectReply *requestMgmtBind(quint8 startIndex = 0x00); // TODO: write all requests diff --git a/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp b/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp index 6c13fb7..71267e7 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceprofile.cpp @@ -200,7 +200,7 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::Adpu &deviceAdpu) debug.nospace() << "DeviceAdpu(SQN: " << deviceAdpu.transactionSequenceNumber << ", "; debug.nospace() << deviceAdpu.status << ", "; debug.nospace() << ZigbeeUtils::convertUint16ToHexString(deviceAdpu.addressOfInterest) << ", "; - debug.nospace() << ZigbeeUtils::convertByteArrayToHexString(deviceAdpu.payload) << ")"; + debug.nospace() << "Payload: " << ZigbeeUtils::convertByteArrayToHexString(deviceAdpu.payload) << ")"; return debug.space(); } @@ -262,3 +262,22 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::PowerDescriptor &powe debug.nospace() << " Power level: " << powerDescriptor.powerLevel; return debug; } + +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecord &bindingTableListRecord) +{ + debug.nospace() << "BindingTableListRecord(" << bindingTableListRecord.sourceAddress.toString() << ", "; + debug.nospace() << "source endpoint: " << bindingTableListRecord.sourceEndpoint << ", "; + debug.nospace() << "cluster: " << static_cast(bindingTableListRecord.clusterId) << " --> "; + switch (bindingTableListRecord.destinationAddressMode) { + case Zigbee::DestinationAddressModeGroup: + debug.nospace() << "destination address (group): " << ZigbeeUtils::convertUint16ToHexString(bindingTableListRecord.destinationAddressShort) << ") "; + break; + case Zigbee::DestinationAddressModeIeeeAddress: + debug.nospace() << "destination address (unicast): " << bindingTableListRecord.destinationAddress.toString() << ", "; + debug.nospace() << "destination endpoint: " << bindingTableListRecord.destinationEndpoint << ") "; + break; + default: + break; + } + return debug; +} diff --git a/libnymea-zigbee/zdo/zigbeedeviceprofile.h b/libnymea-zigbee/zdo/zigbeedeviceprofile.h index e1cedba..679c9cf 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceprofile.h +++ b/libnymea-zigbee/zdo/zigbeedeviceprofile.h @@ -32,6 +32,7 @@ #include #include "zigbee.h" +#include "zigbeeaddress.h" class ZigbeeDeviceProfile { @@ -276,6 +277,17 @@ public: PowerLevel powerLevel = PowerLevelFull; } PowerDescriptor; + typedef struct BindingTableListRecord { + ZigbeeAddress sourceAddress; + quint8 sourceEndpoint; + quint16 clusterId; + Zigbee::DestinationAddressMode destinationAddressMode; // Note: group or unicast + quint16 destinationAddressShort; // Only for destination address 0x01 + ZigbeeAddress destinationAddress; // Only for destination address 0x03 + quint8 destinationEndpoint; // Only for destination address 0x03 + } BindingTableListRecord; + + static NodeDescriptor parseNodeDescriptor(const QByteArray &payload); static MacCapabilities parseMacCapabilities(quint8 macCapabilitiesFlag); static ServerMask parseServerMask(quint16 serverMaskFlag); @@ -290,5 +302,6 @@ QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::MacCapabilities &macC QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::ServerMask &serverMask); QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::DescriptorCapabilities &descriptorCapabilities); QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::PowerDescriptor &powerDescriptor); +QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecord &bindingTableListRecord); #endif // ZIGBEEDEVICEPROFILE_H diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index 1182017..e0b1333 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -154,6 +154,58 @@ void ZigbeeNode::startInitialization() initNodeDescriptor(); } +void ZigbeeNode::readBindingTableEntries() +{ + ZigbeeDeviceObjectReply * reply = deviceObject()->requestMgmtBind(); + connect(reply, &ZigbeeDeviceObjectReply::finished, this, [=](){ + if (reply->error() != ZigbeeDeviceObjectReply::ErrorNoError) { + qCWarning(dcZigbeeDeviceObject()) << "Failed to read binding table" << reply->error(); + return; + } + + // The request finished, but we received a ZDP error. + if (reply->responseAdpu().status != ZigbeeDeviceProfile::StatusSuccess) { + qCWarning(dcZigbeeNode()) << this << "failed to read node descriptor" << reply->responseAdpu().status; + return; + } + + qCDebug(dcZigbeeDeviceObject()) << "Bind table payload" << ZigbeeUtils::convertByteArrayToHexString(reply->responseData()); + QByteArray response = reply->responseData(); + QDataStream stream(&response, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + quint8 sqn; quint8 statusInt; quint8 entriesCount; quint8 startIndex; quint8 bindingTableListCount; + stream >> sqn >> statusInt >> entriesCount >> startIndex >> bindingTableListCount; + ZigbeeDeviceProfile::Status status = static_cast(statusInt); + qCDebug(dcZigbeeDeviceObject()) << "SQN:" << sqn << status << "entries:" << entriesCount << "index:" << startIndex << "list count:" << bindingTableListCount; + + QList bindingTableRecords; + for (int i = 0; i < bindingTableListCount; i++) { + quint64 sourceAddress; quint8 addressMode; + ZigbeeDeviceProfile::BindingTableListRecord record; + stream >> sourceAddress; + record.sourceAddress = ZigbeeAddress(sourceAddress); + + stream >> record.sourceEndpoint >> record.clusterId >> addressMode; + record.destinationAddressMode = static_cast(addressMode); + + if (addressMode == Zigbee::DestinationAddressModeGroup) { + stream >> record.destinationAddressShort; + } else if (addressMode == Zigbee::DestinationAddressModeIeeeAddress) { + quint64 destinationAddressIeee; + stream >> destinationAddressIeee >> record.destinationEndpoint; + record.destinationAddress = ZigbeeAddress(destinationAddressIeee); + } else { + qCWarning(dcZigbeeDeviceObject()) << "Invalid destination address mode in binding table record."; + break; + } + qCDebug(dcZigbeeDeviceObject()) << record; + bindingTableRecords << record; + } + + // TODO: continue reading if there are more entries + }); +} + void ZigbeeNode::initNodeDescriptor() { qCDebug(dcZigbeeNode()) << "Requst node descriptor from" << this; @@ -636,6 +688,6 @@ QDebug operator<<(QDebug debug, ZigbeeNode *node) { debug.nospace().noquote() << "ZigbeeNode(" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress()); debug.nospace().noquote() << ", " << node->extendedAddress().toString(); - debug.nospace().noquote() << ") "; - return debug; + debug.nospace().noquote() << ")"; + return debug.space().quote(); } diff --git a/libnymea-zigbee/zigbeenode.h b/libnymea-zigbee/zigbeenode.h index 3839761..d8ca4f9 100644 --- a/libnymea-zigbee/zigbeenode.h +++ b/libnymea-zigbee/zigbeenode.h @@ -85,6 +85,8 @@ public: // This method starts the node initialization phase (read descriptors and endpoints) void startInitialization(); + void readBindingTableEntries(); + private: ZigbeeNode(ZigbeeNetwork *network, quint16 shortAddress, const ZigbeeAddress &extendedAddress, QObject *parent = nullptr);