/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 "zigbeenetworknxp.h" #include "loggingcategory.h" #include "zigbeeutils.h" #include ZigbeeNetworkNxp::ZigbeeNetworkNxp(QObject *parent) : ZigbeeNetwork(parent) { m_controller = new ZigbeeBridgeControllerNxp(this); 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::canUpdateChanged, this, [](bool canUpdate){ if (canUpdate) { qCDebug(dcZigbeeNetwork()) << "The controller of this network can be updated."; } else { qCDebug(dcZigbeeNetwork()) << "The controller of this network can not be updated."; } }); connect(m_controller, &ZigbeeBridgeControllerNxp::updateRunningChanged, this, [this](bool updateRunning){ if (updateRunning) { qCDebug(dcZigbeeNetwork()) << "The controller is performing an update."; setState(StateUpdating); } }); 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 { if (!m_controller) return nullptr; return qobject_cast(m_controller); } ZigbeeNetworkReply *ZigbeeNetworkNxp::sendRequest(const ZigbeeNetworkRequest &request) { 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) { // 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); } bool ZigbeeNetworkNxp::processVersionReply(ZigbeeInterfaceNxpReply *reply) { qCDebug(dcZigbeeNetwork()) << "Version reply finished" << reply->status(); if (reply->timendOut()) { m_reconnectCounter++; if (m_reconnectCounter >= 3) { if (m_controller->canUpdate()) { qCDebug(dcZigbeeNetwork()) << "Unable to get controller version."; qCDebug(dcZigbeeNetwork()) << "Firmware update provider available. Try to flash the firmware, maybe that fixes the problem."; if (!m_controller->updateRunning()) { clearSettings(); qCDebug(dcZigbeeNetwork()) << "Starting firmware update..."; m_controller->startFirmwareUpdate(); } else { qCWarning(dcZigbeeNetwork()) << "There is already an update running..."; } return false; } else { qCDebug(dcZigbeeNetwork()) << "Unable to get controller version. There is no firmware upgrade available. Giving up."; return false; } } qCWarning(dcZigbeeNetwork()) << "Failed to read firmware version. Retry" << m_reconnectCounter << "/ 3"; ZigbeeInterfaceNxpReply *reply = m_controller->requestVersion(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ processVersionReply(reply); }); return false; } QByteArray payload = reply->responseData(); QDataStream stream(&payload, QIODevice::ReadOnly); stream.setByteOrder(QDataStream::LittleEndian); quint8 major = 0; quint8 minor = 0; quint8 patch = 0; quint16 sdkVersion = 0; stream >> major >> minor >> patch >> sdkVersion; QString version = QString("%1.%2.%3").arg(major).arg(minor).arg(patch); QString versionString = QString("%1 - %2").arg(version).arg(sdkVersion); qCDebug(dcZigbeeNetwork()) << "Controller version" << versionString; m_controller->setFirmwareVersion(versionString); if (m_controller->canUpdate()) { if (m_controller->updateAvailable(version)) { qCDebug(dcZigbeeNetwork()) << "There is an update available for the this zigbee controller:" << version << "-->" << m_controller->updateFirmwareVersion(); qCDebug(dcZigbeeNetwork()) << "Starting firmware update..."; m_controller->startFirmwareUpdate(); return false; } else { qCDebug(dcZigbeeNetwork()) << "The current firmware is up to date."; } } return true; } 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) { m_reconnectCounter = 0; ZigbeeInterfaceNxpReply *reply = m_controller->requestVersion(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ // Retry or firmware upgrade if available if (!processVersionReply(reply)) return; m_controller->refreshControllerState(); }); } else { setState(StateOffline); } } void ZigbeeNetworkNxp::onControllerStateChanged(ZigbeeBridgeControllerNxp::ControllerState controllerState) { qCDebug(dcZigbeeNetwork()) << "Controller state changed" << controllerState; switch (controllerState) { case ZigbeeBridgeControllerNxp::ControllerStateRunning: { setState(StateStarting); m_reconnectCounter = 0; qCDebug(dcZigbeeNetwork()) << "Request controller version"; ZigbeeInterfaceNxpReply *reply = m_controller->requestVersion(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ // Retry or firmware upgrade if available if (!processVersionReply(reply)) return; qCDebug(dcZigbeeNetwork()) << "Get the current network state"; ZigbeeInterfaceNxpReply *reply = m_controller->requestNetworkState(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ qCDebug(dcZigbeeNetwork()) << "Get network state response" << reply->status(); //FIXME: error handling QByteArray data = reply->responseData(); QDataStream payloadStream(&data, QIODevice::ReadOnly); payloadStream.setByteOrder(QDataStream::LittleEndian); quint16 networkAddress; quint64 ieeeAddress; quint8 channel; quint16 panId; quint64 extendedPanId; payloadStream >> networkAddress >> ieeeAddress >> channel >> panId >> extendedPanId; qCDebug(dcZigbeeNetwork()) << "Network running" << ZigbeeUtils::convertUint16ToHexString(networkAddress) << ZigbeeAddress(ieeeAddress).toString() << "Channel:" << channel << "PAN ID:" << panId << "Extended PAN ID:" << ZigbeeUtils::convertUint64ToHexString(extendedPanId); setPanId(panId); setChannel(channel); // Initialize the coordinator node if not already done. if (m_coordinatorNode) { qCDebug(dcZigbeeNetwork()) << "We already have the coordinator node. Network starting done."; m_database->saveNode(m_coordinatorNode); setState(StateRunning); setPermitJoiningInternal(false); return; } ZigbeeNode *coordinatorNode = createNode(networkAddress, ZigbeeAddress(ieeeAddress), this); m_coordinatorNode = 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; } case ZigbeeBridgeControllerNxp::ControllerStateStarting: setState(StateStarting); break; case ZigbeeBridgeControllerNxp::ControllerStateBooting: setState(StateStarting); break; case ZigbeeBridgeControllerNxp::ControllerStateRunningUninitialized: { setState(StateStarting); qCDebug(dcZigbeeNetwork()) << "Request controller version"; ZigbeeInterfaceNxpReply *reply = m_controller->requestVersion(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ // Retry or firmware upgrade if available if (!processVersionReply(reply)) return; QByteArray payload = reply->responseData(); QDataStream stream(&payload, QIODevice::ReadOnly); stream.setByteOrder(QDataStream::LittleEndian); quint8 major = 0; quint8 minor = 0; quint8 patch = 0; quint16 sdkVersion = 0; stream >> major >> minor >> patch >> sdkVersion; QString versionString = QString ("%1.%2.%3 - %4").arg(major).arg(minor).arg(patch).arg(sdkVersion); qCDebug(dcZigbeeNetwork()) << "Controller version" << versionString; m_controller->setFirmwareVersion(versionString); if (extendedPanId() == 0) { quint64 panId = ZigbeeUtils::generateRandomPanId(); setExtendedPanId(panId); qCDebug(dcZigbeeNetwork()) << "There is no pan id set yet. Generated new PAN ID" << panId << ZigbeeUtils::convertUint64ToHexString(panId); ZigbeeSecurityConfiguration securityConfiguration; securityConfiguration.setNetworkKey(ZigbeeNetworkKey::generateKey()); setSecurityConfiguration(securityConfiguration); qCDebug(dcZigbeeNetwork()) << "Generated new network key" << securityConfiguration.networkKey().toString(); } qCDebug(dcZigbeeNetwork()) << "Set PAN ID" << ZigbeeUtils::convertUint64ToHexString(extendedPanId()) << extendedPanId(); ZigbeeInterfaceNxpReply *reply = m_controller->requestSetPanId(extendedPanId()); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ qCDebug(dcZigbeeNetwork()) << "Set PAN ID reply response" << reply->status(); //FIXME: error handling qCDebug(dcZigbeeNetwork()) << "Set channel mask" << channelMask(); ZigbeeInterfaceNxpReply *reply = m_controller->requestSetChannelMask(channelMask().toUInt32()); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ qCDebug(dcZigbeeNetwork()) << "Set channel mask reply response" << reply->status(); //FIXME: error handling qCDebug(dcZigbeeNetwork()) << "Set global link key" << securityConfiguration().globalTrustCenterLinkKey().toString(); ZigbeeInterfaceNxpReply *reply = m_controller->requestSetSecurityKey(Nxp::KeyTypeGlobalLinkKey, securityConfiguration().globalTrustCenterLinkKey()); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ qCDebug(dcZigbeeNetwork()) << "Set global link key response" << reply->status(); //FIXME: error handling qCDebug(dcZigbeeNetwork()) << "Set network link key" << securityConfiguration().networkKey().toString(); ZigbeeInterfaceNxpReply *reply = m_controller->requestSetSecurityKey(Nxp::KeyTypeUniqueLinkKey, securityConfiguration().networkKey()); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ qCDebug(dcZigbeeNetwork()) << "Set network link key response" << reply->status(); //FIXME: error handling qCDebug(dcZigbeeNetwork()) << "Start the network"; ZigbeeInterfaceNxpReply *reply = m_controller->requestStartNetwork(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [reply](){ qCDebug(dcZigbeeNetwork()) << "Start network response" << reply->status(); //FIXME: error handling qCDebug(dcZigbeeNetwork()) << "Waiting for the network to start..."; }); }); }); }); }); }); break; } case ZigbeeBridgeControllerNxp::ControllerStateNotRunning: setState(StateOffline); break; } } void ZigbeeNetworkNxp::onInterfaceNotificationReceived(Nxp::Notification notification, const QByteArray &payload) { switch (notification) { case Nxp::NotificationNetworkStarted: { QByteArray data = payload; QDataStream payloadStream(&data, QIODevice::ReadOnly); payloadStream.setByteOrder(QDataStream::LittleEndian); quint16 networkAddress; quint64 ieeeAddress; quint8 channel; payloadStream >> networkAddress >> ieeeAddress >> channel; qCDebug(dcZigbeeNetwork()) << "Network started" << ZigbeeUtils::convertUint16ToHexString(networkAddress) << ZigbeeAddress(ieeeAddress).toString() << "Channel:" << channel; break; } 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) { qCDebug(dcZigbeeNetwork()) << "Set permit join internal" << permitJoining; quint8 duration = 0; if (permitJoining) { duration = 255; } // Note: since compliance version >= 21 the value 255 is not any more Qt::endless. // we need to refresh the command on timeout 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"; } }); }); } void ZigbeeNetworkNxp::startNetwork() { loadNetwork(); m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); if (!m_controller->enable(serialPortName(), serialBaudrate())) { m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); setState(StateOffline); setError(ErrorHardwareUnavailable); return; } // Wait for available signal... } void ZigbeeNetworkNxp::stopNetwork() { } void ZigbeeNetworkNxp::reset() { qCDebug(dcZigbeeNetwork()) << "Soft reset the controller. The stack will perform a restart."; ZigbeeInterfaceNxpReply *reply = m_controller->requestSoftResetController(); connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [reply](){ qCDebug(dcZigbeeNetwork()) << "Soft reset reply finished" << reply->status(); }); } 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(); }); }