/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 "zigbeebridgecontrollernxp.h" #include "loggingcategory.h" #include "zigbeeutils.h" #include ZigbeeBridgeControllerNxp::ZigbeeBridgeControllerNxp(QObject *parent) : ZigbeeBridgeController(parent) { m_interface = new ZigbeeInterfaceNxp(this); connect(m_interface, &ZigbeeInterfaceNxp::availableChanged, this, &ZigbeeBridgeControllerNxp::onInterfaceAvailableChanged); connect(m_interface, &ZigbeeInterfaceNxp::packageReceived, this, &ZigbeeBridgeControllerNxp::onInterfacePackageReceived); } ZigbeeBridgeControllerNxp::~ZigbeeBridgeControllerNxp() { qCDebug(dcZigbeeController()) << "Destroy controller"; } ZigbeeBridgeControllerNxp::ControllerState ZigbeeBridgeControllerNxp::controllerState() const { 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; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandGetVersion); stream << static_cast(m_sequenceNumber); stream << static_cast(0); // Frame length return createReply(Nxp::CommandGetVersion, m_sequenceNumber, "Request controller version", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestControllerState() { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandGetControllerState); stream << static_cast(m_sequenceNumber); stream << static_cast(0); // Frame length return createReply(Nxp::CommandGetControllerState, m_sequenceNumber, "Request controller state", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSoftResetController() { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandSoftReset); stream << static_cast(m_sequenceNumber); stream << static_cast(0); // Frame length return createReply(Nxp::CommandSoftReset, m_sequenceNumber, "Request soft reset controller", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestFactoryResetController() { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandFactoryReset); stream << static_cast(m_sequenceNumber); stream << static_cast(0); // Frame length return createReply(Nxp::CommandFactoryReset, m_sequenceNumber, "Request factory reset controller", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSetPanId(quint64 panId) { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandSetPanId); stream << static_cast(m_sequenceNumber); stream << static_cast(8); // Frame length stream << panId; return createReply(Nxp::CommandSetPanId, m_sequenceNumber, "Request set PAN ID", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSetChannelMask(quint32 channelMask) { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandSetChannelMask); stream << static_cast(m_sequenceNumber); stream << static_cast(4); // Frame length stream << channelMask; return createReply(Nxp::CommandSetChannelMask, m_sequenceNumber, "Request set channel mask", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSetSecurityKey(Nxp::KeyType keyType, const ZigbeeNetworkKey &key) { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandSetSecurityKey); stream << static_cast(m_sequenceNumber); stream << static_cast(17); // Frame length stream << static_cast(keyType); QByteArray keyData = key.toByteArray(); for (int i = 0; i < 16; i++) { stream << static_cast(keyData.at(i)); } return createReply(Nxp::CommandSetSecurityKey, m_sequenceNumber, "Request set security key", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestStartNetwork() { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandStartNetwork); stream << static_cast(m_sequenceNumber); stream << static_cast(0); // Frame length return createReply(Nxp::CommandStartNetwork, m_sequenceNumber, "Request start network", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestNetworkState() { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandGetNetworkState); stream << static_cast(m_sequenceNumber); stream << static_cast(0); // Frame length return createReply(Nxp::CommandGetNetworkState, m_sequenceNumber, "Request network state", message, this); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::requestSetPermitJoinCoordinator(quint8 duration) { QByteArray message; bumpSequenceNumber(); QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Nxp::CommandSetPermitJoinCoordinator); stream << static_cast(m_sequenceNumber); stream << static_cast(1); // Frame length stream << duration; 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; } bool ZigbeeBridgeControllerNxp::updateAvailable(const QString ¤tVersion) { if (!m_firmwareUpdateHandler) return false; if (m_firmwareUpdateHandler->availableFirmwareVersion() != currentVersion) { return true; } return false; } QString ZigbeeBridgeControllerNxp::updateFirmwareVersion() const { if (!m_firmwareUpdateHandler) return QString(); return m_firmwareUpdateHandler->availableFirmwareVersion(); } void ZigbeeBridgeControllerNxp::startFirmwareUpdate() { if (!m_firmwareUpdateHandler) return; m_updateRunning = true; emit updateRunningChanged(m_updateRunning); qCDebug(dcZigbeeController()) << "Disable UART interface for update..."; m_interface->disable(); m_firmwareUpdateHandler->startUpdate(); } ZigbeeInterfaceNxpReply *ZigbeeBridgeControllerNxp::createReply(Nxp::Command command, quint8 sequenceNumber, const QString &requestName, const QByteArray &requestData, QObject *parent) { // Create the reply ZigbeeInterfaceNxpReply *reply = new ZigbeeInterfaceNxpReply(command, parent); reply->m_requestName = requestName; reply->m_requestData = requestData; reply->m_sequenceNumber = sequenceNumber; // Make sure we clean up on timeout connect(reply, &ZigbeeInterfaceNxpReply::timeout, this, [reply](){ qCWarning(dcZigbeeController()) << "Reply timeout" << reply; }); // Auto delete the object on finished connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply](){ reply->deleteLater(); if (m_currentReply == reply) { m_currentReply = nullptr; QMetaObject::invokeMethod(this, "sendNextRequest", Qt::QueuedConnection); } }); qCDebug(dcZigbeeController()) << "Enqueue request" << reply->command() << "SQN:" << reply->sequenceNumber(); m_replyQueue.enqueue(reply); QMetaObject::invokeMethod(this, "sendNextRequest", Qt::QueuedConnection); return reply; } 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::initializeUpdateProvider() { QFileInfo updateProviderConfigurationFileInfo = QFileInfo(m_settingsDirectory.canonicalPath() + QDir::separator() + "zigbee-update-provider-nxp.conf"); if (!updateProviderConfigurationFileInfo.exists()) { qCDebug(dcZigbeeController()) << "No firmware update provider configured for this controller."; return; } qCDebug(dcZigbeeController()) << "Found update provider configuration" << updateProviderConfigurationFileInfo.absoluteFilePath(); m_firmwareUpdateHandler = new FirmwareUpdateHandlerNxp(updateProviderConfigurationFileInfo, this); if (!m_firmwareUpdateHandler->isValid()) { qCWarning(dcZigbeeController()) << "The firmware update provider is not valid. The firmware update is not available for this NXP zigbee controller."; m_firmwareUpdateHandler->deleteLater(); m_firmwareUpdateHandler = nullptr; return; } connect(m_firmwareUpdateHandler, &FirmwareUpdateHandlerNxp::updateFinished, this, [this](bool success){ if (success) { qCDebug(dcZigbeeController()) << "Update finished successfully. Reenable controller"; enable(m_serialPort, m_baudrate); m_updateRunning = false; emit updateRunningChanged(m_updateRunning); } else { qCWarning(dcZigbeeController()) << "Update finished with errors. Can not continue."; m_updateRunning = false; emit updateRunningChanged(m_updateRunning); // Fixme: check if we should to retry disable(); } }); qCDebug(dcZigbeeController()) << "The firmware update provider is valid. The firmware of this NXP controller can be updated."; m_canUpdate = true; emit canUpdateChanged(m_canUpdate); } void ZigbeeBridgeControllerNxp::onInterfaceAvailableChanged(bool available) { qCDebug(dcZigbeeController()) << "Interface available changed" << available; setAvailable(available); } void ZigbeeBridgeControllerNxp::onInterfacePackageReceived(const QByteArray &package) { QDataStream stream(package); stream.setByteOrder(QDataStream::LittleEndian); quint8 commandInt = 0; quint8 sequenceNumber = 0; stream >> commandInt >> sequenceNumber; // Note: commands >= 0x7D are notifications if (commandInt >= 0x7D) { quint16 payloadLength = 0; stream >> 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(payload); switch (notification) { case Nxp::NotificationDeviceStatusChanged: 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, payload); break; } } else { quint8 statusInt = 0; quint16 payloadLength = 0; stream >> statusInt >> payloadLength; if (package.length() != payloadLength + 5) { qCWarning(dcZigbeeController()) << "Invalid package length received" << ZigbeeUtils::convertByteArrayToHexString(package) << payloadLength; return; } QByteArray data = package.mid(5, payloadLength); Nxp::Command command = static_cast(commandInt); Nxp::Status status = static_cast(statusInt); qCDebug(dcZigbeeController()) << "Interface response received" << command << "SQN:" << sequenceNumber << status << ZigbeeUtils::convertByteArrayToHexString(data); if (m_currentReply->sequenceNumber() == sequenceNumber) { if (m_currentReply->command() == command) { m_currentReply->m_status = status; m_currentReply->m_responseData = data; } else { qCWarning(dcZigbeeController()) << "Received interface response for a pending sequence number but the command does not match the request." << command << m_currentReply->command(); } m_currentReply->setFinished(); } else { qCWarning(dcZigbeeController()) << "Received a response for a non pending reply. There is no pending reply for command" << command << "SQN:" << sequenceNumber; } } } void ZigbeeBridgeControllerNxp::sendNextRequest() { // Check if there is a reply request to send if (m_replyQueue.isEmpty()) return; // Check if there is currently a running reply if (m_currentReply) return; // Send next message m_currentReply = m_replyQueue.dequeue(); qCDebug(dcZigbeeController()) << "Send request" << m_currentReply; m_interface->sendPackage(m_currentReply->requestData()); m_currentReply->m_timer->start(); } bool ZigbeeBridgeControllerNxp::enable(const QString &serialPort, qint32 baudrate) { m_serialPort = serialPort; m_baudrate = baudrate; return m_interface->enable(serialPort, baudrate); } void ZigbeeBridgeControllerNxp::disable() { m_interface->disable(); }