// 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 . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "zdo/zigbeedeviceprofile.h" #include "zigbeenetworkdeconz.h" #include "loggingcategory.h" #include "zigbeeutils.h" #include "zigbeenetworkdatabase.h" #include ZigbeeNetworkDeconz::ZigbeeNetworkDeconz(const QUuid &networkUuid, QObject *parent) : ZigbeeNetwork(networkUuid, parent) { m_controller = new ZigbeeBridgeControllerDeconz(this); connect(m_controller, &ZigbeeBridgeControllerDeconz::availableChanged, this, &ZigbeeNetworkDeconz::onControllerAvailableChanged); connect(m_controller, &ZigbeeBridgeControllerDeconz::firmwareVersionChanged, this, &ZigbeeNetworkDeconz::firmwareVersionChanged); connect(m_controller, &ZigbeeBridgeControllerDeconz::apsDataConfirmReceived, this, &ZigbeeNetworkDeconz::onApsDataConfirmReceived); connect(m_controller, &ZigbeeBridgeControllerDeconz::apsDataIndicationReceived, this, &ZigbeeNetworkDeconz::onApsDataIndicationReceived); m_pollNetworkStateTimer = new QTimer(this); m_pollNetworkStateTimer->setInterval(1000); m_pollNetworkStateTimer->setSingleShot(false); connect(m_pollNetworkStateTimer, &QTimer::timeout, this, &ZigbeeNetworkDeconz::onPollNetworkStateTimeout); } ZigbeeBridgeController *ZigbeeNetworkDeconz::bridgeController() const { if (!m_controller) return nullptr; return qobject_cast(m_controller); } Zigbee::ZigbeeBackendType ZigbeeNetworkDeconz::backendType() const { return Zigbee::ZigbeeBackendTypeDeconz; } ZigbeeNetworkReply *ZigbeeNetworkDeconz::sendRequest(const ZigbeeNetworkRequest &request) { ZigbeeNetworkReply *reply = createNetworkReply(request); // Send the request, and keep the reply until transposrt, zigbee trasmission and response arrived m_pendingReplies.insert(request.requestId(), reply); connect(reply, &ZigbeeNetworkReply::finished, this, [this, request](){ m_pendingReplies.remove(request.requestId()); }); // Finish the reply right away if the network is offline if (!m_controller->available() || state() == ZigbeeNetwork::StateOffline) { finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNetworkOffline); return reply; } if (state() == ZigbeeNetwork::StateStarting) { m_requestQueue.append(reply); connect(reply, &ZigbeeNetworkReply::finished, this, [this, reply](){ m_requestQueue.removeAll(reply); }); return reply; } ZigbeeInterfaceDeconzReply *interfaceReply = m_controller->requestSendRequest(request); connect(interfaceReply, &ZigbeeInterfaceDeconzReply::finished, reply, [this, reply, interfaceReply](){ if (interfaceReply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not send request to controller. SQN:" << interfaceReply->sequenceNumber() << interfaceReply->statusCode(); finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError); return; } // The request has been sent successfully to the device, start the timeout timer now startWaitingReply(reply); }); return reply; } void ZigbeeNetworkDeconz::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); } // Note: since compliance version >= 21 the value 255 is not any more endless. // We need to refresh the command on timeout repeatedly if the duration is longer than 254 s 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); // Set the permit joining timeout network configuration parameter QByteArray parameterData; QDataStream stream(¶meterData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << duration; ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterPermitJoin, parameterData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Request" << reply->command() << "finished with error" << reply->statusCode(); return; } qCDebug(dcZigbeeNetwork()) << "Set permit join configuration request finished" << reply->statusCode(); }); }); } ZigbeeNetworkReply *ZigbeeNetworkDeconz::requestSetPermitJoin(quint16 shortAddress, quint8 duration) { // Get the power descriptor ZigbeeNetworkRequest request; request.setRequestId(generateSequenceNumber()); request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress); request.setDestinationShortAddress(static_cast(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(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); } void ZigbeeNetworkDeconz::sendPendingRequests() { while (!m_requestQueue.isEmpty()) { ZigbeeNetworkReply *reply = m_requestQueue.takeFirst(); ZigbeeInterfaceDeconzReply *interfaceReply = m_controller->requestSendRequest(reply->request()); connect(interfaceReply, &ZigbeeInterfaceDeconzReply::finished, reply, [this, reply, interfaceReply](){ if (interfaceReply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not send request to controller. SQN:" << interfaceReply->sequenceNumber() << interfaceReply->statusCode(); finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError); return; } // The request has been sent successfully to the device, start the timeout timer now startWaitingReply(reply); }); } } void ZigbeeNetworkDeconz::setCreateNetworkState(ZigbeeNetworkDeconz::CreateNetworkState state) { if (m_createState == state) return; m_createState = state; qCDebug(dcZigbeeNetwork()) << "Create network state changed" << m_createState; switch (m_createState) { case CreateNetworkStateIdle: break; case CreateNetworkStateStopNetwork: { ZigbeeInterfaceDeconzReply *reply = m_controller->requestChangeNetworkState(Deconz::NetworkStateOffline); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not stop network for creating a new one. SQN:" << reply->sequenceNumber() << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Stop network finished successfully. SQN:" << reply->sequenceNumber(); // Start polling the device state, should be Online -> Leaving -> Offline m_pollNetworkStateTimer->start(); }); break; } case CreateNetworkStateWriteConfiguration: { // - Set coordinator // - Set channel mask // - Set predefined network PANID (0: let the firmware pick, 1: use the defined pan id) // - Set NWK PANID // - Set trust center address (coordinator address) // - Set security mode // - Set network key QByteArray paramData; QDataStream stream(¶mData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Deconz::NodeTypeCoordinator); qCDebug(dcZigbeeNetwork()) << "Configure bridge to" << Deconz::NodeTypeCoordinator; ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterNodeType, paramData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterNodeType << Deconz::NodeTypeCoordinator << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Configured successfully bridge to" << Deconz::NodeTypeCoordinator << "SQN:" << reply->sequenceNumber(); QByteArray paramData; QDataStream stream(¶mData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(channelMask().toUInt32()); qCDebug(dcZigbeeNetwork()) << "Configure channel mask" << channelMask(); ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterChannelMask, paramData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterChannelMask << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Configured channel mask successfully. SQN:" << reply->sequenceNumber(); qCDebug(dcZigbeeNetwork()) << "Configure firmware to use predefined network PANID"; QByteArray paramData; QDataStream stream(¶mData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(0x01); ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterPredefinedNwkPanId, paramData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterPredefinedNwkPanId << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeController()) << "Configured firmware to use predefined network PNAID successfully. SQN:" << reply->sequenceNumber(); qCDebug(dcZigbeeNetwork()) << "Configure network PANID" << panId() << ZigbeeUtils::convertUint16ToHexString(panId()); QByteArray paramData; QDataStream stream(¶mData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << panId(); ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterPanId, paramData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterPanId << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeController()) << "Configured network PANID successfully. SQN:" << reply->sequenceNumber(); QByteArray paramData; QDataStream stream(¶mData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << m_controller->networkConfiguration().ieeeAddress.toUInt64(); qCDebug(dcZigbeeNetwork()) << "Configure trust center address" << m_controller->networkConfiguration().ieeeAddress.toString(); ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterTrustCenterAddress, paramData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterTrustCenterAddress << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeController()) << "Configured trust center address successfully. SQN:" << reply->sequenceNumber(); QByteArray paramData; QDataStream stream(¶mData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Deconz::SecurityModeNoMasterButTrustCenterKey); qCDebug(dcZigbeeNetwork()) << "Configure security mode" << Deconz::SecurityModeNoMasterButTrustCenterKey; ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterSecurityMode, paramData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterSecurityMode << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeController()) << "Configured security mode successfully. SQN:" << reply->sequenceNumber(); qCDebug(dcZigbeeNetwork()) << "Configure network key" << securityConfiguration().networkKey().toString(); ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterNetworkKey, securityConfiguration().networkKey().toByteArray()); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not write parameter. SQN:" << reply->sequenceNumber() << Deconz::ParameterNetworkKey << reply->statusCode(); // FIXME: set an appropriate error // Note: writing the network key fails all the time... //return; } else { qCDebug(dcZigbeeController()) << "Configured network key successfully. SQN:" << reply->sequenceNumber(); } // Re-read the configurations // Read all network parameters ZigbeeInterfaceDeconzReply *reply = m_controller->readNetworkParameters(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not read network parameters during network start up. SQN:" << reply->sequenceNumber() << reply->statusCode(); } qCDebug(dcZigbeeController()) << m_controller->networkConfiguration(); // Configuration finished, lets start the network setCreateNetworkState(CreateNetworkStateStartNetwork); }); }); }); }); }); }); }); }); break; } case CreateNetworkStateStartNetwork: { ZigbeeInterfaceDeconzReply *reply = m_controller->requestChangeNetworkState(Deconz::NetworkStateConnected); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not start network for creating a new one. SQN:" << reply->sequenceNumber() << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Start network finished successfully. SQN:" << reply->sequenceNumber(); // Start polling the device state, should be Online -> Leaving -> Offline m_pollNetworkStateTimer->start(); }); break; } case CreateNetworkStateReadConfiguration: { // Read all network parameters ZigbeeInterfaceDeconzReply *reply = m_controller->readNetworkParameters(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not read network parameters during network start up. SQN:" << reply->sequenceNumber() << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Reading network parameters finished successfully. SQN:" << reply->sequenceNumber(); qCDebug(dcZigbeeNetwork()) << "Network running with following configuration:"; qCDebug(dcZigbeeNetwork()) << m_controller->networkConfiguration(); setPanId(m_controller->networkConfiguration().panId); setExtendedPanId(m_controller->networkConfiguration().extendedPanId); setChannel(m_controller->networkConfiguration().currentChannel); setCreateNetworkState(CreateNetworkStateInitializeCoordinatorNode); }); break; } case CreateNetworkStateInitializeCoordinatorNode: { if (m_coordinatorNode) { if (!macAddress().isNull() && m_coordinatorNode->extendedAddress() != 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."; m_initializing = false; setNodeInformation(m_coordinatorNode, "deCONZ", "", bridgeController()->firmwareVersion()); setState(StateRunning); setPermitJoining(0); sendPendingRequests(); return; } m_initializing = false; setState(StateRunning); setPermitJoining(0); ZigbeeNode *coordinatorNode = createNode(m_controller->networkConfiguration().shortAddress, m_controller->networkConfiguration().ieeeAddress, this); m_coordinatorNode = coordinatorNode; connect(coordinatorNode, &ZigbeeNode::stateChanged, this, [this, coordinatorNode](ZigbeeNode::State state){ if (state == ZigbeeNode::StateInitialized) { qCDebug(dcZigbeeNetwork()) << "Coordinator initialized successfully." << coordinatorNode; sendPendingRequests(); return; } }); coordinatorNode->startInitialization(); addUnitializedNode(coordinatorNode); } } } void ZigbeeNetworkDeconz::runNetworkInitProcess() { // - Read the network state (until success) // - Read the firmware version // - Read the network configuration parameters // - If network running and we don't have configurations, write them // - If network running and configurations match, we are done // Read the firmware version qCDebug(dcZigbeeNetwork()) << "Request current device state..."; ZigbeeInterfaceDeconzReply *reply = m_controller->requestDeviceState(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Request" << reply->command() << "finished with error" << reply->statusCode(); m_initRetry++; if (!m_controller->available()) { m_initRetry = 0; return; } if (m_initRetry < 10) { qCDebug(dcZigbeeNetwork()) << "Retry to initialize network" << m_initRetry << "/ 10"; runNetworkInitProcess(); } else { qCWarning(dcZigbeeNetwork()) << "Failed to read device state after 10 attempts. Giving up"; m_controller->reconnectConrtroller(); } return; } m_initRetry = 0; // Read all network parameters qCDebug(dcZigbeeNetwork()) << "Start reading controller network parameters..."; ZigbeeInterfaceDeconzReply *reply = m_controller->readNetworkParameters(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not read network parameters during network start up." << reply->statusCode(); m_initRetry++; if (m_initRetry < 3) { qCDebug(dcZigbeeNetwork()) << "Retry to read network parameters" << m_initRetry << "/ 3"; runNetworkInitProcess(); } else { qCWarning(dcZigbeeNetwork()) << "Failed to read network parameters after 3 attempts. Giving up"; m_controller->reconnectConrtroller(); } return; } qCDebug(dcZigbeeNetwork()) << "Reading network parameters finished successfully."; qCDebug(dcZigbeeNetwork()) << m_controller->networkConfiguration(); m_protocolVersion = QString("%1.%2").arg(m_controller->networkConfiguration().protocolVersion >> 8 & 0xFF) .arg(m_controller->networkConfiguration().protocolVersion & 0xFF); qCDebug(dcZigbeeNetwork()) << "Controller API protocol version" << ZigbeeUtils::convertUint16ToHexString(m_controller->networkConfiguration().protocolVersion) << m_protocolVersion; // Try read firmware version (changed with firmware 0x26680700) qCDebug(dcZigbeeNetwork()) << "Request current firmware version..."; ZigbeeInterfaceDeconzReply *reply = m_controller->requestVersion(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ QString firmwareVersion; if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Request" << reply->command() << "finished with error" << reply->statusCode(); } else { // Note: version is an uint32 value, little endian, but we can read the individual bytes in reversed order qCDebug(dcZigbeeNetwork()) << "Version request finished successfully" << ZigbeeUtils::convertByteArrayToHexString(reply->responseData()); quint8 majorVersion = static_cast(reply->responseData().at(3)); quint8 minorVersion = static_cast(reply->responseData().at(2)); Deconz::Platform platform = static_cast(reply->responseData().at(1)); quint8 patchVersion = static_cast(reply->responseData().at(0)); QString platformString; switch (platform) { case Deconz::PlatformConbeeRaspbee: platformString = "RaspBee"; break; case Deconz::PlatformConbeeII: platformString = "ConBee II"; break; default: platformString = "N/A"; } QString versionString = QString("0x%1%2%3%4").arg(majorVersion, 2, 16, QChar('0')) .arg(minorVersion, 2, 16, QChar('0')) .arg(platform, 2, 16, QChar('0')) .arg(patchVersion, 2, 16, QChar('0')); m_firmwareVersion = QString("%1.%2 - %3 (%4)").arg(majorVersion).arg(minorVersion).arg(platformString).arg(QString(versionString)); qCDebug(dcZigbeeNetwork()) << "Firmware version" << firmwareVersion << platform << versionString; } if (!m_firmwareVersion.isEmpty()) { m_controller->setFirmwareVersion(m_firmwareVersion); } else { m_controller->setFirmwareVersion("N/A"); } qCDebug(dcZigbeeNetwork()) << "Reading current network state"; ZigbeeInterfaceDeconzReply *reply = m_controller->requestDeviceState(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not read device state during network start up. SQN:" << reply->sequenceNumber() << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Reading current network state finished successfully." << "SQN:" << reply->sequenceNumber(); QDataStream stream(reply->responseData()); stream.setByteOrder(QDataStream::LittleEndian); quint8 deviceStateFlag = 0; stream >> deviceStateFlag; DeconzDeviceState deviceState = m_controller->parseDeviceStateFlag(deviceStateFlag); qCDebug(dcZigbeeNetwork()) << deviceState; // Update the device state in the controller m_controller->processDeviceState(deviceState); if (m_createNewNetwork) { // Set offline // Write configurations // Set online // Read configurations // Create and initialize coordinator node // Done. Save network setCreateNetworkState(CreateNetworkStateStopNetwork); } else { // Get the network state and start the network if required if (m_controller->networkState() == Deconz::NetworkStateConnected) { qCDebug(dcZigbeeNetwork()) << "The network is already running."; m_initializing = false; setPermitJoiningState(false); // Set the permit joining timeout network configuration parameter QByteArray parameterData; QDataStream stream(¶meterData, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(0); ZigbeeInterfaceDeconzReply *reply = m_controller->requestWriteParameter(Deconz::ParameterPermitJoin, parameterData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Request" << reply->command() << "finished with error" << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Set permit join configuration request finished" << reply->statusCode(); setState(StateRunning); sendPendingRequests(); }); } else if (m_controller->networkState() == Deconz::NetworkStateOffline) { m_initializing = true; qCDebug(dcZigbeeNetwork()) << "The network is offline. Lets start it"; setCreateNetworkState(CreateNetworkStateStartNetwork); } else { // The network is not running yet, lets wait for the state changed } } }); }); }); }); } void ZigbeeNetworkDeconz::startNetworkInternally() { qCDebug(dcZigbeeNetwork()) << "Start zigbee network internally"; m_createNewNetwork = false; // Check if we have to create a pan ID and select the channel if (panId() == 0 || !m_coordinatorNode) { setPanId(ZigbeeUtils::generateRandomPanId()); qCDebug(dcZigbeeNetwork()) << "Generated new extended PAN ID" << panId() << ZigbeeUtils::convertUint16ToHexString(panId()); m_createNewNetwork = true; } m_initRetry = 0; runNetworkInitProcess(); } void ZigbeeNetworkDeconz::onControllerAvailableChanged(bool available) { if (!available) { qCWarning(dcZigbeeNetwork()) << "Hardware controller is not available any more."; setError(ErrorHardwareUnavailable); m_initializing = false; setPermitJoiningState(false); setState(StateOffline); } else { m_error = ErrorNoError; setPermitJoiningState(false); setState(StateStarting); qCDebug(dcZigbeeNetwork()) << "Hardware controller is now available."; startNetworkInternally(); } } void ZigbeeNetworkDeconz::onPollNetworkStateTimeout() { // Stop the timer and make the request m_pollNetworkStateTimer->stop(); switch (m_createState) { case CreateNetworkStateStopNetwork: { ZigbeeInterfaceDeconzReply *reply = m_controller->requestDeviceState(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not read device state during network start up." << reply->statusCode(); // FIXME: set an appropriate error return; } //qCDebug(dcZigbeeNetwork()) << "Read device state finished successfully"; QDataStream stream(reply->responseData()); stream.setByteOrder(QDataStream::LittleEndian); quint8 deviceStateFlag = 0; stream >> deviceStateFlag; // Update the device state in the controller m_controller->processDeviceState(m_controller->parseDeviceStateFlag(deviceStateFlag)); if (m_controller->networkState() == Deconz::NetworkStateOffline) { qCDebug(dcZigbeeNetwork()) << "Network stopped successfully for creation"; // The network is now offline, continue with the state machine in one second (some grace period after network shutdown) QTimer::singleShot(1000, this, [=](){ setCreateNetworkState(CreateNetworkStateWriteConfiguration); }); } else { // Not offline yet, continue poll m_pollNetworkStateTimer->start(); } }); break; } case CreateNetworkStateStartNetwork: { ZigbeeInterfaceDeconzReply *reply = m_controller->requestDeviceState(); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not read device state during network start up. SQN:" << reply->sequenceNumber() << reply->statusCode(); // FIXME: set an appropriate error return; } //qCDebug(dcZigbeeNetwork()) << "Read device state finished successfully. SQN:" << reply->sequenceNumber(); QDataStream stream(reply->responseData()); stream.setByteOrder(QDataStream::LittleEndian); quint8 deviceStateFlag = 0; stream >> deviceStateFlag; // Update the device state in the controller m_controller->processDeviceState(m_controller->parseDeviceStateFlag(deviceStateFlag)); if (m_controller->networkState() == Deconz::NetworkStateConnected) { // The network is now online, continue with the state machine setCreateNetworkState(CreateNetworkStateReadConfiguration); } else if (m_controller->networkState() == Deconz::NetworkStateOffline) { qCWarning(dcZigbeeNetwork()) << "Failed to start the network."; setCreateNetworkState(CreateNetworkStateIdle); setState(StateOffline); setError(ErrorZigbeeError); return; } else { // Not offline yet, continue poll m_pollNetworkStateTimer->start(); } }); break; } default: break; } } void ZigbeeNetworkDeconz::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm) { ZigbeeNetworkReply *reply = m_pendingReplies.value(confirm.requestId); if (!reply) { qCWarning(dcZigbeeNetwork()) << "Received confirmation but could not find any reply. Ignoring the confirmation"; return; } setReplyResponseError(reply, confirm.zigbeeStatusCode); } void ZigbeeNetworkDeconz::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &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 ZigbeeNetworkDeconz::startNetwork() { loadNetwork(); if (!m_controller->enable(serialPortName(), serialBaudrate())) { setPermitJoiningState(false); setState(StateOffline); setCreateNetworkState(CreateNetworkStateIdle); setError(ErrorHardwareUnavailable); return; } setPermitJoiningState(false); m_initializing = true; // Note: wait for the controller available signal and start the initialization there } void ZigbeeNetworkDeconz::stopNetwork() { ZigbeeInterfaceDeconzReply *reply = m_controller->requestChangeNetworkState(Deconz::NetworkStateOffline); setState(StateStopping); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not leave network. SQN:" << reply->sequenceNumber() << reply->statusCode(); // FIXME: set an appropriate error return; } qCDebug(dcZigbeeNetwork()) << "Network left successfully. SQN:" << reply->sequenceNumber(); setState(StateOffline); m_controller->disable(); }); } void ZigbeeNetworkDeconz::reset() { qCDebug(dcZigbeeNetwork()) << "Reboot the controller. The stack will perform a restart."; m_controller->rebootController(); } void ZigbeeNetworkDeconz::factoryResetNetwork() { qCDebug(dcZigbeeNetwork()) << "Factory reset network and forget all information. This cannot be undone."; m_controller->disable(); clearSettings(); setState(StateUninitialized); qCDebug(dcZigbeeNetwork()) << "The factory reset is finished. Start restart with a fresh network."; startNetwork(); } void ZigbeeNetworkDeconz::destroyNetwork() { qCDebug(dcZigbeeNetwork()) << "Destroy network and delete the database"; m_controller->disable(); clearSettings(); }