549 lines
24 KiB
C++
549 lines
24 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 "zdo/zigbeedeviceprofile.h"
|
|
#include "zigbeenetworkti.h"
|
|
#include "loggingcategory.h"
|
|
#include "zigbeeutils.h"
|
|
#include "zigbeenetworkdatabase.h"
|
|
|
|
#include <QDataStream>
|
|
#include <QtCrypto>
|
|
|
|
ZigbeeNetworkTi::ZigbeeNetworkTi(const QUuid &networkUuid, QObject *parent) :
|
|
ZigbeeNetwork(networkUuid, parent)
|
|
{
|
|
m_controller = new ZigbeeBridgeControllerTi(this);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::availableChanged, this, &ZigbeeNetworkTi::onControllerAvailableChanged);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::controllerStateChanged, this, &ZigbeeNetworkTi::onControllerStateChanged);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::firmwareVersionChanged, this, &ZigbeeNetworkTi::firmwareVersionChanged);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::permitJoinStateChanged, this, &ZigbeeNetworkTi::onPermitJoinStateChanged);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::deviceIndication, this, &ZigbeeNetworkTi::onDeviceIndication);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::apsDataIndicationReceived, this, &ZigbeeNetworkTi::onApsDataIndicationReceived);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::apsDataConfirmReceived, this, &ZigbeeNetworkTi::onApsDataConfirmReceived);
|
|
connect(m_controller, &ZigbeeBridgeControllerTi::nodeLeft, this, &ZigbeeNetworkTi::onNodeLeaveIndication);
|
|
}
|
|
|
|
ZigbeeBridgeController *ZigbeeNetworkTi::bridgeController() const
|
|
{
|
|
if (!m_controller)
|
|
return nullptr;
|
|
|
|
return m_controller;
|
|
}
|
|
|
|
Zigbee::ZigbeeBackendType ZigbeeNetworkTi::backendType() const
|
|
{
|
|
return Zigbee::ZigbeeBackendTypeTi;
|
|
}
|
|
|
|
ZigbeeNetworkReply *ZigbeeNetworkTi::sendRequest(const ZigbeeNetworkRequest &request)
|
|
{
|
|
ZigbeeNetworkReply *reply = createNetworkReply(request);
|
|
|
|
// Finish the reply right away if the network is offline
|
|
if (!m_controller->available() || state() == ZigbeeNetwork::StateOffline || state() == ZigbeeNetwork::StateStopping) {
|
|
finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNetworkOffline);
|
|
return reply;
|
|
}
|
|
|
|
if (state() == ZigbeeNetwork::StateStarting) {
|
|
m_requestQueue.append(reply);
|
|
return reply;
|
|
}
|
|
|
|
ZigbeeInterfaceTiReply *interfaceReply = m_controller->requestSendRequest(request);
|
|
connect(interfaceReply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply, interfaceReply](){
|
|
if (interfaceReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeController()) << "Could not send request to controller." << interfaceReply->statusCode();
|
|
finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError);
|
|
return;
|
|
}
|
|
|
|
finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNoError);
|
|
});
|
|
|
|
return reply;
|
|
}
|
|
|
|
void ZigbeeNetworkTi::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);
|
|
}
|
|
|
|
ZigbeeInterfaceTiReply *requestPermitJoinReply = m_controller->requestPermitJoin(duration, address);
|
|
connect(requestPermitJoinReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
|
|
if (requestPermitJoinReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Could not set permit join to" << duration << ZigbeeUtils::convertUint16ToHexString(address) << requestPermitJoinReply->statusCode();
|
|
return;
|
|
}
|
|
qCDebug(dcZigbeeNetwork()) << "Permit join request finished successfully:" << duration;
|
|
|
|
setPermitJoiningState(true, duration);
|
|
|
|
// Opening the green power network too
|
|
// Todo: This should probably be somewhere else, but not yet sure how other backeds deal with this
|
|
QByteArray payload;
|
|
QDataStream stream(&payload, QIODevice::WriteOnly);
|
|
stream.setByteOrder(QDataStream::LittleEndian);
|
|
stream << static_cast<quint8>(0x0b); // options
|
|
stream << static_cast<quint16>(duration);
|
|
|
|
|
|
// Build ZCL frame control
|
|
ZigbeeClusterLibrary::FrameControl frameControl;
|
|
frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
|
|
frameControl.manufacturerSpecific = false;
|
|
frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient;
|
|
frameControl.disableDefaultResponse = true;
|
|
|
|
// Build ZCL header
|
|
ZigbeeClusterLibrary::Header header;
|
|
header.frameControl = frameControl;
|
|
header.command = 0x02;// TODO: ZigbeeClusterGreenPower::ClusterCommandCommissioningMode;
|
|
header.transactionSequenceNumber = generateSequenceNumber();
|
|
|
|
// Build ZCL frame
|
|
ZigbeeClusterLibrary::Frame frame;
|
|
frame.header = header;
|
|
frame.payload = payload;
|
|
|
|
ZigbeeNetworkRequest request;
|
|
request.setRequestId(generateSequenceNumber());
|
|
request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
|
|
request.setDestinationShortAddress(0xFFFD);
|
|
request.setDestinationEndpoint(242); // Green Power Endpoint
|
|
request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
|
|
request.setClusterId(0x21);
|
|
request.setTxOptions(static_cast<Zigbee::ZigbeeTxOption>(0x00)); // FIXME: There should be TxOptionsNone I guess...
|
|
request.setSourceEndpoint(242); // Green Power Endpoint
|
|
request.setRadius(30); // FIXME: There should be a more clever way to figure out the radius
|
|
request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
|
|
|
|
m_controller->requestSendRequest(request);
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkTi::initController()
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Initializing controller";
|
|
setState(StateStarting);
|
|
setError(ErrorNoError);
|
|
|
|
ZigbeeInterfaceTiReply *initReply = m_controller->init();
|
|
connect(initReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
|
|
if (initReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Error initializing controller";
|
|
setState(StateUninitialized);
|
|
setError(ErrorZigbeeError);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkTi::commissionController()
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Commissioning controller";
|
|
|
|
quint16 panId = ZigbeeUtils::generateRandomPanId();
|
|
|
|
ZigbeeInterfaceTiReply *commissionReply = m_controller->commission(Ti::DeviceLogicalTypeCoordinator, panId, channelMask());
|
|
connect(commissionReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
|
|
if (commissionReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Error commissioning controller.";
|
|
setState(StateUninitialized);
|
|
setError(ErrorZigbeeError);
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcZigbeeNetwork()) << "Controller commissioned";
|
|
|
|
startControllerNetwork();
|
|
setMacAddress(m_controller->networkConfiguration().ieeeAddress);
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkTi::startControllerNetwork()
|
|
{
|
|
setState(StateStarting);
|
|
qCDebug(dcZigbeeNetwork()) << "Starting network on controller...";
|
|
ZigbeeInterfaceTiReply *startReply = m_controller->start();
|
|
connect(startReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
|
|
if (startReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Error starting network.";
|
|
setState(StateOffline);
|
|
setError(ErrorZigbeeError);
|
|
return;
|
|
}
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkTi::processGreenPowerFrame(const Zigbee::ApsdeDataIndication &indication)
|
|
{
|
|
ZigbeeClusterLibrary::Frame inputFrame = ZigbeeClusterLibrary::parseFrameData(indication.asdu);
|
|
QDataStream inputStream(inputFrame.payload);
|
|
inputStream.setByteOrder(QDataStream::LittleEndian);
|
|
quint8 commandId, payloadSize;
|
|
quint16 options;
|
|
quint32 srcId, frameCounter;
|
|
inputStream >> options >> srcId >> frameCounter >> commandId >> payloadSize;
|
|
QByteArray commandFrame = inputFrame.payload.right(payloadSize);
|
|
qCWarning(dcZigbeeNetwork()) << "Green Power frame:" << options << srcId << frameCounter << commandId << payloadSize << commandFrame.toHex();
|
|
|
|
if (commandId == 0xE0) {
|
|
qCWarning(dcZigbeeNetwork()) << "Green power commissioning";
|
|
QDataStream commandStream(commandFrame);
|
|
commandStream.setByteOrder(QDataStream::LittleEndian);
|
|
quint8 deviceId, inputOptions, extendedOptions;
|
|
QByteArray securityKey;
|
|
quint32 keyMic;
|
|
quint32 outgoingCounter;
|
|
commandStream >> deviceId >> inputOptions >> extendedOptions;
|
|
for (int i = 0; i < 16; i++) {
|
|
quint8 byte;
|
|
commandStream >> byte;
|
|
securityKey.append(byte);
|
|
}
|
|
commandStream >> keyMic >> outgoingCounter;
|
|
|
|
// Send commissioning reply
|
|
QByteArray payload;
|
|
QDataStream stream(&payload, QIODevice::WriteOnly);
|
|
stream.setByteOrder(QDataStream::LittleEndian);
|
|
ZigbeeDataType options = ZigbeeDataType(static_cast<quint32>(0x00e548), Zigbee::Uint24); // options
|
|
for (int i = 0; i < options.data().length(); i++) {
|
|
stream << static_cast<quint8>(options.data().at(i));
|
|
}
|
|
stream << srcId;
|
|
stream << static_cast<quint16>(0x0b84); // Green Power group as configured during startup
|
|
stream << deviceId;
|
|
stream << outgoingCounter;
|
|
payload.append(encryptSecurityKey(srcId, securityKey));
|
|
|
|
// Build ZCL frame control
|
|
ZigbeeClusterLibrary::FrameControl frameControl;
|
|
frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
|
|
frameControl.manufacturerSpecific = false;
|
|
frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient;
|
|
frameControl.disableDefaultResponse = true;
|
|
|
|
// Build ZCL header
|
|
ZigbeeClusterLibrary::Header header;
|
|
header.frameControl = frameControl;
|
|
header.command = 0x01; // TODO: ZigbeeClusterGreenPower::ClusterCommandPairing;
|
|
header.transactionSequenceNumber = generateSequenceNumber() - 1;
|
|
|
|
// Build ZCL frame
|
|
ZigbeeClusterLibrary::Frame frame;
|
|
frame.header = header;
|
|
frame.payload = payload;
|
|
|
|
ZigbeeNetworkRequest request;
|
|
request.setRequestId(generateSequenceNumber() + 1);
|
|
request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
|
|
request.setDestinationShortAddress(0xFFFD);
|
|
request.setDestinationEndpoint(242); // Green Power Endpoint
|
|
request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
|
|
request.setClusterId(0x21);
|
|
request.setTxOptions(static_cast<Zigbee::ZigbeeTxOption>(0x00)); // FIXME: There should be TxOptionsNone I guess...
|
|
request.setSourceEndpoint(242); // Green Power Endpoint
|
|
request.setRadius(30); // FIXME: There should be a more clever way to figure out the radius
|
|
request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
|
|
|
|
m_controller->requestSendRequest(request);
|
|
|
|
QStringList l;
|
|
for (int i = 0; i < request.asdu().length(); i++) {
|
|
l.append(QString::number(static_cast<quint8>(request.asdu().at(i))));
|
|
}
|
|
|
|
// TODO: create the GreenPower Node here once GreenPower support is added properly
|
|
// emit greenPowerDeviceJoined(srcId, deviceId, indication.sourceIeeeAddress())
|
|
}
|
|
}
|
|
|
|
QByteArray ZigbeeNetworkTi::encryptSecurityKey(quint32 sourceId, const QByteArray &securityKey)
|
|
{
|
|
#if (QCA_VERSION >= QCA_VERSION_CHECK(2, 2, 0))
|
|
QByteArray sourceIdArray;
|
|
sourceIdArray.append(static_cast<quint8>(sourceId & 0x000000ff));
|
|
sourceIdArray.append(static_cast<quint8>((sourceId & 0x0000ff00) >> 8));
|
|
sourceIdArray.append(static_cast<quint8>((sourceId & 0x00ff0000) >> 16));
|
|
sourceIdArray.append(static_cast<quint8>((sourceId & 0xff000000) >> 24));
|
|
|
|
QByteArray nonce(13, Qt::Uninitialized);
|
|
for (int i = 0; i < 3; i++) {
|
|
for (int j = 0; j < 4; j++) {
|
|
nonce[4 * i + j] = sourceIdArray.at(j);
|
|
}
|
|
}
|
|
nonce[12] = 0x05;
|
|
|
|
QByteArray zigbeeLinkKey = securityConfiguration().globalTrustCenterLinkKey().toByteArray();
|
|
|
|
QCA::Initializer init;
|
|
QCA::Cipher cipher("aes128", QCA::Cipher::CCM, QCA::Cipher::DefaultPadding, QCA::Encode, zigbeeLinkKey, nonce);
|
|
QByteArray encrypted = cipher.update(securityKey).toByteArray();
|
|
encrypted.append(cipher.final().toByteArray());
|
|
return encrypted;
|
|
#else
|
|
Q_UNUSED(sourceId)
|
|
qCWarning(dcZigbeeNetwork()) << "Greenpower encryption requires AES-128-CCM (requires qca-qt5-2 >= 2.2.0)";
|
|
return securityKey;
|
|
#endif
|
|
}
|
|
|
|
void ZigbeeNetworkTi::onControllerAvailableChanged(bool available)
|
|
{
|
|
if (!available) {
|
|
qCWarning(dcZigbeeNetwork()) << "Hardware controller is not available any more.";
|
|
setError(ErrorHardwareUnavailable);
|
|
setPermitJoiningState(false);
|
|
setState(StateOffline);
|
|
setError(ErrorHardwareUnavailable);
|
|
} else {
|
|
setPermitJoiningState(false);
|
|
setState(StateOffline);
|
|
setError(ErrorNoError);
|
|
qCDebug(dcZigbeeNetwork()) << "Hardware controller is now available.";
|
|
initController();
|
|
}
|
|
}
|
|
|
|
void ZigbeeNetworkTi::onControllerStateChanged(ZigbeeBridgeControllerTi::ControllerState state)
|
|
{
|
|
switch (state) {
|
|
case ZigbeeBridgeControllerTi::ControllerStateDown:
|
|
setState(StateOffline);
|
|
break;
|
|
case ZigbeeBridgeControllerTi::ControllerStateInitialized:
|
|
|
|
// The mac address of the controller will be stored locally when the coordinator node replies
|
|
// to the node descriptor request which includes the IEEE address.
|
|
// If we don't know the mac address yet, it means that we've never had the zigbee network up and running
|
|
// => start the commissioning procedure, else, directly start the network
|
|
// Note: if the user messes around with the stick by provisioning stuff with another instance/software, it
|
|
// will not be detected currently and the "wrong" network starts up.
|
|
qCDebug(dcZigbeeNetwork()) << "Controller initialized";
|
|
qCDebug(dcZigbeeNetwork()) << "Stored MAC address:" << macAddress() << "Controller MAC address:" << m_controller->networkConfiguration().ieeeAddress;
|
|
|
|
if (macAddress() != ZigbeeAddress("00:00:00:00:00:00:00:00") && m_controller->networkConfiguration().ieeeAddress != macAddress()) {
|
|
qCWarning(dcZigbeeNetwork()) << "The controller MAC address changed. Please connect the original controller or create a new network.";
|
|
setState(StateUninitialized);
|
|
setError(ErrorHardwareModuleChanged);
|
|
stopNetwork();
|
|
return;
|
|
}
|
|
if (macAddress() == ZigbeeAddress("00:00:00:00:00:00:00")) {
|
|
// Controller network is not commissioned yet
|
|
qCDebug(dcZigbeeNetwork()) << "Controller is not comissioned yet. Comissioning now...";
|
|
commissionController();
|
|
return;
|
|
}
|
|
|
|
qCDebug(dcZigbeeNetwork()) << "Controller is ready and commissioned.";
|
|
startControllerNetwork();
|
|
|
|
break;
|
|
case ZigbeeBridgeControllerTi::ControllerStateRunning: {
|
|
qCDebug(dcZigbeeNetwork()) << "Controller network running. Registering endpoints on controller..";
|
|
|
|
setPanId(m_controller->networkConfiguration().panId);
|
|
setExtendedPanId(m_controller->networkConfiguration().extendedPanId);
|
|
setChannel(m_controller->networkConfiguration().currentChannel);
|
|
|
|
// TODO: This should be public API of libnymea-zigbee so that the application layer (e.g. nymea-plugins)
|
|
// can register the endpoints it needs for the particular application/device.
|
|
// Fow now we're registering HomeAutomation, LightLink and GreenPower endpoints.
|
|
m_controller->registerEndpoint(1, Zigbee::ZigbeeProfileHomeAutomation, 5, 0, {ZigbeeClusterLibrary::ClusterIdOtaUpgrade});
|
|
m_controller->registerEndpoint(12, Zigbee::ZigbeeProfileLightLink, 5, 0);
|
|
|
|
// The Green Power endpoing is a bit special, it also needs to be added to a group
|
|
ZigbeeInterfaceTiReply *registerLLEndpointReply = m_controller->registerEndpoint(242, Zigbee::ZigbeeProfileGreenPower, 5, 0);
|
|
connect(registerLLEndpointReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
|
|
if (registerLLEndpointReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Error registering GreenPower endpoint.";
|
|
setState(StateOffline);
|
|
return;
|
|
}
|
|
qCDebug(dcZigbeeNetwork()) << "Registered GreenPower endpoint on coordinator node.";
|
|
|
|
ZigbeeInterfaceTiReply *addGEndpointGroupReply = m_controller->addEndpointToGroup(242, 0x0b84);
|
|
connect(addGEndpointGroupReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
|
|
if (addGEndpointGroupReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeNetwork()) << "Error adding GreenPower endpoint to group.";
|
|
setState(StateOffline);
|
|
return;
|
|
}
|
|
|
|
// Now we're done. If this is a first start (no coordinator node loaded from configs) we'll add
|
|
// ourselves now and start the inspection.
|
|
if (!m_coordinatorNode) {
|
|
qCDebug(dcZigbeeNetwork()) << "Initializing coordinator node:" << m_controller->networkConfiguration();
|
|
ZigbeeNode *coordinatorNode = createNode(m_controller->networkConfiguration().shortAddress, m_controller->networkConfiguration().ieeeAddress, this);
|
|
m_coordinatorNode = coordinatorNode;
|
|
addUnitializedNode(coordinatorNode);
|
|
}
|
|
|
|
|
|
ZigbeeInterfaceTiReply *ledReply = m_controller->setLed(false);
|
|
connect(ledReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
|
|
|
|
setState(StateRunning);
|
|
|
|
// Introspecing ourselves on every start. Most of the times this wouldn't be needed, but if the above
|
|
// endpoints are changed (e.g. on a future upgrade), we'll want to refresh.
|
|
m_coordinatorNode->startInitialization();
|
|
connect(m_coordinatorNode, &ZigbeeNode::stateChanged, this, [=](ZigbeeNode::State state){
|
|
if (state == ZigbeeNode::StateInitialized) {
|
|
setNodeInformation(m_coordinatorNode, "z-Stack", "", bridgeController()->firmwareVersion());
|
|
}
|
|
});
|
|
|
|
while (!m_requestQueue.isEmpty()) {
|
|
ZigbeeNetworkReply *reply = m_requestQueue.takeFirst();
|
|
ZigbeeInterfaceTiReply *interfaceReply = m_controller->requestSendRequest(reply->request());
|
|
connect(interfaceReply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply, interfaceReply](){
|
|
if (interfaceReply->statusCode() != Ti::StatusCodeSuccess) {
|
|
qCWarning(dcZigbeeController()) << "Could send request to controller." << interfaceReply->statusCode();
|
|
finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError);
|
|
return;
|
|
}
|
|
finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNoError);
|
|
});
|
|
}
|
|
});
|
|
});
|
|
});
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ZigbeeNetworkTi::onPermitJoinStateChanged(quint8 duration)
|
|
{
|
|
setPermitJoiningState(duration > 0, duration);
|
|
m_controller->setLed(duration > 0);
|
|
}
|
|
|
|
void ZigbeeNetworkTi::onDeviceIndication(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, quint8 macCapabilities)
|
|
{
|
|
onDeviceAnnounced(shortAddress, ieeeAddress, macCapabilities);
|
|
}
|
|
|
|
void ZigbeeNetworkTi::onNodeLeaveIndication(const ZigbeeAddress &ieeeAddress, bool request, bool remove, bool rejoin)
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Received node leave indication" << ieeeAddress.toString() << "request:" << request << "remove:" << remove << "rejoining:" << rejoin;
|
|
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 ZigbeeNetworkTi::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm)
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Data confirm received:" << confirm;
|
|
}
|
|
|
|
void ZigbeeNetworkTi::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication)
|
|
{
|
|
// If it's for the green power endpoint, we'll have to do a commissioning.
|
|
// TODO: This should probably be in ZigbeeNode or ZigbeeDeviceObject, but not yet sure how other backends deal with it
|
|
if (indication.destinationEndpoint == 242) {
|
|
processGreenPowerFrame(indication);
|
|
}
|
|
|
|
// Check if this indocation is related to any pending reply
|
|
if (indication.profileId == Zigbee::ZigbeeProfileDevice) {
|
|
handleZigbeeDeviceProfileIndication(indication);
|
|
return;
|
|
}
|
|
|
|
// Else let the node handle this indication
|
|
handleZigbeeClusterLibraryIndication(indication);
|
|
}
|
|
|
|
void ZigbeeNetworkTi::startNetwork()
|
|
{
|
|
loadNetwork();
|
|
|
|
if (!m_controller->enable(serialPortName(), serialBaudrate())) {
|
|
setPermitJoiningState(false);
|
|
setState(StateOffline);
|
|
setError(ErrorHardwareUnavailable);
|
|
return;
|
|
}
|
|
|
|
setPermitJoiningState(false);
|
|
}
|
|
|
|
void ZigbeeNetworkTi::stopNetwork()
|
|
{
|
|
setState(StateStopping);
|
|
m_controller->disable();
|
|
setState(StateOffline);
|
|
}
|
|
|
|
void ZigbeeNetworkTi::reset()
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Resetting controller.";
|
|
m_controller->reset();
|
|
}
|
|
|
|
void ZigbeeNetworkTi::factoryResetNetwork()
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Factory resetting network and forget all information. This cannot be undone.";
|
|
|
|
ZigbeeInterfaceTiReply *reply = m_controller->factoryReset();
|
|
connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
|
|
qCDebug(dcZigbeeNetwork()) << "Factory reset reply finished" << reply->statusCode();
|
|
|
|
m_controller->disable();
|
|
clearSettings();
|
|
setMacAddress(ZigbeeAddress("00:00:00:00:00:00"));
|
|
setState(StateOffline);
|
|
setError(ErrorNoError);
|
|
qCDebug(dcZigbeeNetwork()) << "The factory reset is finished. Restarting with a fresh network.";
|
|
startNetwork();
|
|
});
|
|
}
|
|
|
|
void ZigbeeNetworkTi::destroyNetwork()
|
|
{
|
|
qCDebug(dcZigbeeNetwork()) << "Destroy network and delete the database";
|
|
m_controller->disable();
|
|
clearSettings();
|
|
}
|