diff --git a/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h b/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h index aaa2f84..e174628 100644 --- a/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h +++ b/libnymea-zigbee/backends/deconz/zigbeebridgecontrollerdeconz.h @@ -146,9 +146,6 @@ signals: void networkStateChanged(Deconz::NetworkState networkState); void networkConfigurationParameterChanged(const DeconzNetworkConfiguration &networkConfiguration); - void apsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); - void apsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); - private slots: void onInterfaceAvailableChanged(bool available); void onInterfacePackageReceived(const QByteArray &package); diff --git a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp index d7279b1..ae1c642 100644 --- a/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp +++ b/libnymea-zigbee/backends/deconz/zigbeenetworkdeconz.cpp @@ -411,7 +411,6 @@ void ZigbeeNetworkDeconz::setPermitJoiningInternal(bool permitJoining) qCDebug(dcZigbeeNetwork()) << "Set permit join configuration request finished" << reply->statusCode(); }); - }); } diff --git a/libnymea-zigbee/backends/nxp/interface/nxp.h b/libnymea-zigbee/backends/nxp/interface/nxp.h index 942395e..c459edf 100644 --- a/libnymea-zigbee/backends/nxp/interface/nxp.h +++ b/libnymea-zigbee/backends/nxp/interface/nxp.h @@ -19,13 +19,18 @@ public: CommandStartNetwork = 0x07, CommandGetNetworkState = 0x08, - CommandSetPermitJoinCoordinator = 0x09 + CommandSetPermitJoinCoordinator = 0x09, + + + CommandSendApsDataRequest = 0x20 }; Q_ENUM(Command) enum Notification { NotificationDeviceStatusChanged = 0x7D, NotificationNetworkStarted = 0x7E, + NotificationApsDataConfirm = 0x80, + NotificationApsDataIndication = 0x81, NotificationDebugMessage = 0xFE }; Q_ENUM(Notification) diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp index de0f603..af031e5 100644 --- a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp @@ -1,4 +1,4 @@ -#include "zigbeebridgecontrollernxp.h" +#include "zigbeebridgecontrollernxp.h" #include "loggingcategory.h" #include "zigbeeutils.h" @@ -22,6 +22,25 @@ ZigbeeBridgeControllerNxp::ControllerState ZigbeeBridgeControllerNxp::controller return m_controllerState; } +void ZigbeeBridgeControllerNxp::refreshControllerState() +{ + // Get controller state + qCDebug(dcZigbeeController()) << "Refresh controller state"; + ZigbeeInterfaceNxpReply *reply = requestControllerState(); + connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ + qCDebug(dcZigbeeNetwork()) << "Request controller state" << reply->status(); + + if (reply->status() != Nxp::StatusSuccess) { + qCWarning(dcZigbeeController()) << "Failed to request controller state" << reply->status(); + return; + } + + m_controllerState = static_cast(reply->responseData().at(0)); + qCDebug(dcZigbeeController()) << "Controller state changed" << m_controllerState; + emit controllerStateChanged(m_controllerState); + }); +} + ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestVersion() { QByteArray message; @@ -160,6 +179,32 @@ ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSetPermitJoinCoordina return createReply(Nxp::CommandSetPermitJoinCoordinator, m_sequenceNumber, "Request set permit join in coordinator", message, this); } +ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSendRequest(const ZigbeeNetworkRequest &request) +{ + ZigbeeInterfaceNxpReply *interfaceReply = nullptr; + qCDebug(dcZigbeeAps()) << "APSDE-DATA.request" << request; + + switch (request.destinationAddressMode()) { + case Zigbee::DestinationAddressModeGroup: + interfaceReply = requestEnqueueSendDataGroup(request.requestId(), request.destinationShortAddress(), + request.profileId(), request.clusterId(),request.sourceEndpoint(), + request.asdu(), request.txOptions(), request.radius()); + break; + case Zigbee::DestinationAddressModeShortAddress: + interfaceReply = requestEnqueueSendDataShortAddress(request.requestId(), request.destinationShortAddress(), + request.destinationEndpoint(), request.profileId(), request.clusterId(), + request.sourceEndpoint(), request.asdu(), request.txOptions(), request.radius()); + break; + case Zigbee::DestinationAddressModeIeeeAddress: + interfaceReply = requestEnqueueSendDataIeeeAddress(request.requestId(), request.destinationIeeeAddress(), + request.destinationEndpoint(), request.profileId(), request.clusterId(), + request.sourceEndpoint(), request.asdu(), request.txOptions(), request.radius()); + break; + } + + return interfaceReply; +} + ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::createReply(Nxp::Command command, quint8 sequenceNumber, const QString &requestName, const QByteArray &requestData, QObject *parent) { // Create the reply @@ -193,6 +238,114 @@ void ZigbeeBridgeControllerNxp::bumpSequenceNumber() m_sequenceNumber += 1; } +ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestEnqueueSendDataGroup(quint8 requestId, quint16 groupAddress, quint16 profileId, quint16 clusterId, quint8 sourceEndpoint, const QByteArray &asdu, Zigbee::ZigbeeTxOptions txOptions, quint8 radius) +{ + Q_UNUSED(txOptions) + Q_ASSERT_X(asdu.length() <= 127, "ASDU", "ASDU package length has to <= 127 bytes"); + + QByteArray payload; + QDataStream payloadStream(&payload, QIODevice::WriteOnly); + payloadStream.setByteOrder(QDataStream::LittleEndian); + payloadStream << requestId; + payloadStream << static_cast(Zigbee::DestinationAddressModeGroup); + payloadStream << groupAddress; + payloadStream << static_cast(0); // Note: group has no destination endpoint + payloadStream << profileId; + payloadStream << clusterId; + payloadStream << sourceEndpoint; + payloadStream << static_cast(0x00); // Network and application layer security + payloadStream << radius; + payloadStream << static_cast(asdu.size()); + for (int i = 0; i < asdu.size(); i++) { + payloadStream << static_cast(asdu.at(i)); + } + + QByteArray message; + bumpSequenceNumber(); + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Nxp::CommandSendApsDataRequest); + stream << static_cast(m_sequenceNumber); + stream << static_cast(payload.size()); + for (int i = 0; i < payload.size(); i++) { + stream << static_cast(payload.at(i)); + } + + return createReply(Nxp::CommandSendApsDataRequest, m_sequenceNumber, "Request send ASP data request to group", message, this); +} + +ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestEnqueueSendDataShortAddress(quint8 requestId, quint16 shortAddress, quint8 destinationEndpoint, quint16 profileId, quint16 clusterId, quint8 sourceEndpoint, const QByteArray &asdu, Zigbee::ZigbeeTxOptions txOptions, quint8 radius) +{ + Q_UNUSED(txOptions) + Q_ASSERT_X(asdu.length() <= 127, "ASDU", "ASDU package length has to <= 127 bytes"); + + QByteArray payload; + QDataStream payloadStream(&payload, QIODevice::WriteOnly); + payloadStream.setByteOrder(QDataStream::LittleEndian); + payloadStream << requestId; + payloadStream << static_cast(Zigbee::DestinationAddressModeShortAddress); + payloadStream << shortAddress; + payloadStream << destinationEndpoint; + payloadStream << profileId; + payloadStream << clusterId; + payloadStream << sourceEndpoint; + payloadStream << static_cast(0x00); // Network and application layer security + payloadStream << radius; + payloadStream << static_cast(asdu.size()); + for (int i = 0; i < asdu.size(); i++) { + payloadStream << static_cast(asdu.at(i)); + } + + QByteArray message; + bumpSequenceNumber(); + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Nxp::CommandSendApsDataRequest); + stream << static_cast(m_sequenceNumber); + stream << static_cast(payload.size()); + for (int i = 0; i < payload.size(); i++) { + stream << static_cast(payload.at(i)); + } + + return createReply(Nxp::CommandSendApsDataRequest, m_sequenceNumber, "Request send ASP data request to short address", message, this); +} + +ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestEnqueueSendDataIeeeAddress(quint8 requestId, ZigbeeAddress ieeeAddress, quint8 destinationEndpoint, quint16 profileId, quint16 clusterId, quint8 sourceEndpoint, const QByteArray &asdu, Zigbee::ZigbeeTxOptions txOptions, quint8 radius) +{ + Q_UNUSED(txOptions) + Q_ASSERT_X(asdu.length() <= 127, "ASDU", "ASDU package length has to <= 127 bytes"); + + QByteArray payload; + QDataStream payloadStream(&payload, QIODevice::WriteOnly); + payloadStream.setByteOrder(QDataStream::LittleEndian); + payloadStream << requestId; + payloadStream << static_cast(Zigbee::DestinationAddressModeIeeeAddress); + payloadStream << ieeeAddress.toUInt64(); + payloadStream << destinationEndpoint; + payloadStream << profileId; + payloadStream << clusterId; + payloadStream << sourceEndpoint; + payloadStream << static_cast(0x00); // Network and application layer security + payloadStream << radius; + payloadStream << static_cast(asdu.size()); + for (int i = 0; i < asdu.size(); i++) { + payloadStream << static_cast(asdu.at(i)); + } + + QByteArray message; + bumpSequenceNumber(); + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Nxp::CommandSendApsDataRequest); + stream << static_cast(m_sequenceNumber); + stream << static_cast(payload.size()); + for (int i = 0; i < payload.size(); i++) { + stream << static_cast(payload.at(i)); + } + + return createReply(Nxp::CommandSendApsDataRequest, m_sequenceNumber, "Request send ASP data request to IEEE address", message, this); +} + void ZigbeeBridgeControllerNxp::onInterfaceAvailableChanged(bool available) { qCDebug(dcZigbeeController()) << "Interface available changed" << available; @@ -210,29 +363,98 @@ void ZigbeeBridgeControllerNxp::onInterfacePackageReceived(const QByteArray &pac if (commandInt >= 0x7D) { quint16 payloadLength = 0; stream >> payloadLength; - QByteArray data = package.mid(4, payloadLength); + QByteArray payload = package.mid(4, payloadLength); if (package.length() != payloadLength + 4) { qCWarning(dcZigbeeController()) << "Invalid package length received" << ZigbeeUtils::convertByteArrayToHexString(package) << payloadLength; return; } Nxp::Notification notification = static_cast(commandInt); - qCDebug(dcZigbeeController()) << "Interface notification received" << notification << "SQN:" << sequenceNumber << ZigbeeUtils::convertByteArrayToHexString(data); + //qCDebug(dcZigbeeController()) << "Interface notification received" << notification << "SQN:" << sequenceNumber << ZigbeeUtils::convertByteArrayToHexString(payload); switch (notification) { - case Nxp::NotificationDebugMessage: - if (data.isEmpty()) { - qCWarning(dcZigbeeController()) << "Received empty debug log notification"; - return; - } - qCDebug(dcZigbeeController()) << "*****DEBUG*****" << static_cast(data.at(0)) << "\n" << qUtf8Printable(data.right(data.length() - 1)); - break; case Nxp::NotificationDeviceStatusChanged: - m_controllerState = static_cast(data.at(0)); + m_controllerState = static_cast(payload.at(0)); qCDebug(dcZigbeeController()) << "Controller state changed" << m_controllerState; emit controllerStateChanged(m_controllerState); break; + case Nxp::NotificationApsDataConfirm: { + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + Zigbee::ApsdeDataConfirm confirm; + stream >> confirm.requestId >> confirm.destinationAddressMode; + if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeGroup || confirm.destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) + stream >> confirm.destinationShortAddress; + + if (confirm.destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) + stream >> confirm.destinationIeeeAddress; + + stream >> confirm.sourceEndpoint >> confirm.destinationEndpoint >> confirm.zigbeeStatusCode; + + qCDebug(dcZigbeeController()) << confirm; + qCDebug(dcZigbeeAps()) << "APSDE-DATA.confirm" << confirm; + + emit apsDataConfirmReceived(confirm); + break; + } + case Nxp::NotificationApsDataIndication: { + QDataStream stream(&payload, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + Zigbee::ApsdeDataIndication indication; + quint8 status; + stream >> status; + stream >> indication.destinationAddressMode; + Zigbee::DestinationAddressMode destinationAddressMode = static_cast(indication.destinationAddressMode); + if (destinationAddressMode == Zigbee::DestinationAddressModeGroup || destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) + stream >> indication.destinationShortAddress; + + if (destinationAddressMode == Zigbee::DestinationAddressModeIeeeAddress) + stream >> indication.destinationIeeeAddress; + + stream >> indication.destinationEndpoint; + + stream >> indication.sourceAddressMode; + if (indication.sourceAddressMode == Zigbee::DestinationAddressModeGroup || indication.sourceAddressMode == Zigbee::DestinationAddressModeShortAddress) + stream >> indication.sourceShortAddress; + + if (indication.sourceAddressMode == Zigbee::DestinationAddressModeIeeeAddress) + stream >> indication.sourceShortAddress; + + stream >> indication.sourceEndpoint; + stream >> indication.profileId; + stream >> indication.clusterId; + quint16 asduLength = 0; + stream >> asduLength; + for (int i = 0; i < asduLength; i++) { + quint8 byte = 0; + stream >> byte; + indication.asdu.append(static_cast(byte)); + } + stream >> indication.lqi; + + // FIXME: security status + + qCDebug(dcZigbeeController()) << indication; + qCDebug(dcZigbeeAps()) << "APSDE-DATA.indication" << indication; + + emit apsDataIndicationReceived(indication); + break; + } + case Nxp::NotificationDebugMessage: { + if (payload.isEmpty()) { + qCWarning(dcZigbeeController()) << "Received empty debug log notification"; + return; + } + Nxp::LogLevel logLevel = static_cast(payload.at(0)); + QString debugMessage = QString::fromLocal8Bit(payload.right(payload.length() - 1)); + if (static_cast(logLevel) <= static_cast(Nxp::LogLevelWarning)) { + qCWarning(dcZigbeeController()) << "***** Controller DEBUG *****" << logLevel << debugMessage; + } else { + qCDebug(dcZigbeeController()) << "***** Controller DEBUG *****" << logLevel << debugMessage; + } + break; + } default: - emit interfaceNotificationReceived(notification, data); + emit interfaceNotificationReceived(notification, payload); break; } } else { diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h index c4644ca..e1cd2ed 100644 --- a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h +++ b/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h @@ -34,6 +34,7 @@ public: Q_ENUM(ControllerState) ControllerState controllerState() const; + void refreshControllerState(); // Controllere requests ZigbeeInterfaceNxpReply *requestVersion(); @@ -52,6 +53,7 @@ public: ZigbeeInterfaceNxpReply *requestSetPermitJoinCoordinator(quint8 duration); // APS + ZigbeeInterfaceNxpReply *requestSendRequest(const ZigbeeNetworkRequest &request); signals: @@ -69,6 +71,11 @@ private: void bumpSequenceNumber(); + ZigbeeInterfaceNxpReply *requestEnqueueSendDataGroup(quint8 requestId, quint16 groupAddress, quint16 profileId, quint16 clusterId, quint8 sourceEndpoint, const QByteArray &asdu, Zigbee::ZigbeeTxOptions txOptions, quint8 radius = 0); + ZigbeeInterfaceNxpReply *requestEnqueueSendDataShortAddress(quint8 requestId, quint16 shortAddress, quint8 destinationEndpoint, quint16 profileId, quint16 clusterId, quint8 sourceEndpoint, const QByteArray &asdu, Zigbee::ZigbeeTxOptions txOptions, quint8 radius = 0); + ZigbeeInterfaceNxpReply *requestEnqueueSendDataIeeeAddress(quint8 requestId, ZigbeeAddress ieeeAddress, quint8 destinationEndpoint, quint16 profileId, quint16 clusterId, quint8 sourceEndpoint, const QByteArray &asdu, Zigbee::ZigbeeTxOptions txOptions, quint8 radius = 0); + + private slots: void onInterfaceAvailableChanged(bool available); void onInterfacePackageReceived(const QByteArray &package); diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp index 17ccfe1..3c0e002 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp @@ -11,8 +11,15 @@ ZigbeeNetworkNxp::ZigbeeNetworkNxp(QObject *parent) : connect(m_controller, &ZigbeeBridgeControllerNxp::availableChanged, this, &ZigbeeNetworkNxp::onControllerAvailableChanged); connect(m_controller, &ZigbeeBridgeControllerNxp::interfaceNotificationReceived, this, &ZigbeeNetworkNxp::onInterfaceNotificationReceived); connect(m_controller, &ZigbeeBridgeControllerNxp::controllerStateChanged, this, &ZigbeeNetworkNxp::onControllerStateChanged); - //connect(m_controller, &ZigbeeBridgeControllerNxp::apsDataConfirmReceived, this, &ZigbeeNetworkNxp::onApsDataConfirmReceived); - //connect(m_controller, &ZigbeeBridgeControllerNxp::apsDataIndicationReceived, this, &ZigbeeNetworkNxp::onApsDataIndicationReceived); + connect(m_controller, &ZigbeeBridgeControllerNxp::apsDataConfirmReceived, this, &ZigbeeNetworkNxp::onApsDataConfirmReceived); + connect(m_controller, &ZigbeeBridgeControllerNxp::apsDataIndicationReceived, this, &ZigbeeNetworkNxp::onApsDataIndicationReceived); + + m_permitJoinRefreshTimer = new QTimer(this); + m_permitJoinRefreshTimer->setInterval(250 * 1000); + m_permitJoinRefreshTimer->setSingleShot(false); + connect(m_permitJoinRefreshTimer, &QTimer::timeout, this, [this](){ + setPermitJoiningInternal(true); + }); } ZigbeeBridgeController *ZigbeeNetworkNxp::bridgeController() const @@ -25,27 +32,130 @@ ZigbeeBridgeController *ZigbeeNetworkNxp::bridgeController() const ZigbeeNetworkReply *ZigbeeNetworkNxp::sendRequest(const ZigbeeNetworkRequest &request) { - Q_UNUSED(request) - return nullptr; + ZigbeeNetworkReply *reply = createNetworkReply(request); + // Send the request, and keep the reply until transposrt, zigbee trasmission and response arrived + connect(reply, &ZigbeeNetworkReply::finished, this, [this, request](){ + m_pendingReplies.remove(request.requestId()); + }); + + // Finish the reply right the way if the network is offline + if (!m_controller->available()) { + finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNetworkOffline); + return reply; + } + + ZigbeeInterfaceNxpReply *interfaceReply = m_controller->requestSendRequest(request); + connect(interfaceReply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply, interfaceReply](){ + if (interfaceReply->status() != Nxp::StatusSuccess) { + qCWarning(dcZigbeeController()) << "Could send request to controller. SQN:" << interfaceReply->sequenceNumber() << interfaceReply->status(); + finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError); + return; + } + + // Note: this is a special case for nxp coordinator requests, they don't send a confirm because the request will not be sent trough the network + if (reply->request().destinationShortAddress() == 0x0000 && reply->request().profileId() == Zigbee::ZigbeeProfileDevice) { + qCDebug(dcZigbeeNetwork()) << "Finish reply since there will be no CONFIRM for local node requests."; + finishNetworkReply(reply); + return; + } + + quint8 networkRequestId = interfaceReply->responseData().at(0); + qCDebug(dcZigbeeNetwork()) << "Request has network SQN" << networkRequestId; + reply->request().setRequestId(networkRequestId); + m_pendingReplies.insert(networkRequestId, reply); + + // The request has been sent successfully to the device, start the timeout timer now + startWaitingReply(reply); + }); + + return reply; } ZigbeeNetworkReply *ZigbeeNetworkNxp::setPermitJoin(quint16 shortAddress, quint8 duration) { - Q_UNUSED(shortAddress) - Q_UNUSED(duration) - return nullptr; + // Get the power descriptor + ZigbeeNetworkRequest request; + request.setRequestId(generateSequenceNumber()); + request.setDestinationAddressMode(Zigbee::DestinationAddressModeGroup); + request.setDestinationShortAddress(static_cast(shortAddress)); + request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP + request.setClusterId(ZigbeeDeviceProfile::MgmtPermitJoinRequest); + request.setSourceEndpoint(0); // ZDO + request.setRadius(0); + + // Build ASDU + QByteArray asdu; + QDataStream stream(&asdu, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << request.requestId(); + stream << duration; + stream << static_cast(0x01); // TrustCenter significance, always force to 1 according to Spec. + request.setTxOptions(Zigbee::ZigbeeTxOptions()); // no ACK for broadcasts + request.setAsdu(asdu); + + qCDebug(dcZigbeeNetwork()) << "Send permit join request" << ZigbeeUtils::convertUint16ToHexString(request.destinationShortAddress()) << duration << "s"; + return sendRequest(request); +} + +void ZigbeeNetworkNxp::handleZigbeeDeviceProfileIndication(const Zigbee::ApsdeDataIndication &indication) +{ + // Check if this is a device announcement + if (indication.clusterId == ZigbeeDeviceProfile::DeviceAnnounce) { + QDataStream stream(indication.asdu); + stream.setByteOrder(QDataStream::LittleEndian); + quint8 sequenceNumber = 0; quint16 shortAddress = 0; quint64 ieeeAddress = 0; quint8 macFlag = 0; + stream >> sequenceNumber >> shortAddress >> ieeeAddress >> macFlag; + onDeviceAnnounced(shortAddress, ZigbeeAddress(ieeeAddress), macFlag); + return; + } + + if (indication.destinationShortAddress == Zigbee::BroadcastAddressAllNodes || + indication.destinationShortAddress == Zigbee::BroadcastAddressAllRouters || + indication.destinationShortAddress == Zigbee::BroadcastAddressAllNonSleepingNodes) { + qCDebug(dcZigbeeNetwork()) << "Received unhandled broadcast ZDO indication" << indication; + + // FIXME: check what we can do with such messages like permit join + return; + } + + ZigbeeNode *node = getZigbeeNode(indication.sourceShortAddress); + if (!node) { + qCWarning(dcZigbeeNetwork()) << "Received a ZDO indication for an unrecognized node. There is no such node in the system. Ignoring indication" << indication; + // FIXME: check if we want to create it since the device definitly exists within the network + return; + } + + // Let the node handle this indication + handleNodeIndication(node, indication); +} + +void ZigbeeNetworkNxp::handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication) +{ + ZigbeeClusterLibrary::Frame frame = ZigbeeClusterLibrary::parseFrameData(indication.asdu); + //qCDebug(dcZigbeeNetwork()) << "Handle ZCL indication" << indication << frame; + + // Get the node + ZigbeeNode *node = getZigbeeNode(indication.sourceShortAddress); + if (!node) { + qCWarning(dcZigbeeNetwork()) << "Received a ZCL indication for an unrecognized node. There is no such node in the system. Ignoring indication" << indication; + // FIXME: maybe create and init the node, since it is in the network, but not recognized + // FIXME: maybe remove this node since we might have removed it but it did not respond, or we not explicitly allowed it to join. + return; + } + // Let the node handle this indication + handleNodeIndication(node, indication); } void ZigbeeNetworkNxp::onControllerAvailableChanged(bool available) { qCDebug(dcZigbeeNetwork()) << "Controller is" << (available ? "now available" : "not available any more"); if (available) { - // Get controller state + m_controller->refreshControllerState(); // Get network state, depending on the controller state //reset(); - factoryResetNetwork(); + //factoryResetNetwork(); } else { setState(StateOffline); } @@ -71,7 +181,6 @@ void ZigbeeNetworkNxp::onControllerStateChanged(ZigbeeBridgeControllerNxp::Contr qCDebug(dcZigbeeNetwork()) << "Controller version" << versionString; m_controller->setFirmwareVersion(versionString); - qCDebug(dcZigbeeNetwork()) << "Get the current network state"; ZigbeeInterfaceNxpReply *reply = m_controller->requestNetworkState(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ @@ -106,27 +215,18 @@ void ZigbeeNetworkNxp::onControllerStateChanged(ZigbeeBridgeControllerNxp::Contr ZigbeeNode *coordinatorNode = createNode(networkAddress, ZigbeeAddress(ieeeAddress), this); m_coordinatorNode = coordinatorNode; - // TODO: initialize - - m_database->saveNode(m_coordinatorNode); - saveNetwork(); - setState(StateRunning); - -// // Network creation done when coordinator node is initialized -// connect(coordinatorNode, &ZigbeeNode::stateChanged, this, [this, coordinatorNode](ZigbeeNode::State state){ -// if (state == ZigbeeNode::StateInitialized) { -// qCDebug(dcZigbeeNetwork()) << "Coordinator initialized successfully." << coordinatorNode; -// m_initializing = false; -// setState(StateRunning); -// setPermitJoiningInternal(false); -// return; -// } -// }); - -// coordinatorNode->startInitialization(); -// addUnitializedNode(coordinatorNode); - + // Network creation done when coordinator node is initialized + connect(coordinatorNode, &ZigbeeNode::stateChanged, this, [this, coordinatorNode](ZigbeeNode::State state){ + if (state == ZigbeeNode::StateInitialized) { + qCDebug(dcZigbeeNetwork()) << "Coordinator initialized successfully." << coordinatorNode; + setState(StateRunning); + setPermitJoiningInternal(false); + return; + } + }); + coordinatorNode->startInitialization(); + addUnitializedNode(coordinatorNode); }); }); break; @@ -226,10 +326,52 @@ void ZigbeeNetworkNxp::onInterfaceNotificationReceived(Nxp::Notification notific } default: qCWarning(dcZigbeeNetwork()) << "Unhandeld interface notification received" << notification << ZigbeeUtils::convertByteArrayToHexString(payload); + break; + } +} +void ZigbeeNetworkNxp::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm) +{ + ZigbeeNetworkReply *reply = m_pendingReplies.value(confirm.requestId); + if (!reply) { + qCWarning(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation"; + return; } + setReplyResponseError(reply, static_cast(confirm.zigbeeStatusCode)); +} +void ZigbeeNetworkNxp::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication) +{ + // Check if this indocation is related to any pending reply + if (indication.profileId == Zigbee::ZigbeeProfileDevice) { + handleZigbeeDeviceProfileIndication(indication); + return; + } + + // Let the node handle this indication + handleZigbeeClusterLibraryIndication(indication); +} + +void ZigbeeNetworkNxp::onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress ieeeAddress, quint8 macCapabilities) +{ + qCDebug(dcZigbeeNetwork()) << "Device announced" << ZigbeeUtils::convertUint16ToHexString(shortAddress) << ieeeAddress.toString() << ZigbeeUtils::convertByteToHexString(macCapabilities); + + // Lets check if this device is in the uninitialized node list, if so, remove it and recreate the device + if (hasUninitializedNode(ieeeAddress)) { + qCWarning(dcZigbeeNetwork()) << "Device announced but there is already an initialization running for it. Remove the device and restart the initialization."; + ZigbeeNode *uninitializedNode = getZigbeeNode(ieeeAddress); + removeUninitializedNode(uninitializedNode); + } + + if (hasNode(ieeeAddress)) { + qCWarning(dcZigbeeNetwork()) << "Already known device announced. FIXME: Ignoring announcement" << ieeeAddress.toString(); + return; + } + + ZigbeeNode *node = createNode(shortAddress, ieeeAddress, macCapabilities, this); + addUnitializedNode(node); + node->startInitialization(); } void ZigbeeNetworkNxp::setPermitJoiningInternal(bool permitJoining) @@ -241,14 +383,40 @@ void ZigbeeNetworkNxp::setPermitJoiningInternal(bool permitJoining) duration = 255; } - // TODO: send broadcast message using ZDO and refrash + // Note: since compliance version >= 21 the value 255 is not any more Qt::endless. + // we need to refresh the command on timeout - qCDebug(dcZigbeeNetwork()) << "Set permit join in the coordinator node to" << duration << "[s]"; - ZigbeeInterfaceNxpReply *reply = m_controller->requestSetPermitJoinCoordinator(duration); - connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply, permitJoining](){ - qCDebug(dcZigbeeNetwork()) << "Set permit join in the coordinator finished" << reply->status(); - m_permitJoining = permitJoining; - emit permitJoiningChanged(m_permitJoining); + + ZigbeeNetworkReply *reply = setPermitJoin(Zigbee::BroadcastAddressAllRouters, duration); + connect(reply, &ZigbeeNetworkReply::finished, this, [this, reply, permitJoining, duration](){ + if (reply->zigbeeApsStatus() != Zigbee::ZigbeeApsStatusSuccess) { + qCDebug(dcZigbeeNetwork()) << "Could not set permit join to" << duration; + m_permitJoining = false; + emit permitJoiningChanged(m_permitJoining); + return; + } + + qCDebug(dcZigbeeNetwork()) << "Permit join request finished successfully"; + if (permitJoining) { + m_permitJoinRefreshTimer->start(); + } else { + m_permitJoinRefreshTimer->stop(); + } + + if (m_permitJoining != permitJoining) { + m_permitJoining = permitJoining; + emit permitJoiningChanged(m_permitJoining); + } + + qCDebug(dcZigbeeNetwork()) << "Set permit join in the coordinator node to" << duration << "[s]"; + ZigbeeInterfaceNxpReply *reply = m_controller->requestSetPermitJoinCoordinator(duration); + connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [reply](){ + qCDebug(dcZigbeeNetwork()) << "Set permit join in the coordinator finished" << reply->status(); + if (reply->status() != Nxp::StatusSuccess) { + qCWarning(dcZigbeeNetwork()) << "Failed to set permit join status in coordinator"; + } + + }); }); } @@ -256,6 +424,9 @@ void ZigbeeNetworkNxp::startNetwork() { loadNetwork(); + m_permitJoining = false; + emit permitJoiningChanged(m_permitJoining); + if (!m_controller->enable(serialPortName(), serialBaudrate())) { m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); @@ -264,10 +435,7 @@ void ZigbeeNetworkNxp::startNetwork() return; } - m_permitJoining = false; - emit permitJoiningChanged(m_permitJoining); - - // Get current state and load information + // Wait for available signal... } void ZigbeeNetworkNxp::stopNetwork() @@ -287,6 +455,8 @@ void ZigbeeNetworkNxp::reset() void ZigbeeNetworkNxp::factoryResetNetwork() { qCDebug(dcZigbeeNetwork()) << "Factory reset network and forget all information. This cannot be undone."; + clearSettings(); + ZigbeeInterfaceNxpReply *reply = m_controller->requestFactoryResetController(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [reply](){ qCDebug(dcZigbeeNetwork()) << "Factory reset reply finished" << reply->status(); diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h index 53f705f..b46e55b 100644 --- a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h +++ b/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h @@ -2,6 +2,7 @@ #define ZIGBEENETWORKNXP_H #include +#include #include "zigbeenetwork.h" #include "zigbeechannelmask.h" @@ -23,16 +24,29 @@ public: private: ZigbeeBridgeControllerNxp *m_controller = nullptr; bool m_networkRunning = false; + QHash m_pendingReplies; + + QTimer *m_permitJoinRefreshTimer = nullptr; + + // ZDO + void handleZigbeeDeviceProfileIndication(const Zigbee::ApsdeDataIndication &indication); + + // ZCL + void handleZigbeeClusterLibraryIndication(const Zigbee::ApsdeDataIndication &indication); private slots: void onControllerAvailableChanged(bool available); void onControllerStateChanged(ZigbeeBridgeControllerNxp::ControllerState controllerState); void onInterfaceNotificationReceived(Nxp::Notification notification, const QByteArray &payload); + void onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); + void onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); + + void onDeviceAnnounced(quint16 shortAddress, ZigbeeAddress ieeeAddress, quint8 macCapabilities); + protected: void setPermitJoiningInternal(bool permitJoining) override; - signals: public slots: diff --git a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp index d47cfdb..53e6714 100644 --- a/libnymea-zigbee/zdo/zigbeedeviceobject.cpp +++ b/libnymea-zigbee/zdo/zigbeedeviceobject.cpp @@ -30,6 +30,7 @@ #include "loggingcategory.h" #include +#include ZigbeeDeviceObject::ZigbeeDeviceObject(ZigbeeNetwork *network, ZigbeeNode *node, QObject *parent) : QObject(parent), @@ -407,6 +408,7 @@ void ZigbeeDeviceObject::finishZdoReply(ZigbeeDeviceObjectReply *zdoReply) break; } + m_pendingReplies.remove(zdoReply->transactionSequenceNumber()); zdoReply->finished(); } diff --git a/libnymea-zigbee/zigbeebridgecontroller.h b/libnymea-zigbee/zigbeebridgecontroller.h index 3317c49..7db5087 100644 --- a/libnymea-zigbee/zigbeebridgecontroller.h +++ b/libnymea-zigbee/zigbeebridgecontroller.h @@ -30,6 +30,8 @@ #include +#include "zigbee.h" + class ZigbeeBridgeController : public QObject { Q_OBJECT @@ -50,6 +52,10 @@ protected: signals: void availableChanged(bool available); void firmwareVersionChanged(const QString &firmwareVersion); + + void apsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm); + void apsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication); + }; #endif // ZIGBEEBRIDGECONTROLLER_H