641 lines
30 KiB
C++
641 lines
30 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
|
|
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
|
|
*
|
|
* nymea-zigbee
|
|
* Zigbee integration module for nymea
|
|
*
|
|
* Copyright (C) 2013 - 2024, nymea GmbH
|
|
* Copyright (C) 2024 - 2025, chargebyte austria GmbH
|
|
*
|
|
* This file is part of nymea-zigbee.
|
|
*
|
|
* nymea-zigbee is free software: you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* as published by the Free Software Foundation, either version 3
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* nymea-zigbee 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 nymea-zigbee. If not, see <https://www.gnu.org/licenses/>.
|
|
*
|
|
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
|
|
|
|
#include "zigbeenetworknxp.h"
|
|
#include "loggingcategory.h"
|
|
#include "zigbeeutils.h"
|
|
#include "zigbeenetworkdatabase.h"
|
|
|
|
#include <QDataStream>
|
|
|
|
ZigbeeNetworkNxp::ZigbeeNetworkNxp(const QUuid &networkUuid, QObject *parent) :
|
|
ZigbeeNetwork(networkUuid, parent)
|
|
{
|
|
m_controller = new ZigbeeBridgeControllerNxp(this);
|
|
connect(m_controller, &ZigbeeBridgeControllerNxp::availableChanged, this, &ZigbeeNetworkNxp::onControllerAvailableChanged);
|
|
connect(m_controller, &ZigbeeBridgeControllerNxp::firmwareVersionChanged, this, &ZigbeeNetworkNxp::firmwareVersionChanged);
|
|
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::apsDataAckReceived, this, &ZigbeeNetworkNxp::onApsDataAckReceived);
|
|
connect(m_controller, &ZigbeeBridgeControllerNxp::nodeLeft, this, &ZigbeeNetworkNxp::onNodeLeftIndication);
|
|
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);
|
|
}
|
|
});
|
|
}
|
|
|
|
ZigbeeBridgeController *ZigbeeNetworkNxp::bridgeController() const
|
|
{
|
|
if (!m_controller)
|
|
return nullptr;
|
|
|
|
return qobject_cast<ZigbeeBridgeController *>(m_controller);
|
|
}
|
|
|
|
Zigbee::ZigbeeBackendType ZigbeeNetworkNxp::backendType() const
|
|
{
|
|
return Zigbee::ZigbeeBackendTypeNxp;
|
|
}
|
|
|
|
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, reply](){
|
|
if (m_pendingReplies.values().contains(reply)) {
|
|
quint8 requestId = m_pendingReplies.key(reply);
|
|
m_pendingReplies.remove(requestId);
|
|
return;
|
|
}
|
|
|
|
if (m_bufferedReplies.values().contains(reply)) {
|
|
quint8 requestId = m_bufferedReplies.key(reply);
|
|
m_bufferedReplies.remove(requestId);
|
|
return;
|
|
}
|
|
//qCWarning(dcZigbeeNetwork()) << "#### Removed network reply" << reply << "ID:" << requestId << "Current reply count" << m_pendingReplies.size();
|
|
});
|
|
|
|
// Finish the reply right away if the network is offline
|
|
if (!m_controller->available() || state() == ZigbeeNetwork::StateOffline) {
|
|
finishReplyInternally(reply, ZigbeeNetworkReply::ErrorNetworkOffline);
|
|
return reply;
|
|
}
|
|
|
|
// Enqueu reply and send next one if we have enouth capacity
|
|
m_replyQueue.enqueue(reply);
|
|
//qCDebug(dcZigbeeNetwork()) << "=== Pending replies count (enqueued)" << m_replyQueue.count();
|
|
sendNextReply();
|
|
|
|
return reply;
|
|
}
|
|
|
|
void ZigbeeNetworkNxp::setPermitJoining(quint8 duration, quint16 address)
|
|
{
|
|
if (duration > 0) {
|
|
qCDebug(dcZigbeeNetwork()) << "Set permit join for" << duration << "s on" << ZigbeeUtils::convertUint16ToHexString(address);
|
|
} else {
|
|
qCDebug(dcZigbeeNetwork()) << "Disable permit join on"<< ZigbeeUtils::convertUint16ToHexString(address);
|
|
}
|
|
|
|
if (address == 0x0000) {
|
|
// Only the coordinator is allowed to join the network
|
|
qCDebug(dcZigbeeNetwork()) << "Set permit join in the coordinator node only to" << duration << "[s]";
|
|
ZigbeeInterfaceNxpReply *reply = m_controller->requestSetPermitJoinCoordinator(duration);
|
|
connect(reply, &ZigbeeInterfaceNxpReply::finished, this, [this, reply, duration](){
|
|
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";
|
|
setPermitJoiningState(false);
|
|
} else {
|
|
setPermitJoiningState(duration > 0, duration);
|
|
}
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
// Note: since compliance version >= 21 the value 255 is not any more endless.
|
|
// We need to refresh the command on timeout if the duration is longer
|
|
|
|
ZigbeeNetworkReply *reply = requestSetPermitJoin(address, duration);
|
|
connect(reply, &ZigbeeNetworkReply::finished, this, [this, reply, duration, address](){
|
|
if (reply->zigbeeApsStatus() != Zigbee::ZigbeeApsStatusSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Could not set permit join to" << duration << ZigbeeUtils::convertUint16ToHexString(address) << reply->zigbeeApsStatus();
|
|
setPermitJoiningState(false);
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcZigbeeNetwork()) << "Permit join request finished successfully";
|
|
setPermitJoiningState(duration > 0, duration);
|
|
|
|
if (address == Zigbee::BroadcastAddressAllRouters || address == 0x0000) {
|
|
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::sendNextReply()
|
|
{
|
|
if (m_replyQueue.isEmpty())
|
|
return;
|
|
|
|
if (m_currentReply)
|
|
return;
|
|
|
|
|
|
ZigbeeNetworkReply *reply = m_replyQueue.dequeue();
|
|
//qCDebug(dcZigbeeNetwork()) << "=== Pending replies count (dequeued)" << m_replyQueue.count();
|
|
|
|
ZigbeeInterfaceNxpReply *interfaceReply = m_controller->requestSendRequest(reply->request());
|
|
connect(interfaceReply, &ZigbeeInterfaceNxpReply::finished, reply, [this, reply, interfaceReply](){
|
|
if (interfaceReply->status() != Nxp::StatusSuccess) {
|
|
qCWarning(dcZigbeeController()) << "Could not send request to controller. SQN:" << interfaceReply->sequenceNumber() << interfaceReply->status();
|
|
finishReplyInternally(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().destinationAddressMode() == Zigbee::DestinationAddressModeShortAddress &&
|
|
reply->request().destinationShortAddress() == 0x0000 &&
|
|
reply->request().profileId() == Zigbee::ZigbeeProfileDevice) ||
|
|
(reply->request().destinationAddressMode() == Zigbee::DestinationAddressModeIeeeAddress &&
|
|
m_coordinatorNode &&
|
|
reply->request().destinationIeeeAddress() == m_coordinatorNode->extendedAddress() &&
|
|
reply->request().profileId() == Zigbee::ZigbeeProfileDevice)) {
|
|
|
|
qCDebug(dcZigbeeNetwork()) << "Finish reply since there will be no CONFIRM for local node requests.";
|
|
finishReplyInternally(reply);
|
|
return;
|
|
}
|
|
|
|
quint8 networkRequestId = interfaceReply->responseData().at(0);
|
|
ZigbeeNetworkRequest request = reply->request();
|
|
request.setRequestId(networkRequestId);
|
|
updateReplyRequest(reply, request);
|
|
|
|
qCDebug(dcZigbeeAps()) << "Request SQN updated:" << reply->request();
|
|
//qCWarning(dcZigbeeNetwork()) << "#### Insert network reply" << reply << "ID:" << networkRequestId << "Current reply count" << m_pendingReplies.count();
|
|
m_pendingReplies.insert(networkRequestId, reply);
|
|
// The request has been sent successfully to the device, start the timeout timer now
|
|
startWaitingReply(reply);
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkNxp::finishReplyInternally(ZigbeeNetworkReply *reply, ZigbeeNetworkReply::Error error)
|
|
{
|
|
finishNetworkReply(reply, error);
|
|
if (m_currentReply == reply) {
|
|
m_currentReply = nullptr;
|
|
}
|
|
|
|
sendNextReply();
|
|
}
|
|
|
|
ZigbeeNetworkReply *ZigbeeNetworkNxp::requestSetPermitJoin(quint16 shortAddress, quint8 duration)
|
|
{
|
|
// Get the power descriptor
|
|
ZigbeeNetworkRequest request;
|
|
request.setRequestId(generateSequenceNumber());
|
|
request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
|
|
request.setDestinationShortAddress(static_cast<quint16>(shortAddress));
|
|
request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
|
|
request.setClusterId(ZigbeeDeviceProfile::MgmtPermitJoinRequest);
|
|
request.setSourceEndpoint(0); // ZDO
|
|
request.setRadius(30);
|
|
|
|
// Build ASDU
|
|
QByteArray asdu;
|
|
QDataStream stream(&asdu, QIODevice::WriteOnly);
|
|
stream.setByteOrder(QDataStream::LittleEndian);
|
|
stream << request.requestId();
|
|
stream << duration;
|
|
stream << static_cast<quint8>(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.";
|
|
// FIXME: try 3 times, then give up or perform a factory flash
|
|
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](){
|
|
if (processVersionReply(reply)) {
|
|
m_controller->refreshControllerState();
|
|
}
|
|
});
|
|
|
|
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::onControllerAvailableChanged(bool available)
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Controller is" << (available ? "now available" : "not available any more");
|
|
if (available) {
|
|
if (m_controller->canUpdate() && !m_controller->initiallyFlashed()) {
|
|
qCDebug(dcZigbeeNetwork()) << "The firmware of the controller can be updated and has not been initially flashed. Perform a factory reset flash procedure...";
|
|
m_controller->startFactoryResetUpdate();
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
if (!macAddress().isNull() && ZigbeeAddress(ieeeAddress) != macAddress()) {
|
|
qCWarning(dcZigbeeNetwork()) << "The mac address of the coordinator has changed since the network has been set up.";
|
|
qCWarning(dcZigbeeNetwork()) << "The network is bound to a specific controller. Since the controller has changed the network can not be started.";
|
|
qCWarning(dcZigbeeNetwork()) << "Please factory reset the network or plug in the original controller.";
|
|
setError(ZigbeeNetwork::ErrorHardwareModuleChanged);
|
|
stopNetwork();
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcZigbeeNetwork()) << "We already have the coordinator node. Network starting done.";
|
|
setNodeInformation(m_coordinatorNode, "NXP", "JN516x", bridgeController()->firmwareVersion());
|
|
setPermitJoining(0);
|
|
setState(StateRunning);
|
|
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;
|
|
setNodeInformation(m_coordinatorNode, "NXP", "JN516x", bridgeController()->firmwareVersion());
|
|
/* Note: this currently has been hardcoded into the firmware. TODO: implement appropriate method for binding coordinator to group
|
|
|
|
ZigbeeClusterGroups *groupsCluster = coordinatorNode->getEndpoint(0x01)->inputCluster<ZigbeeClusterGroups>(ZigbeeClusterLibrary::ClusterIdGroups);
|
|
if (!groupsCluster) {
|
|
qCWarning(dcZigbeeNetwork()) << "Failed to get groups cluster from coordinator. The coordinator will not be in default group 0x0000";
|
|
setState(StateRunning);
|
|
setPermitJoining(0);
|
|
return;
|
|
}
|
|
|
|
ZigbeeClusterReply *reply = groupsCluster->addGroup(0x0000, "Default");
|
|
connect(reply, &ZigbeeClusterReply::finished, this, [=](){
|
|
if (reply->error() != ZigbeeClusterReply::ErrorNoError) {
|
|
qCWarning(dcZigbeeNetwork()) << "Failed to add coordinator to default group 0x0000. The coordinator will not be in default group 0x0000";
|
|
}
|
|
setState(StateRunning);
|
|
setPermitJoining(0);
|
|
});
|
|
*/
|
|
|
|
setState(StateRunning);
|
|
setPermitJoining(0);
|
|
return;
|
|
}
|
|
});
|
|
|
|
coordinatorNode->startInitialization();
|
|
addUnitializedNode(coordinatorNode);
|
|
});
|
|
});
|
|
break;
|
|
}
|
|
case ZigbeeBridgeControllerNxp::ControllerStateStarting:
|
|
setState(StateStarting);
|
|
break;
|
|
case ZigbeeBridgeControllerNxp::ControllerStateBooting:
|
|
setState(StateStarting);
|
|
break;
|
|
case ZigbeeBridgeControllerNxp::ControllerStateRunningUninitialized: {
|
|
// Create the database if there is no database available
|
|
setState(StateStarting);
|
|
|
|
initializeDatabase();
|
|
|
|
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) {
|
|
qCDebug(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation" << confirm;
|
|
return;
|
|
}
|
|
|
|
if (confirm.zigbeeStatusCode == Zigbee::ZigbeeNwkLayerStatusFrameBuffered) {
|
|
// Move the reply to the buffered hash
|
|
m_pendingReplies.remove(confirm.requestId);
|
|
m_bufferedReplies.insert(confirm.requestId, reply);
|
|
// The frame has been buffered and will be sent once the route has been discovered.
|
|
// If the ACK will arrive, the frame was sent successfully, otherwise on timeout the request failed
|
|
qCWarning(dcZigbeeNetwork()) << "Request frame buffered" << reply->request();
|
|
return;
|
|
}
|
|
|
|
setReplyResponseError(reply, 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::onApsDataAckReceived(const Zigbee::ApsdeDataAck &acknowledgement)
|
|
{
|
|
// Check first if we received an ACK from a buffered node...if so, the network reply can be finished with the given ACK status
|
|
ZigbeeNetworkReply *reply = m_bufferedReplies.value(acknowledgement.requestId);
|
|
if (reply) {
|
|
if (acknowledgement.zigbeeStatusCode != Zigbee::ZigbeeApsStatusSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Buffered frame from network request has been acknowledged with error" << acknowledgement;
|
|
} else {
|
|
qCDebug(dcZigbeeNetwork()) << "Buffered frame from network request has been acknowledged successfully" << acknowledgement;
|
|
}
|
|
setReplyResponseError(reply, acknowledgement.zigbeeStatusCode);
|
|
} else {
|
|
if (acknowledgement.zigbeeStatusCode != Zigbee::ZigbeeApsStatusSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << acknowledgement;
|
|
} else {
|
|
ZigbeeNode *node = getZigbeeNode(acknowledgement.destinationAddress);
|
|
if (node) {
|
|
// We received a successfull ACk from this node...it is reachable in any case
|
|
setNodeReachable(node, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ZigbeeNetworkNxp::onNodeLeftIndication(const ZigbeeAddress &ieeeAddress, bool rejoining)
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Received node left indication" << ieeeAddress.toString() << "rejoining:" << rejoining;
|
|
if (!hasNode(ieeeAddress)) {
|
|
qCDebug(dcZigbeeNetwork()) << "Node left the network" << ieeeAddress.toString();
|
|
return;
|
|
}
|
|
|
|
ZigbeeNode *node = getZigbeeNode(ieeeAddress);
|
|
qCDebug(dcZigbeeNetwork()) << node << "left the network";
|
|
removeNode(node);
|
|
}
|
|
|
|
void ZigbeeNetworkNxp::startNetwork()
|
|
{
|
|
loadNetwork();
|
|
|
|
setPermitJoiningState(false);
|
|
|
|
if (!m_controller->enable(serialPortName(), serialBaudrate())) {
|
|
setState(StateOffline);
|
|
setError(ErrorHardwareUnavailable);
|
|
return;
|
|
}
|
|
|
|
// Wait for available signal...
|
|
}
|
|
|
|
void ZigbeeNetworkNxp::stopNetwork()
|
|
{
|
|
m_controller->disable();
|
|
}
|
|
|
|
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();
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkNxp::destroyNetwork()
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Destroy network and delete the database";
|
|
m_controller->disable();
|
|
clearSettings();
|
|
}
|