/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 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 "zigbeenodeendpointnxp.h" #include "zigbeenetworknxp.h" #include "loggingcategory.h" #include "zigbeenodenxp.h" #include "zigbeeutils.h" #include ZigbeeNetworkNxp::ZigbeeNetworkNxp(QObject *parent) : ZigbeeNetwork(parent) { m_controller = new ZigbeeBridgeControllerNxp(this); connect(m_controller, &ZigbeeBridgeControllerNxp::messageReceived, this, &ZigbeeNetworkNxp::onMessageReceived); connect(m_controller, &ZigbeeBridgeControllerNxp::availableChanged, this, &ZigbeeNetworkNxp::onControllerAvailableChanged); } ZigbeeBridgeController *ZigbeeNetworkNxp::bridgeController() const { if (m_controller) return qobject_cast(m_controller); return nullptr; } void ZigbeeNetworkNxp::setStartingState(ZigbeeNetworkNxp::StartingState state) { if (m_startingState == state) return; m_startingState = state; switch (m_startingState) { case StartingStateNone: break; case StartingStateErase: { m_networkRunning = false; qCDebug(dcZigbeeNetwork()) << "Starting state changed: Erase persistant data"; ZigbeeInterfaceReply *reply = m_controller->commandErasePersistantData(); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); // TODO: check error handling //return; } m_factoryResetting = false; qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; setStartingState(StartingStateGetVersion); }); break; } case StartingStateReset: { m_networkRunning = false; qCDebug(dcZigbeeNetwork()) << "Starting state changed: Reset controller"; ZigbeeInterfaceReply *reply = m_controller->commandResetController(); connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; // Note: the controller is now sending a log of cluster and attribte/command information // After the reset the state machine will continue }); break; } case StartingStateGetVersion: { qCDebug(dcZigbeeNetwork()) << "Starting state changed: Get controller version"; readControllerVersion(); break; } case StartingStateSetPanId: { qCDebug(dcZigbeeNetwork()) << "Starting state changed: Set PAN ID"; if (extendedPanId() == 0) { setExtendedPanId(ZigbeeUtils::generateRandomPanId()); } ZigbeeInterfaceReply *reply = m_controller->commandSetExtendedPanId(extendedPanId()); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; setStartingState(StartingStateSetChannel); }); break; } case StartingStateSetChannel: { // Create channel mask // Note: normal number passed, that specific channel will be used || Bitfield: all channels would be 0x07FFF800 // 0x07fff800 select from all channels 11 - 26 // 0x02108800 primary zigbee light link channels 11, 15, 20, 25 qCDebug(dcZigbeeNetwork()) << "Starting state changed: Set channel mask"; ZigbeeInterfaceReply *reply = nullptr; if (channel() == 0) { qCDebug(dcZigbeeNetwork()) << "Autoselect quitest channel for the zigbee network. FIXME: currently hardcoded to 13 due to firmware error."; quint32 channelMask = 0; channelMask |= 1 << (14); reply = m_controller->commandSetChannelMask(channelMask); } else { quint32 channelMask = 0; channelMask |= 1 << (channel()); qCDebug(dcZigbeeNetwork()) << "Using channel" << channel() << "for the zigbee network."; reply = m_controller->commandSetChannelMask(channelMask); } connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeNetwork()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; setStartingState(StartingStateSetSecurity); }); break; } case StartingStateSetSecurity: { qCDebug(dcZigbeeNetwork()) << "Starting state changed: Set security configuration"; qCDebug(dcZigbeeNetwork()) << "Set global trust center link key" << securityConfiguration().globalTrustCenterLinkKey(); ZigbeeInterfaceReply *reply = m_controller->commandSetSecurityStateAndKey(4, 0, 1, securityConfiguration().globalTrustCenterLinkKey().toByteArray()); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeNetwork()) << "Set network link key" << securityConfiguration().networkKey(); ZigbeeInterfaceReply *reply2 = m_controller->commandSetSecurityStateAndKey(3, 0, 1, securityConfiguration().networkKey().toByteArray()); connect(reply2, &ZigbeeInterfaceReply::finished, this, [this, reply2](){ reply2->deleteLater(); if (reply2->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply2->request().description() << reply2->status() << reply2->statusErrorMessage(); } qCDebug(dcZigbeeController()) << reply2->request().description() << "finished successfully"; setStartingState(StartingStateSetNodeType); }); }); break; } case StartingStateSetNodeType: { qCDebug(dcZigbeeNetwork()) << "Starting state changed: Set node type"; ZigbeeInterfaceReply *reply = m_controller->commandSetNodeType(ZigbeeNode::NodeTypeCoordinator); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; setStartingState(StartingStateStartNetwork); }); break; } case StartingStateStartNetwork: { qCDebug(dcZigbeeNetwork()) << "Starting state changed: Starting network"; ZigbeeInterfaceReply *reply = m_controller->commandStartNetwork(); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeNetwork()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeController()) << reply->additionalMessage(); processNetworkFormed(reply->additionalMessage()); }); break; } } } void ZigbeeNetworkNxp::readControllerVersion() { ZigbeeInterfaceReply *reply = m_controller->commandGetVersion(); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } if (reply->additionalMessage().data().count() != 4) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << ":" << "Invalid payload size"; return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; // Parse version quint16 majorVersion = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(0, 2)); quint16 minorVersion = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(2, 2)); qCDebug(dcZigbeeNetwork()) << "Controller version:" << QString("%1.%2").arg(majorVersion).arg(minorVersion); m_controller->setFirmwareVersionString(QString("%1.%2").arg(majorVersion).arg(minorVersion)); if (m_startingState == StartingStateGetVersion) setStartingState(StartingStateSetPanId); }); } void ZigbeeNetworkNxp::readPermitJoinStatus() { ZigbeeInterfaceReply *reply = m_controller->commandGetPermitJoinStatus(); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeController()) << reply->additionalMessage(); setPermitJoining(static_cast(reply->additionalMessage().data().at(0))); }); } ZigbeeNetworkReply *ZigbeeNetworkNxp::sendRequest(const ZigbeeNetworkRequest &request) { Q_UNUSED(request) qCCritical(dcZigbeeNetwork()) << "Cannot send request. Not implemented for this backend"; return nullptr; } ZigbeeNode *ZigbeeNetworkNxp::createNode(QObject *parent) { return new ZigbeeNodeNxp(m_controller, parent); } void ZigbeeNetworkNxp::setPermitJoiningInternal(bool permitJoining) { // ZigbeeInterfaceReply *replyGroup = m_controller->commandAddGroup(0x00, 0x0000, 0x01, 0x01, 0x0001); // connect(replyGroup, &ZigbeeInterfaceReply::finished, replyGroup, &ZigbeeInterfaceReply::deleteLater); // Note: 0xfffc = all routers qCDebug(dcZigbeeNetwork()) << "Send request to" << (permitJoining ? "enable" : "disable") << "permit joining network."; ZigbeeInterfaceReply *reply = m_controller->commandPermitJoin(0xfffc, (permitJoining ? 255 : 0)); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply, permitJoining](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeController()) << reply->additionalMessage(); if (m_permitJoining == permitJoining) return; qCDebug(dcZigbeeNetwork()) << "Permit joining changed to" << permitJoining; m_permitJoining = permitJoining; emit permitJoiningChanged(m_permitJoining); }); } void ZigbeeNetworkNxp::onCommandInitiateTouchLinkFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; } void ZigbeeNetworkNxp::onCommandTouchLinkFactoryResetFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; } void ZigbeeNetworkNxp::onCommandRequestLinkQualityFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeController()) << reply->additionalMessage(); // FIXME: parsing quint8 sequenceNumber = static_cast(reply->additionalMessage().data().at(0)); quint8 statusCode = static_cast(reply->additionalMessage().data().at(1)); quint8 neighborTableEntries = static_cast(reply->additionalMessage().data().at(2)); quint8 neighborTableListCount = static_cast(reply->additionalMessage().data().at(3)); quint8 startIndex = static_cast(reply->additionalMessage().data().at(4)); qCDebug(dcZigbeeNetwork()) << "LQI response:"; qCDebug(dcZigbeeNetwork()) << " SQN:" << ZigbeeUtils::convertByteToHexString(sequenceNumber); qCDebug(dcZigbeeNetwork()) << " Status:" << ZigbeeUtils::convertByteToHexString(statusCode); qCDebug(dcZigbeeNetwork()) << " Neighbor table entries:" << neighborTableEntries; qCDebug(dcZigbeeNetwork()) << " Neighbor table list count:" << neighborTableListCount; qCDebug(dcZigbeeNetwork()) << " Start index:" << startIndex; int offset = 5; // Note: according to docs, if the table has no neigbors the list will be empty if (neighborTableEntries == 0) { qCDebug(dcZigbeeNetwork()) << " There are no neigbors"; return; } for (int i = startIndex; i < neighborTableListCount; i++) { quint16 shortAddress = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(offset, 2)); quint64 panId = ZigbeeUtils::convertByteArrayToUint64(reply->additionalMessage().data().mid(offset + 2, 8)); quint64 ieeeAddress = ZigbeeUtils::convertByteArrayToUint64(reply->additionalMessage().data().mid(offset + 10, 8)); quint8 depth = static_cast(reply->additionalMessage().data().at(offset + 18)); quint8 linkQuality = static_cast(reply->additionalMessage().data().at(offset + 19)); quint8 bitMap = static_cast(reply->additionalMessage().data().at(offset + 20)); offset += 21; qCDebug(dcZigbeeNetwork()) << " Neighbor:" << i; qCDebug(dcZigbeeNetwork()) << " Address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress); qCDebug(dcZigbeeNetwork()) << " PAN id:" << panId; qCDebug(dcZigbeeNetwork()) << " Extended address:" << ZigbeeAddress(ieeeAddress); qCDebug(dcZigbeeNetwork()) << " Depth:" << depth; qCDebug(dcZigbeeNetwork()) << " Link quality:" << linkQuality; qCDebug(dcZigbeeNetwork()) << " BitMap:" << ZigbeeUtils::convertByteToHexString(bitMap); } } void ZigbeeNetworkNxp::processLoggingMessage(const ZigbeeInterfaceMessage &message) { quint8 logLevel = static_cast(message.data().at(0)); QString logMessage = QString::fromUtf8(message.data().right(message.data().count() - 1)); QString logLevelString; switch (logLevel) { case 0: logLevelString = "Emergency:"; break; case 1: logLevelString = "Alert:"; break; case 2: logLevelString = "Critical:"; break; case 3: logLevelString = "Error:"; break; case 4: logLevelString = "Warning:"; break; case 5: logLevelString = "Notice:"; break; case 6: logLevelString = "Information:"; break; case 7: logLevelString = "Debug:"; break; default: logLevelString = "Unknown:"; break; } if (logLevel < 5) { qCWarning(dcZigbeeController()).noquote() << "ControllerLog:" << logLevelString << logMessage; } else { qCDebug(dcZigbeeController()).noquote() << "ControllerLog:" << logLevelString << logMessage; } } void ZigbeeNetworkNxp::processFactoryNewRestart(const ZigbeeInterfaceMessage &message) { quint8 controllerStatus = static_cast(message.data().at(0)); QString controllerStatusString; switch (controllerStatus) { case 0: controllerStatusString = "startup"; break; case 1: controllerStatusString = "wait start"; break; case 2: controllerStatusString = "NRF start"; break; case 3: controllerStatusString = "discovery"; break; case 4: controllerStatusString = "network init"; break; case 5: controllerStatusString = "rescan"; break; case 6: controllerStatusString = "running"; break; default: qCWarning(dcZigbeeNetwork()) << "Unhandled controller status" << controllerStatus; break; } qCDebug(dcZigbeeNetwork()) << "Restart finished. Current controller state:" << controllerStatusString; if (m_startingState == StartingStateReset) setStartingState(StartingStateGetVersion); } void ZigbeeNetworkNxp::processNodeClusterList(const ZigbeeInterfaceMessage &message) { quint8 sourceEndpoint = static_cast(message.data().at(0)); quint16 profileId = static_cast(message.data().at(1)); profileId <<= 8; profileId |= static_cast(message.data().at(2)); // FIXME: return; qCDebug(dcZigbeeController()) << "Node cluster list received:"; qCDebug(dcZigbeeController()) << " Souce endpoint:" << sourceEndpoint; qCDebug(dcZigbeeController()) << " Profile:" << ZigbeeUtils::profileIdToString(static_cast(profileId)); QByteArray clusterListData = message.data().right(message.data().count() - 3); for (int i = 0; i < clusterListData.count(); i += 2) { quint16 clusterId = static_cast(clusterListData.at(i)); clusterId <<= 8; clusterId |= clusterListData .at(i + 1); qCDebug(dcZigbeeController()) << " Cluster ID:" << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); } } void ZigbeeNetworkNxp::processNodeAttributeList(const ZigbeeInterfaceMessage &message) { quint8 sourceEndpoint = static_cast(message.data().at(0)); quint16 profileId = ZigbeeUtils::convertByteArrayToUint16(message.data().mid(1, 2)); quint16 clusterId = ZigbeeUtils::convertByteArrayToUint16(message.data().mid(3, 2)); // FIXME: return; qCDebug(dcZigbeeController()) << "Node attribute list received:"; qCDebug(dcZigbeeController()) << " Souce endpoint:" << sourceEndpoint; qCDebug(dcZigbeeController()) << " Profile:" << ZigbeeUtils::profileIdToString(static_cast(profileId)); qCDebug(dcZigbeeController()) << " Cluster ID:" << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); QByteArray attributeListData = message.data().right(message.data().count() - 5); if (attributeListData.count() % 2 != 0) { qCWarning(dcZigbeeController()) << "Attribute list is incomplete. Truncate last byte"; attributeListData = attributeListData.left(attributeListData.count() - 1); } qCWarning(dcZigbeeController()) << "Attributes" << ZigbeeUtils::convertByteArrayToHexString(attributeListData); for (int i = 0; i < attributeListData.count(); i += 2) { quint16 attribute = ZigbeeUtils::convertByteArrayToUint16(attributeListData.mid(i, 2)); qCDebug(dcZigbeeController()) << " Attribute:" << ZigbeeUtils::convertUint16ToHexString(attribute); } } void ZigbeeNetworkNxp::processNodeCommandIdList(const ZigbeeInterfaceMessage &message) { quint8 sourceEndpoint = static_cast(message.data().at(0)); quint16 profileId = ZigbeeUtils::convertByteArrayToUint16(message.data().mid(1, 2)); quint16 clusterId = ZigbeeUtils::convertByteArrayToUint16(message.data().mid(3, 2)); // FIXME: return; qCDebug(dcZigbeeController()) << "Node command list received:"; qCDebug(dcZigbeeController()) << " Souce endpoint:" << sourceEndpoint; qCDebug(dcZigbeeController()) << " Profile:" << ZigbeeUtils::profileIdToString(static_cast(profileId)); qCDebug(dcZigbeeController()) << " Cluster ID:" << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); QByteArray commandListData = message.data().right(message.data().count() - 5); for (int i = 0; i < commandListData.count(); i++) { quint8 attribute = static_cast(commandListData.at(i)); qCDebug(dcZigbeeController()) << " Command:" << ZigbeeUtils::convertByteToHexString(attribute); } } void ZigbeeNetworkNxp::processDeviceAnnounce(const ZigbeeInterfaceMessage &message) { QByteArray data = message.data(); quint16 shortAddress = 0; quint64 ieeeAddress = 0; quint8 macCapabilitiesFlag = 0; QDataStream stream(&data, QIODevice::ReadOnly); stream >> shortAddress >> ieeeAddress >> macCapabilitiesFlag; ZigbeeAddress nodeIeeeAddress(ieeeAddress); qCDebug(dcZigbeeNetwork()) << "Device announced:"; qCDebug(dcZigbeeNetwork()) << " Address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress); qCDebug(dcZigbeeNetwork()) << " Extended address:" << nodeIeeeAddress; qCDebug(dcZigbeeNetwork()) << " Mac capabilities:" << ZigbeeUtils::convertByteToHexString(macCapabilitiesFlag); // Check if we already have a node with this if (hasNode(nodeIeeeAddress)) { qCDebug(dcZigbeeNetwork()) << "Node already registered in the network."; ZigbeeNode *node = getZigbeeNode(nodeIeeeAddress); if (node->shortAddress() != shortAddress) { qCWarning(dcZigbeeNetwork()) << "The node changed the NWK address from" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress()) << "to" << ZigbeeUtils::convertUint16ToHexString(shortAddress); // FIMXE: check if we should reinitialize the node } return; } ZigbeeNodeNxp *node = new ZigbeeNodeNxp(m_controller, this); node->setShortAddress(shortAddress); node->setExtendedAddress(ZigbeeAddress(ieeeAddress)); node->setMacCapabilitiesFlag(macCapabilitiesFlag); node->startInitialization(); addUnitializedNode(node); // qCDebug(dcZigbeeNetwork()) << "Start authenticating" << node; // ZigbeeInterfaceReply *reply = m_controller->commandAuthenticateDevice(node->extendedAddress(), securityConfiguration().networkKey().toByteArray()); // connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply, node](){ // reply->deleteLater(); // if (reply->status() != ZigbeeInterfaceReply::Success) { // qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); // return; // } // qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; // qCDebug(dcZigbeeController()) << reply->additionalMessage(); // quint64 gatewayIeeeAddress = ZigbeeUtils::convertByteArrayToUint64(reply->additionalMessage().data().mid(0, 8)); // QString encryptedKey = reply->additionalMessage().data().mid(8, 16).toHex(); // QByteArray mic = reply->additionalMessage().data().mid(24, 4); // quint64 initiatorIeeeAddress = ZigbeeUtils::convertByteArrayToUint64(reply->additionalMessage().data().mid(28, 8)); // quint8 activeKeySequenceNumber = static_cast(reply->additionalMessage().data().at(36)); // quint8 channel = static_cast(reply->additionalMessage().data().at(37)); // quint16 shortPan = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(38, 2)); // quint64 extendedPanId = ZigbeeUtils::convertByteArrayToUint64(reply->additionalMessage().data().mid(40, 8)); // qCDebug(dcZigbeeNetwork()) << "Authentication response for" << node; // qCDebug(dcZigbeeNetwork()) << " Gateways address:" << ZigbeeAddress(gatewayIeeeAddress); // qCDebug(dcZigbeeNetwork()) << " Key:" << ZigbeeNetworkKey(encryptedKey).toString(); // qCDebug(dcZigbeeNetwork()) << " MIC:" << mic.toHex(); // qCDebug(dcZigbeeNetwork()) << " Initiator address:" << ZigbeeAddress(initiatorIeeeAddress); // qCDebug(dcZigbeeNetwork()) << " Active key sequence number:" << activeKeySequenceNumber; // qCDebug(dcZigbeeNetwork()) << " Channel:" << channel; // qCDebug(dcZigbeeNetwork()) << " Short PAN ID:" << ZigbeeUtils::convertUint16ToHexString(shortPan); // qCDebug(dcZigbeeNetwork()) << " Extended PAN ID:" << extendedPanId << ZigbeeUtils::convertUint64ToHexString(extendedPanId); // node->startInitialization(); // addUnitializedNode(node); // }); } void ZigbeeNetworkNxp::processAttributeReport(const ZigbeeInterfaceMessage &message) { QByteArray data = message.data(); quint8 sequenceNumber = 0; quint16 sourceAddress = 0; quint8 endpointId = 0; quint16 clusterId = 0; quint16 attributeId = 0; quint8 attributeStatus = 0; quint8 attributDataType = 0; quint16 dataSize = 0; QDataStream stream(&data, QIODevice::ReadOnly); stream >> sequenceNumber >> sourceAddress >> endpointId >> clusterId >> attributeId >> attributeStatus >> attributDataType >> dataSize; Zigbee::DataType dataType = static_cast(attributDataType); QByteArray attributeData = data.right(dataSize); if (attributeData.length() != dataSize) { qCWarning(dcZigbeeNetwork()) << "HACK" << attributeData.length() << "!=" << dataSize; // Note: the NXP firmware for JN5169 has a bug here and does not send the attributeStatus. // Repars data without attribute status sequenceNumber = 0; sourceAddress = 0; endpointId = 0; clusterId = 0; attributeId = 0; attributeStatus = 0; attributDataType = 0; dataSize = 0; QDataStream alternativeStream(&data, QIODevice::ReadOnly); alternativeStream >> sequenceNumber >> sourceAddress >> endpointId >> clusterId >> attributeId >> attributDataType >> dataSize; dataType = static_cast(attributDataType); attributeData = data.right(dataSize); } qCDebug(dcZigbeeNetwork()) << "Attribute report:"; qCDebug(dcZigbeeNetwork()) << " SQN:" << ZigbeeUtils::convertByteToHexString(sequenceNumber); qCDebug(dcZigbeeNetwork()) << " Source address:" << ZigbeeUtils::convertUint16ToHexString(sourceAddress); qCDebug(dcZigbeeNetwork()) << " End point:" << ZigbeeUtils::convertByteToHexString(endpointId); qCDebug(dcZigbeeNetwork()) << " Cluster:" << ZigbeeUtils::clusterIdToString(static_cast(clusterId)); qCDebug(dcZigbeeNetwork()) << " Attribut id:" << ZigbeeUtils::convertUint16ToHexString(attributeId); qCDebug(dcZigbeeNetwork()) << " Attribut status:" << static_cast(attributeStatus); qCDebug(dcZigbeeNetwork()) << " Attribut data type:" << dataType; qCDebug(dcZigbeeNetwork()) << " Attribut size:" << dataSize; qCDebug(dcZigbeeNetwork()) << " Data:" << ZigbeeUtils::convertByteArrayToHexString(attributeData); switch (dataType) { case Zigbee::CharString: qCDebug(dcZigbeeNetwork()) << " Data(converted)" << QString::fromUtf8(attributeData); break; case Zigbee::Bool: qCDebug(dcZigbeeNetwork()) << " Data(converted)" << static_cast(attributeData.at(0)); break; default: break; } ZigbeeNodeNxp *node = qobject_cast(getZigbeeNode(sourceAddress)); if (!node) { qCWarning(dcZigbeeNode()) << "Received an attribute report from an unknown node. Ignoring data."; return; } ZigbeeClusterAttributeReport attributeReport; attributeReport.sourceAddress = sourceAddress; attributeReport.endpointId = endpointId; attributeReport.clusterId = static_cast(clusterId); attributeReport.attributeId = attributeId; attributeReport.attributeStatus = static_cast(attributeStatus); attributeReport.dataType = dataType; attributeReport.data = attributeData; node->setClusterAttributeReport(attributeReport); } void ZigbeeNetworkNxp::processReadAttributeResponse(const ZigbeeInterfaceMessage &message) { ZigbeeClusterAttributeReport attributeReport = ZigbeeUtils::parseAttributeReport(message.data()); ZigbeeNodeNxp *node = qobject_cast(getZigbeeNode(attributeReport.sourceAddress)); if (!node) { qCWarning(dcZigbeeNode()) << "Received an attribute report from an unknown node. Ignoring data."; return; } node->setClusterAttributeReport(attributeReport); } void ZigbeeNetworkNxp::processLeaveIndication(const ZigbeeInterfaceMessage &message) { QByteArray data = message.data(); quint64 extendedAddress = 0; bool rejoining = 0; QDataStream stream(&data, QIODevice::ReadOnly); stream >> extendedAddress; stream >> rejoining; ZigbeeAddress address(extendedAddress); qCDebug(dcZigbeeNetwork()) << "Node leaving indication:" << address.toString() << "rejoining:" << rejoining; ZigbeeNode *node = getZigbeeNode(address); if (node) removeNode(node); } void ZigbeeNetworkNxp::processRestartProvisioned(const ZigbeeInterfaceMessage &message) { if (message.data().isEmpty()) return; quint8 status = static_cast(message.data().at(0)); switch (status) { case 0: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: start up"; break; case 1: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: wait start"; break; case 2: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: NFN start"; break; case 3: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: discovery"; break; case 4: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: network init"; break; case 5: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: rescan"; break; case 6: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: running"; break; default: qCDebug(dcZigbeeNetwork()) << "Restart provisioned: unknown"; break; } if (m_startingState == StartingStateReset) { if (m_networkRunning) { qCDebug(dcZigbeeNetwork()) << "Reset finished. Network already running. No need to set it up"; readControllerVersion(); // FIXME: get network status //setStartingState(StartingStateGetPermitJoinStatus); } else { qCDebug(dcZigbeeNetwork()) << "Reset finished. Set up network"; setStartingState(StartingStateGetVersion); } } } void ZigbeeNetworkNxp::processRouterDiscoveryConfirm(const ZigbeeInterfaceMessage &message) { if (message.data().isEmpty()) return; quint8 status = static_cast(message.data().at(0)); Zigbee::ZigbeeNwkLayerStatus networkStatus = static_cast(message.data().at(0)); qCDebug(dcZigbeeNetwork()) << "Router discovery confirm received" << status << networkStatus; } void ZigbeeNetworkNxp::processDefaultResponse(const ZigbeeInterfaceMessage &message) { // Parse network status QByteArray data = message.data(); quint8 sequenceNumber = 0; quint16 shortAddress = 0; quint8 sourceEndpoint = 0; quint8 destinationEndpoint = 0; quint16 clusterId = 0; quint8 commandId = 0; quint8 commandStatus = 0; QDataStream stream(&data, QIODevice::ReadOnly); stream >> sequenceNumber >> shortAddress >> sourceEndpoint >> destinationEndpoint >> clusterId >> commandId >> commandStatus; qCDebug(dcZigbeeNetwork()).noquote() << "Default response" << sequenceNumber; qCDebug(dcZigbeeNetwork()) << " Short address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress); qCDebug(dcZigbeeNetwork()) << " Source endpoint:" << ZigbeeUtils::convertByteToHexString(sourceEndpoint); qCDebug(dcZigbeeNetwork()) << " Destination endpoint:" << ZigbeeUtils::convertByteToHexString(sourceEndpoint); qCDebug(dcZigbeeNetwork()) << " Destination endpoint:" << static_cast(clusterId); qCDebug(dcZigbeeNetwork()) << " Command:" << ZigbeeUtils::convertByteToHexString(commandId); qCDebug(dcZigbeeNetwork()) << " Command status:" << static_cast(commandStatus); // FIXME: check if this is related to a current reply } void ZigbeeNetworkNxp::onMessageReceived(const ZigbeeInterfaceMessage &message) { switch (message.messageType()) { case Zigbee::MessageTypeLogging: processLoggingMessage(message); break; case Zigbee::MessageTypeFactoryNewRestart: processFactoryNewRestart(message); break; case Zigbee::MessageTypeNodeClusterList: processNodeClusterList(message); break; case Zigbee::MessageTypeNodeAttributeList: processNodeAttributeList(message); break; case Zigbee::MessageTypeNodeCommandIdList: processNodeCommandIdList(message); break; case Zigbee::MessageTypeDeviceAnnounce: processDeviceAnnounce(message); break; case Zigbee::MessageTypeAttributeReport: processAttributeReport(message); break; case Zigbee::MessageTypeReadAttributeResponse: processReadAttributeResponse(message); break; case Zigbee::MessageTypeLeaveIndication: processLeaveIndication(message); break; case Zigbee::MessageTypeNetworkJoinedFormed: processNetworkFormed(message); break; case Zigbee::MessageTypeRestartProvisioned: processRestartProvisioned(message); break; case Zigbee::MessageTypeRouterDiscoveryConfirm: processRouterDiscoveryConfirm(message); break; case Zigbee::MessageTypeDefaultResponse: processDefaultResponse(message); break; default: qCWarning(dcZigbeeController()) << "Unhandled message received:" << message; break; } } void ZigbeeNetworkNxp::onControllerAvailableChanged(bool available) { qCDebug(dcZigbeeNetwork()) << "Hardware controller is" << (available ? "now available" : "not available"); if (!available) { foreach (ZigbeeNode *node, nodes()) { qobject_cast(node)->setConnected(false); } setError(ErrorHardwareUnavailable); m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); setStartingState(StartingStateNone); setState(StateOffline); } else { m_error = ErrorNoError; m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); // Note: if we are factory resetting, erase also the data on the controller before resetting if (m_factoryResetting) { setStartingState(StartingStateErase); } else { setStartingState(StartingStateReset); } setState(StateStarting); } } //void ZigbeeNetworkNxp::requestMatchDescriptor(const quint16 &shortAddress, const Zigbee::ZigbeeProfile &profile) //{ // // TargetAddress profile InputClusterCount InputClusterList OutputClusterCount OutputClusterList // Q_UNUSED(profile) // QByteArray data; // QDataStream stream(&data, QIODevice::WriteOnly); // stream << shortAddress; // stream << static_cast(0xFFFF); // stream << static_cast(0); // stream << static_cast(0); // stream << static_cast(0); // stream << static_cast(0); // ZigbeeInterfaceRequest request(ZigbeeInterfaceMessage(Zigbee::MessageTypeMatchDescriptorRequest, data)); // request.setExpectedAdditionalMessageType(Zigbee::MessageTypeMatchDescriptorResponse); // request.setDescription("Request match descriptors " + ZigbeeUtils::convertUint16ToHexString(shortAddress)); // request.setTimoutIntervall(5000); // ZigbeeInterfaceReply *reply = controller()->sendRequest(request); // connect(reply, &ZigbeeInterfaceReply::finished, this, &ZigbeeNetworkNxp::onRequestMatchDescriptorFinished); //} void ZigbeeNetworkNxp::onCommandResetControllerFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; } void ZigbeeNetworkNxp::onCommandSoftResetControllerFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; } void ZigbeeNetworkNxp::onCommandStartScanFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeController()) << reply->additionalMessage(); processNetworkFormed(reply->additionalMessage()); } void ZigbeeNetworkNxp::onCommandRequestMatchDescriptorFinished() { ZigbeeInterfaceReply *reply = static_cast(sender()); reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; qCDebug(dcZigbeeController()) << reply->additionalMessage(); } void ZigbeeNetworkNxp::processNetworkFormed(const ZigbeeInterfaceMessage &message) { // Parse network status QByteArray data = message.data(); quint8 networkStatus = 0; quint16 shortAddress = 0; quint64 extendedAddress = 0; quint8 currentChannel = 0; QDataStream stream(&data, QIODevice::ReadOnly); stream >> networkStatus >> shortAddress >> extendedAddress >> currentChannel; QString networkStatusString; if (networkStatus == 0) { networkStatusString = "joined"; } else if (networkStatus == 1) { networkStatusString = "formed"; } else if (networkStatus >= 128 && networkStatus <= 244) { networkStatusString = "failed: Zigbee event code: " + QString::number(networkStatus); } else { networkStatusString = "unknown"; } if (networkStatus != Zigbee::ZigbeeNwkLayerStatusJointNetwork && networkStatus != Zigbee::ZigbeeNwkLayerStatusFormedNetwork) { qCWarning(dcZigbeeNetwork()) << "Forming network failed" << networkStatusString; m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); setStartingState(StartingStateNone); setState(StateOffline); setError(ErrorZigbeeError); m_networkRunning = false; return; } setChannel(currentChannel); // Parse network channel qCDebug(dcZigbeeNetwork()).noquote() << "Network" << networkStatusString; qCDebug(dcZigbeeNetwork()) << " Extended PAN ID:" << extendedPanId(); qCDebug(dcZigbeeNetwork()) << " Address:" << ZigbeeUtils::convertUint16ToHexString(shortAddress); qCDebug(dcZigbeeNetwork()) << " Extended address:" << ZigbeeAddress(extendedAddress); qCDebug(dcZigbeeNetwork()) << " Channel:" << channel(); qCDebug(dcZigbeeNetwork()) << " Permit joining:" << permitJoining(); saveNetwork(); m_networkRunning = true; if (nodes().isEmpty()) { // Create coordinator node ZigbeeNodeNxp *node = new ZigbeeNodeNxp(m_controller, m_controller); node->setShortAddress(shortAddress); node->setExtendedAddress(ZigbeeAddress(extendedAddress)); node->startInitialization(); connect(node, &ZigbeeNode::stateChanged, this, [this, node](){ if (node->state() == ZigbeeNode::StateInitialized) { m_coordinatorNode = qobject_cast(node); addNode(m_coordinatorNode); qCDebug(dcZigbeeNetwork()) << "Coordinator node initialized. The network is now set up."; setState(StateRunning); // ZigbeeInterfaceReply *reply = m_controller->commandBindLocalGroup(Zigbee::ClusterIdOnOff, 0x01, 0x0001); // connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){ // reply->deleteLater(); // if (reply->status() != ZigbeeInterfaceReply::Success) { // qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); // return; // } // qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; // }); } }); } else { // Primary initialization was already done. ZigbeeInterfaceReply *reply = m_controller->commandGetVersion(); connect(reply, &ZigbeeInterfaceReply::finished, this, [this, reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } if (reply->additionalMessage().data().count() != 4) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << ":" << "Invalid payload size"; return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; // Parse version quint16 majorVersion = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(0, 2)); quint16 minorVersion = ZigbeeUtils::convertByteArrayToUint16(reply->additionalMessage().data().mid(2, 2)); qCDebug(dcZigbeeNetwork()) << "Controller version:" << QString("%1.%2").arg(majorVersion).arg(minorVersion); m_controller->setFirmwareVersionString(QString("%1.%2").arg(majorVersion).arg(minorVersion)); setState(StateRunning); }); } } void ZigbeeNetworkNxp::startNetwork() { // FIXME: define if router or coordinator qCDebug(dcZigbeeNetwork()) << "Start network..."; if (state() == StateUninitialized && !m_factoryResetting) loadNetwork(); // Do a factory reset if there are no network configuration and create a new one from scratch m_factoryResetting = !networkConfigurationAvailable(); setState(StateOffline); // Check if we have to create a pan ID and select the channel if (extendedPanId() == 0) { setExtendedPanId(ZigbeeUtils::generateRandomPanId()); qCDebug(dcZigbeeNetwork()) << "Created new PAN ID:" << extendedPanId(); } if (securityConfiguration().networkKey().isNull()) { qCDebug(dcZigbeeNetwork()) << "Create a new network key"; ZigbeeNetworkKey key = ZigbeeNetworkKey::generateKey(); m_securityConfiguration.setNetworkKey(key); } qCDebug(dcZigbeeNetwork()) << "Using network link key" << securityConfiguration().networkKey(); qCDebug(dcZigbeeNetwork()) << "Using global trust center link key" << securityConfiguration().globalTrustCenterLinkKey(); // TODO: get desired channel, by default use all if (!m_controller->enable(serialPortName(), serialBaudrate())) { m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); setState(StateOffline); setStartingState(StartingStateNone); setError(ErrorHardwareUnavailable); return; } m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); // Note: wait for the controller available signal and start the initialization there } void ZigbeeNetworkNxp::stopNetwork() { qCDebug(dcZigbeeNetwork()) << "Stopping network..."; setState(StateStopping); if (m_controller) { delete m_controller; m_controller = nullptr; } setStartingState(StartingStateNone); m_permitJoining = false; emit permitJoiningChanged(m_permitJoining); setState(StateOffline); setError(ErrorNoError); } void ZigbeeNetworkNxp::reset() { qCDebug(dcZigbeeNetwork()) << "Reset the bridge controller."; setState(StateOffline); ZigbeeInterfaceReply *reply = m_controller->commandResetController(); connect(reply, &ZigbeeInterfaceReply::finished, this, [reply](){ reply->deleteLater(); if (reply->status() != ZigbeeInterfaceReply::Success) { qCWarning(dcZigbeeController()) << "Could not" << reply->request().description() << reply->status() << reply->statusErrorMessage(); return; } qCDebug(dcZigbeeController()) << reply->request().description() << "finished successfully"; // Note: the controller is now sending a log of cluster and attribte/command information // After the reset the state machine will continue }); } void ZigbeeNetworkNxp::factoryResetNetwork() { qCDebug(dcZigbeeNetwork()) << "Factory reset network and forget all information. This cannot be undone."; clearSettings(); setState(StateUninitialized); m_factoryResetting = true; qCDebug(dcZigbeeNetwork()) << "The factory reset is finished. Start restart with a fresh network."; startNetwork(); }