/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 "zigbeedeviceobject.h" #include "zigbeenetwork.h" #include "loggingcategory.h" #include #include ZigbeeDeviceObject::ZigbeeDeviceObject(ZigbeeNetwork *network, ZigbeeNode *node, QObject *parent) : QObject(parent), m_network(network), m_node(node) { } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestNodeDescriptor() { qCDebug(dcZigbeeDeviceObject()) << "Request node descriptor from" << m_node; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::NodeDescriptorRequest); // Generate a new transaction sequence number for this device object quint8 transactionSequenceNumber = m_transactionSequenceNumber++; // Build ZDO frame QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << transactionSequenceNumber << m_node->shortAddress(); // 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()) { finishZdoReply(zdoReply); return; } // We received the confirmation but not yet the indication }); return zdoReply; } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestPowerDescriptor() { qCDebug(dcZigbeeDeviceObject()) << "Request power descriptor from" << m_node; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::PowerDescriptorRequest); // Generate a new transaction sequence number for this device object quint8 transactionSequenceNumber = m_transactionSequenceNumber++; // Build ZDO frame QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << transactionSequenceNumber << m_node->shortAddress(); // 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()) { finishZdoReply(zdoReply); return; } // We received the confirmation but not yet the indication }); return zdoReply; } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestActiveEndpoints() { qCDebug(dcZigbeeDeviceObject()) << "Request active endpoints from" << m_node; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::ActiveEndpointsRequest); // Generate a new transaction sequence number for this device object quint8 transactionSequenceNumber = m_transactionSequenceNumber++; // Build ZDO frame QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << transactionSequenceNumber << m_node->shortAddress(); // 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()) { finishZdoReply(zdoReply); return; } // We received the confirmation but not yet the indication }); return zdoReply; } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestSimpleDescriptor(quint8 endpointId) { qCDebug(dcZigbeeDeviceObject()) << "Request simple descriptor from" << m_node << "endpoint" << endpointId; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::SimpleDescriptorRequest); // Generate a new transaction sequence number for this device object quint8 transactionSequenceNumber = m_transactionSequenceNumber++; // Build ZDO frame QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << transactionSequenceNumber << request.destinationShortAddress() << endpointId; // 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()) { finishZdoReply(zdoReply); return; } // We received the confirmation but not yet the indication }); return zdoReply; } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestBindIeeeAddress(quint8 sourceEndpointId, quint16 clusterId, const ZigbeeAddress &destinationIeeeAddress, quint8 destinationEndpointId) { qCDebug(dcZigbeeDeviceObject()) << "Request bind ieee address from" << m_node << "endpoint" << clusterId << "to" << destinationIeeeAddress.toString() << destinationEndpointId; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::BindRequest); // Generate a new transaction sequence number for this device object quint8 transactionSequenceNumber = m_transactionSequenceNumber++; // Build ZDO frame QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << transactionSequenceNumber; stream << m_node->extendedAddress().toUInt64(); stream << sourceEndpointId; stream << clusterId; stream << static_cast(Zigbee::DestinationAddressModeIeeeAddress); stream << destinationIeeeAddress.toUInt64(); stream << destinationEndpointId; // 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()) { finishZdoReply(zdoReply); return; } // We received the confirmation but not yet the indication }); return zdoReply; } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::requestMgmtLeaveNetwork(bool rejoin, bool removeChildren) { qCDebug(dcZigbeeDeviceObject()) << "Request management leave network from" << m_node << "rejoin" << rejoin << "remove children" << removeChildren; // Build APS request ZigbeeNetworkRequest request = buildZdoRequest(ZigbeeDeviceProfile::MgmtLeaveRequest); // Generate a new transaction sequence number for this device object quint8 transactionSequenceNumber = m_transactionSequenceNumber++; // Build ZDO frame quint8 leaveFlag = 0; if (rejoin) { leaveFlag |= 0x01; } if (removeChildren) { leaveFlag |= 0x02; } QByteArray asdu; QDataStream stream(&asdu, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << transactionSequenceNumber << m_node->extendedAddress().toUInt64() << leaveFlag; // 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()) { finishZdoReply(zdoReply); return; } // We received the confirmation but not yet the indication }); return zdoReply; } ZigbeeNetworkRequest ZigbeeDeviceObject::buildZdoRequest(quint16 zdoRequest) { ZigbeeNetworkRequest request; request.setRequestId(m_network->generateSequenceNumber()); request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress); request.setDestinationShortAddress(m_node->shortAddress()); request.setDestinationEndpoint(0); // ZDO request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP request.setClusterId(zdoRequest); request.setSourceEndpoint(0); // ZDO return request; } ZigbeeDeviceObjectReply *ZigbeeDeviceObject::createZigbeeDeviceObjectReply(const ZigbeeNetworkRequest &request, quint8 transactionSequenceNumber) { ZigbeeDeviceObjectReply *zdoReply = new ZigbeeDeviceObjectReply(request, this); connect(zdoReply, &ZigbeeDeviceObjectReply::finished, zdoReply, &ZigbeeDeviceObjectReply::deleteLater, Qt::QueuedConnection); zdoReply->m_expectedResponse = static_cast(request.clusterId() | 0x8000); zdoReply->m_transactionSequenceNumber = transactionSequenceNumber; m_pendingReplies.insert(transactionSequenceNumber, zdoReply); return zdoReply; } bool ZigbeeDeviceObject::verifyNetworkError(ZigbeeDeviceObjectReply *zdoReply, ZigbeeNetworkReply *networkReply) { bool success = false; switch (networkReply->error()) { case ZigbeeNetworkReply::ErrorNoError: // The request has been transported successfully to he destination, now // wait for the expected indication or check if we already recieved it zdoReply->m_apsConfirmReceived = true; zdoReply->m_zigbeeApsStatus = networkReply->zigbeeApsStatus(); zdoReply->m_zigbeeNwkStatus = networkReply->zigbeeNwkStatus(); success = true; break; case ZigbeeNetworkReply::ErrorInterfaceError: zdoReply->m_error = ZigbeeDeviceObjectReply::ErrorInterfaceError; break; case ZigbeeNetworkReply::ErrorTimeout: zdoReply->m_error = ZigbeeDeviceObjectReply::ErrorTimeout; break; case ZigbeeNetworkReply::ErrorNetworkOffline: zdoReply->m_error = ZigbeeDeviceObjectReply::ErrorNetworkOffline; break; case ZigbeeNetworkReply::ErrorZigbeeApsStatusError: zdoReply->m_apsConfirmReceived = true; zdoReply->m_error = ZigbeeDeviceObjectReply::ErrorZigbeeApsStatusError; zdoReply->m_zigbeeApsStatus = networkReply->zigbeeApsStatus(); break; case ZigbeeNetworkReply::ErrorZigbeeNwkStatusError: zdoReply->m_apsConfirmReceived = true; zdoReply->m_error = ZigbeeDeviceObjectReply::ErrorZigbeeNwkStatusError; zdoReply->m_zigbeeNwkStatus = networkReply->zigbeeNwkStatus(); break; } return success; } void ZigbeeDeviceObject::finishZdoReply(ZigbeeDeviceObjectReply *zdoReply) { switch(zdoReply->error()) { case ZigbeeDeviceObjectReply::ErrorNoError: qCDebug(dcZigbeeDeviceObject()) << "Reply finished successfully" << zdoReply->request(); break; case ZigbeeDeviceObjectReply::ErrorZigbeeApsStatusError: qCWarning(dcZigbeeDeviceObject()) << "Failed to send request to device" << zdoReply->request() << zdoReply->error() << zdoReply->zigbeeApsStatus(); break; case ZigbeeDeviceObjectReply::ErrorZigbeeNwkStatusError: qCWarning(dcZigbeeDeviceObject()) << "Failed to send request to device" << zdoReply->request() << zdoReply->error() << zdoReply->zigbeeNwkStatus(); break; default: qCWarning(dcZigbeeDeviceObject()) << "Failed to send request to device" << zdoReply->request() << zdoReply->error(); break; } m_pendingReplies.remove(zdoReply->transactionSequenceNumber()); zdoReply->finished(); } void ZigbeeDeviceObject::processApsDataIndication(const Zigbee::ApsdeDataIndication &indication) { // Check if we have a waiting ZDO reply for this data ZigbeeDeviceProfile::Adpu asdu = ZigbeeDeviceProfile::parseAdpu(indication.asdu); ZigbeeDeviceObjectReply *zdoReply = m_pendingReplies.value(asdu.transactionSequenceNumber); if (zdoReply && indication.clusterId == (zdoReply->request().clusterId() | 0x8000)) { zdoReply->m_responseData = indication.asdu; zdoReply->m_responseAdpu = asdu; zdoReply->m_zdpIndicationReceived = true; if (zdoReply->isComplete()) { finishZdoReply(zdoReply); } return; } qCWarning(dcZigbeeDeviceObject()) << "Unhandled ZDO indication" << m_node << asdu; }