From 64496548202e5315be9b7e3dd43f8a9710f6fa83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20St=C3=BCrz?= Date: Sun, 5 Apr 2020 16:13:30 +0200 Subject: [PATCH] Introduce channel mask class and continue deCONZ UART protocol --- libnymea-zigbee/deconz/interface/deconz.h | 11 + .../interface/zigbeeinterfacedeconzreply.cpp | 11 + .../interface/zigbeeinterfacedeconzreply.h | 5 + .../deconz/zigbeebridgecontrollerdeconz.cpp | 200 ++++++++++++++++-- .../deconz/zigbeebridgecontrollerdeconz.h | 49 +++-- .../deconz/zigbeenetworkdeconz.cpp | 176 +++++++++------ libnymea-zigbee/deconz/zigbeenetworkdeconz.h | 5 +- libnymea-zigbee/libnymea-zigbee.pro | 2 + libnymea-zigbee/zigbee.h | 38 ++-- libnymea-zigbee/zigbeechannelmask.cpp | 106 ++++++++++ libnymea-zigbee/zigbeechannelmask.h | 71 +++++++ libnymea-zigbee/zigbeenetwork.cpp | 14 ++ libnymea-zigbee/zigbeenetwork.h | 6 + libnymea-zigbee/zigbeenode.cpp | 1 - 14 files changed, 578 insertions(+), 117 deletions(-) create mode 100644 libnymea-zigbee/zigbeechannelmask.cpp create mode 100644 libnymea-zigbee/zigbeechannelmask.h diff --git a/libnymea-zigbee/deconz/interface/deconz.h b/libnymea-zigbee/deconz/interface/deconz.h index 6f799ac..3cf9485 100644 --- a/libnymea-zigbee/deconz/interface/deconz.h +++ b/libnymea-zigbee/deconz/interface/deconz.h @@ -93,6 +93,16 @@ public: }; Q_ENUM(NodeType) + enum SourceAddressMode { + SourceAddressModeNone = 0x00, + SourceAddressModeShortSourceAddress = 0x01, + SourceAddressModeAddLastHoppAddress = 0x02, // since 0x0108 + SourceAddressModeIeeeSourceAddress = 0x03, + SourceAddressModeShortAndIeeeSourceAddress = 0x04 // since 0x010B + }; + Q_ENUM(SourceAddressMode) + + enum SecurityMode { SecurityModeNoSecurity = 0x00, SecurityModePreconfiguredNetworkKey = 0x01, @@ -109,4 +119,5 @@ public: }; + #endif // DECONZ_H diff --git a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp index 3469fd5..38daea3 100644 --- a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp +++ b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp @@ -47,6 +47,17 @@ Deconz::StatusCode ZigbeeInterfaceDeconzReply::statusCode() const return m_statusCode; } +bool ZigbeeInterfaceDeconzReply::aborted() const +{ + return m_aborted; +} + +void ZigbeeInterfaceDeconzReply::abort() +{ + m_aborted = true; + emit finished(); +} + ZigbeeInterfaceDeconzReply::ZigbeeInterfaceDeconzReply(Deconz::Command command, quint8 sequenceNumber, QObject *parent) : QObject(parent), m_command(command), diff --git a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h index 2a67a5f..1b267e9 100644 --- a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h +++ b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h @@ -47,9 +47,14 @@ public: // Response content Deconz::StatusCode statusCode() const; + bool aborted() const; + void abort(); + private: explicit ZigbeeInterfaceDeconzReply(Deconz::Command command, quint8 sequenceNumber, QObject *parent = nullptr); + bool m_aborted = false; + // Request content Deconz::Command m_command; quint8 m_sequenceNumber = 0; diff --git a/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp index c1fc5d6..ef00e9f 100644 --- a/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp +++ b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp @@ -41,7 +41,7 @@ ZigbeeBridgeControllerDeconz::ZigbeeBridgeControllerDeconz(QObject *parent) : m_watchdogTimer = new QTimer(this); m_watchdogTimer->setSingleShot(false); m_watchdogTimer->setInterval(m_watchdogResetTimout * 1000); // Set the watchdog to 85 seconds, reset every 60 s - connect(m_watchdogTimer, &QTimer::timeout, this, &ZigbeeBridgeControllerDeconz::onWatchdogTimerTimeout); + connect(m_watchdogTimer, &QTimer::timeout, this, &ZigbeeBridgeControllerDeconz::resetControllerWatchdog); } ZigbeeBridgeControllerDeconz::~ZigbeeBridgeControllerDeconz() @@ -49,6 +49,17 @@ ZigbeeBridgeControllerDeconz::~ZigbeeBridgeControllerDeconz() qCDebug(dcZigbeeController()) << "Destroy controller"; } +DeconzNetworkConfiguration ZigbeeBridgeControllerDeconz::networkConfiguration() const +{ + return m_networkConfiguration; +} + +void ZigbeeBridgeControllerDeconz::setFirmwareVersionString(const QString &firmwareVersion) +{ + m_firmwareVersion = firmwareVersion; + emit firmwareVersionChanged(m_firmwareVersion); +} + ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestVersion() { quint8 sequenceNumber = generateSequenceNumber(); @@ -120,14 +131,15 @@ ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestWriteParameter( quint8 sequenceNumber = generateSequenceNumber(); qCDebug(dcZigbeeController()) << "Request write parameter. SQN:" << sequenceNumber << parameter << ZigbeeUtils::convertByteArrayToHexString(data); + quint16 payloadLength = static_cast(1 + data.length()); QByteArray message; QDataStream stream(&message, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian); stream << static_cast(Deconz::CommandWriteParameter); stream << static_cast(sequenceNumber); stream << static_cast(0); // Reserverd - stream << static_cast(7 + 1 + data.length()); // Frame length 7 + 1 parameter + payload length - stream << static_cast(1 + data.length()); // 1 parameter + payload length + stream << static_cast(7 + payloadLength); // Frame length 7 + payload length + stream << static_cast(payloadLength); // 1 parameter + parameter data length stream << static_cast(parameter); for (int i = 0; i < data.length(); i++) { stream << static_cast(data.at(i)); @@ -157,6 +169,31 @@ ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestChangeNetworkSt return createReply(Deconz::CommandChangeNetworkState, sequenceNumber, this); } +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestReadReceivedDataIndication(Deconz::SourceAddressMode sourceAddressMode) +{ + quint8 sequenceNumber = generateSequenceNumber(); + qCDebug(dcZigbeeController()) << "Request read received data indication. SQN:" << sequenceNumber << ZigbeeUtils::convertByteToHexString(sourceAddressMode); + + quint16 payloadLength = 0; + if (sourceAddressMode != Deconz::SourceAddressModeNone) { + payloadLength = 1; + } + + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Deconz::CommandApsDataIndication); + stream << static_cast(sequenceNumber); + stream << static_cast(0); // Reserverd + stream << static_cast(7 + payloadLength); // Frame length + payload length + stream << static_cast(payloadLength); // payload length + if (payloadLength > 0) + stream << static_cast(sourceAddressMode); + + m_interface->sendPackage(message); + + return createReply(Deconz::CommandApsDataIndication, sequenceNumber, this); +} quint8 ZigbeeBridgeControllerDeconz::generateSequenceNumber() { @@ -464,6 +501,7 @@ ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::readNetworkParameters( m_watchdogTimer->stop(); // Finished reading all parameters. Finish the independent reply in order to indicate the process has finished + emit networkConfigurationParameterChanged(m_networkConfiguration); readNetworkParametersReply->m_statusCode = Deconz::StatusCodeSuccess; readNetworkParametersReply->finished(); return; @@ -491,10 +529,11 @@ ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::readNetworkParameters( // Note: this value describes how much seconds are left until the watchdog triggers. Reset it right the way if (watchdogTimeout < 15) { - onWatchdogTimerTimeout(); + resetControllerWatchdog(); } // Finished reading all parameters. Finish the independent reply in order to indicate the process has finished + emit networkConfigurationParameterChanged(m_networkConfiguration); readNetworkParametersReply->m_statusCode = Deconz::StatusCodeSuccess; readNetworkParametersReply->finished(); }); @@ -515,20 +554,121 @@ ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::readNetworkParameters( return readNetworkParametersReply; } -ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::resetWatchdog() +DeconzDeviceState ZigbeeBridgeControllerDeconz::parseDeviceStateFlag(quint8 deviceStateFlag) { - QByteArray parameterData; - QDataStream stream(¶meterData, QIODevice::WriteOnly); - stream.setByteOrder(QDataStream::LittleEndian); - stream << m_watchdogTimeout; - return requestWriteParameter(Deconz::ParameterWatchdogTtl, parameterData); + DeconzDeviceState state; + state.networkState = static_cast(deviceStateFlag & 0x03); + state.aspDataConfirm = (deviceStateFlag & 0x04); + state.aspDataIndication = (deviceStateFlag & 0x08); + state.configurationChanged = (deviceStateFlag & 0x10); + state.aspDataRequestFreeSlots = (deviceStateFlag & 0x20); + return state; +} + +void ZigbeeBridgeControllerDeconz::processDeviceState(DeconzDeviceState deviceState) +{ + qCDebug(dcZigbeeController()) << "Device state changed notification:" << deviceState.networkState + << "ASPDE-DATA.confirm:" << deviceState.aspDataConfirm + << "ASPDE-DATA.indication:" << deviceState.aspDataIndication + << "configuration changed:" << deviceState.configurationChanged + << "ASPDE-DATA.request free slots:" << deviceState.aspDataRequestFreeSlots; + + // Check if we have to fech new data + if (deviceState.aspDataConfirm) { + ZigbeeInterfaceDeconzReply *reply = requestReadReceivedDataIndication(); + connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply](){ + if (reply->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Could not read data indication." << reply->statusCode(); + // FIXME: set an appropriate error + return; + } + + // ASP data indication received + + QDataStream stream(reply->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 deviceStateFlag = 0; quint8 destinationAddressModeFlag = 0; + quint16 destinationShortAddress = 0; quint64 destinationIeeeAddress = 0; quint8 destinationEndpoint = 0; + quint8 sourceAddressModeFlag = 0; quint16 sourceShortAddress = 0; quint64 sourceIeeeAddress = 0; quint8 sourceEndpoint = 0; + quint16 profileId = 0; quint16 clusterId = 0; quint16 asduLength = 0; QByteArray asdu; quint8 reserved = 0; + quint8 lqi = 0; qint8 rssi = 0; + + stream >> payloadLenght >> deviceStateFlag >> destinationAddressModeFlag; + Zigbee::DestinationAddressMode destinationAddressMode = static_cast(destinationAddressModeFlag); + if (destinationAddressMode == Zigbee::DestinationAddressModeGroup || destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) + stream >> destinationShortAddress; + + if (destinationAddressMode == Zigbee::DestinationAddressModeUnicastIeee) + stream >> destinationIeeeAddress; + + stream >> destinationEndpoint >> sourceAddressModeFlag; + + Zigbee::SourceAddressMode sourceAddressMode = static_cast(sourceAddressModeFlag); + if (sourceAddressMode == Zigbee::SourceAddressModeShortAddress || sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) + stream >> sourceShortAddress; + + if (sourceAddressMode == Zigbee::SourceAddressModeIeeeAddress || sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) + stream >> sourceIeeeAddress; + + stream >> sourceEndpoint >> profileId >> clusterId >> asduLength; + + // Fill asdu data + for (int i = 0; i < asduLength; i++) { + quint8 byte = 0; + stream >> byte; + asdu.append(static_cast(byte)); + } + + stream >> reserved >> reserved >> lqi >> reserved >> reserved >> reserved >> reserved >> rssi; + + + qCDebug(dcZigbeeController()) << "Data indication received:"; + qCDebug(dcZigbeeController()) << " Destination address mode:" << destinationAddressMode; + if (destinationAddressMode == Zigbee::DestinationAddressModeGroup) + qCDebug(dcZigbeeController()) << " Destination address (group):" << ZigbeeUtils::convertUint16ToHexString(destinationShortAddress); + + if (destinationAddressMode == Zigbee::DestinationAddressModeShortAddress) + qCDebug(dcZigbeeController()) << " Destination short address:" << ZigbeeUtils::convertUint16ToHexString(destinationShortAddress); + + if (destinationAddressMode == Zigbee::DestinationAddressModeUnicastIeee) + qCDebug(dcZigbeeController()) << " Destination IEEE address:" << ZigbeeAddress(destinationIeeeAddress).toString(); + + qCDebug(dcZigbeeController()) << " Destination endpoint" << ZigbeeUtils::convertByteToHexString(destinationEndpoint); + + qCDebug(dcZigbeeController()) << " Source address mode:" << sourceAddressMode; + if (sourceAddressMode == Zigbee::SourceAddressModeShortAddress || sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) + qCDebug(dcZigbeeController()) << " Source address:" << ZigbeeUtils::convertUint16ToHexString(sourceShortAddress); + + if (sourceAddressMode == Zigbee::SourceAddressModeIeeeAddress || sourceAddressMode == Zigbee::SourceAddressModeShortAndIeeeAddress) + qCDebug(dcZigbeeController()) << " Source IEEE address:" << ZigbeeAddress(sourceIeeeAddress).toString(); + + + qCDebug(dcZigbeeController()) << " Source endpoint:" << ZigbeeUtils::convertByteToHexString(sourceEndpoint); + qCDebug(dcZigbeeController()) << " Profile:" << static_cast(profileId); + qCDebug(dcZigbeeController()) << " Cluster:" << static_cast(clusterId); + qCDebug(dcZigbeeController()) << " ASDU:" << ZigbeeUtils::convertByteArrayToHexString(asdu); + qCDebug(dcZigbeeController()) << " LQI:" << lqi; + qCDebug(dcZigbeeController()) << " RSSI:" << rssi << "dBm"; + + processDeviceState(parseDeviceStateFlag(deviceStateFlag)); + }); + } + + } void ZigbeeBridgeControllerDeconz::onInterfaceAvailableChanged(bool available) { if (available) { + // FIXME: only start if the protocol version is >= 0x0108 m_watchdogTimer->start(); } else { + // Clean up any pending replies + foreach (quint8 id, m_pendingReplies.keys()) { + ZigbeeInterfaceDeconzReply *reply = m_pendingReplies.take(id); + reply->abort(); + } + m_watchdogTimer->stop(); } @@ -539,20 +679,20 @@ void ZigbeeBridgeControllerDeconz::onInterfacePackageReceived(const QByteArray & { QDataStream stream(package); stream.setByteOrder(QDataStream::LittleEndian); - quint8 command = 0; quint8 sequenceNumber = 0; quint8 status = 0; quint16 frameLength = 0; - stream >> command >> sequenceNumber >> status >> frameLength; + quint8 commandInt = 0; quint8 sequenceNumber = 0; quint8 statusInt = 0; quint16 frameLength = 0; + stream >> commandInt >> sequenceNumber >> statusInt >> frameLength; - qCDebug(dcZigbeeController()) << "Interface message received" - << static_cast(command) - << "SQN:" << sequenceNumber - << static_cast(status) - << "Frame length:" << frameLength; + QByteArray data = package.right(package.length() - 5); + Deconz::Command command = static_cast(commandInt); + Deconz::StatusCode status = static_cast(statusInt); + qCDebug(dcZigbeeController()) << "Interface message received" << command << "SQN:" << sequenceNumber + << status << "Frame length:" << frameLength << ZigbeeUtils::convertByteArrayToHexString(data); // Check if this is an interface response for a pending reply if (m_pendingReplies.contains(sequenceNumber) && m_pendingReplies.value(sequenceNumber)->command() == command) { ZigbeeInterfaceDeconzReply *reply = m_pendingReplies.take(sequenceNumber); - reply->m_responseData = package.right(package.length() - 5); - reply->m_statusCode = static_cast(status); + reply->m_responseData = data; + reply->m_statusCode = status; reply->finished(); return; } @@ -563,12 +703,28 @@ void ZigbeeBridgeControllerDeconz::onInterfacePackageReceived(const QByteArray & m_sequenceNumber = sequenceNumber; // No request for this data, lets check which notification and process the data + switch (command) { + case Deconz::CommandDeviceStateChanged: { + quint8 deviceStateFlag = 0; + stream >> deviceStateFlag; + processDeviceState(parseDeviceStateFlag(deviceStateFlag)); + break; + } + default: + qCWarning(dcZigbeeController()) << "Unhandled interface package received" << command << "SQN:" << sequenceNumber + << status << "Frame length:" << frameLength << ZigbeeUtils::convertByteArrayToHexString(data); + break; + } + } -void ZigbeeBridgeControllerDeconz::onWatchdogTimerTimeout() +void ZigbeeBridgeControllerDeconz::resetControllerWatchdog() { - qCDebug(dcZigbeeController()) << "Reset application watchdog on the deCONZ controller"; - ZigbeeInterfaceDeconzReply *reply = resetWatchdog(); + QByteArray parameterData; + QDataStream stream(¶meterData, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << m_watchdogTimeout; + ZigbeeInterfaceDeconzReply *reply = requestWriteParameter(Deconz::ParameterWatchdogTtl, parameterData); connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [reply](){ if (reply->statusCode() != Deconz::StatusCodeSuccess) { qCWarning(dcZigbeeController()) << "Could not reset the application watchdog on the deCONZ controller." << reply->statusCode(); diff --git a/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h index 456cade..cf99f5b 100644 --- a/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h +++ b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h @@ -41,24 +41,37 @@ #include "interface/zigbeeinterfacedeconz.h" #include "interface/zigbeeinterfacedeconzreply.h" +// This struct describes the current deCONZ network configuration parameters typedef struct DeconzNetworkConfiguration { ZigbeeAddress ieeeAddress; // R - quint16 panId; // R - quint16 shortAddress; // R - quint64 extendedPanId; // R - Deconz::NodeType nodeType; // RW - quint32 channelMask; // RW - quint64 apsExtendedPanId; // RW + quint16 panId = 0; // R + quint16 shortAddress = 0; // R + quint64 extendedPanId = 0; // R + Deconz::NodeType nodeType = Deconz::NodeTypeCoordinator; // RW + quint32 channelMask = 0; // RW + quint64 apsExtendedPanId = 0; // RW ZigbeeAddress trustCenterAddress; // RW - Deconz::SecurityMode securityMode; // RW + Deconz::SecurityMode securityMode = Deconz::SecurityModeNoMasterButTrustCenterKey; // RW ZigbeeNetworkKey networkKey; // RW - quint8 currentChannel; // R - quint16 protocolVersion; // R - quint8 networkUpdateId; // RW - quint32 watchdogTimeout; // RW + quint8 currentChannel = 0; // R + quint16 protocolVersion = 0; // R + quint8 networkUpdateId = 0; // RW + quint32 watchdogTimeout = 85; // RW } DeconzNetworkConfiguration; + +// This struct describes the deCONZ device state +typedef struct DeconzDeviceState { + Deconz::NetworkState networkState = Deconz::NetworkStateOffline; + bool aspDataConfirm = false; + bool aspDataIndication = false; + bool configurationChanged = false; + bool aspDataRequestFreeSlots = false; +} DeconzDeviceState; + + + class ZigbeeBridgeControllerDeconz : public ZigbeeBridgeController { Q_OBJECT @@ -69,11 +82,16 @@ public: explicit ZigbeeBridgeControllerDeconz(QObject *parent = nullptr); ~ZigbeeBridgeControllerDeconz() override; + DeconzNetworkConfiguration networkConfiguration() const; + void setFirmwareVersionString(const QString &firmwareVersion); + ZigbeeInterfaceDeconzReply *requestVersion(); ZigbeeInterfaceDeconzReply *requestDeviceState(); ZigbeeInterfaceDeconzReply *requestReadParameter(Deconz::Parameter parameter); ZigbeeInterfaceDeconzReply *requestWriteParameter(Deconz::Parameter parameter, const QByteArray &data); - ZigbeeInterfaceDeconzReply *requestStartJoinNetwork(); + ZigbeeInterfaceDeconzReply *requestChangeNetworkState(Deconz::NetworkState networkState); + ZigbeeInterfaceDeconzReply *requestReadReceivedDataIndication(Deconz::SourceAddressMode sourceAddressMode = Deconz::SourceAddressModeShortSourceAddress); + private: ZigbeeInterfaceDeconz *m_interface = nullptr; @@ -92,15 +110,18 @@ private: // The data can be fetched from m_networkConfiguration on success. ZigbeeInterfaceDeconzReply *readNetworkParameters(); - ZigbeeInterfaceDeconzReply *resetWatchdog(); + DeconzDeviceState parseDeviceStateFlag(quint8 deviceStateFlag); + + void processDeviceState(DeconzDeviceState deviceState); signals: + void networkConfigurationParameterChanged(const DeconzNetworkConfiguration &networkConfiguration); private slots: void onInterfaceAvailableChanged(bool available); void onInterfacePackageReceived(const QByteArray &package); - void onWatchdogTimerTimeout(); + void resetControllerWatchdog(); public slots: bool enable(const QString &serialPort, qint32 baudrate); diff --git a/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp b/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp index f56e32b..9a3d607 100644 --- a/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp +++ b/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp @@ -29,6 +29,8 @@ #include "loggingcategory.h" #include "zigbeeutils.h" +#include + ZigbeeNetworkDeconz::ZigbeeNetworkDeconz(QObject *parent) : ZigbeeNetwork(parent) { @@ -58,65 +60,117 @@ void ZigbeeNetworkDeconz::setPermitJoiningInternal(bool permitJoining) Q_UNUSED(permitJoining) } +void ZigbeeNetworkDeconz::startNetworkInternally() +{ + qCDebug(dcZigbeeNetwork()) << "Start network internally"; + + // Check if we have to create a pan ID and select the channel + if (extendedPanId() == 0) { + m_createNewNetwork = true; + setExtendedPanId(ZigbeeUtils::generateRandomPanId()); + qCDebug(dcZigbeeNetwork()) << "Created new PAN ID:" << extendedPanId(); + } + + if (securityConfiguration().networkKey().isNull()) { + m_createNewNetwork = true; + qCDebug(dcZigbeeNetwork()) << "Create a new network key"; + ZigbeeNetworkKey key = ZigbeeNetworkKey::generateKey(); + m_securityConfiguration.setNetworkKey(key); + } + + qCDebug(dcZigbeeNetwork()) << "Using" << securityConfiguration().networkKey() << "network link key"; + qCDebug(dcZigbeeNetwork()) << "Using" << securityConfiguration().globalTrustCenterLinkKey() << "global trust center link key"; + + + // - Read the firmware version + // - Read the network configuration parameters + // - Read the network state + + // - If network running and we don't have configurations, write them + // - If network running and configurations match, we are done + + // Read the firmware version + ZigbeeInterfaceDeconzReply *reply = m_controller->requestVersion(); + 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()) << "Version request finished" << reply->statusCode() << ZigbeeUtils::convertByteArrayToHexString(reply->responseData()); + // Note: version is an uint32 value, little endian, but we can read the individual bytes in reversed order + 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)); + QString firmwareVersion = QString("%1.%2").arg(majorVersion).arg(minorVersion); + qCDebug(dcZigbeeNetwork()) << "Firmware version" << firmwareVersion << platform; + + // Read all network parameters + ZigbeeInterfaceDeconzReply *reply = m_controller->readNetworkParameters(); + connect(reply, &ZigbeeInterfaceDeconzReply::finished, this, [this, reply, firmwareVersion](){ + if (reply->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Could not read network parameters during network start up." << reply->statusCode(); + // FIXME: set an appropriate error + return; + } + + qCDebug(dcZigbeeNetwork()) << "Reading network parameters finished successfully."; + QString protocolVersion = QString("%1.%2").arg(m_controller->networkConfiguration().protocolVersion & 0xFF00) + .arg(m_controller->networkConfiguration().protocolVersion & 0x00FF); + + m_controller->setFirmwareVersionString(QString("%1 - %2").arg(firmwareVersion).arg(protocolVersion)); + + 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()); + + + }); + + + + if (m_createNewNetwork) { + // Write the configurations which need to be changed + + + // Initialize coordinator node + + + } else { + // Get the network state and start the network if required + + + + } + + + + }); + }); +} + void ZigbeeNetworkDeconz::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); - - // FIXME: do this in the startig state machine - ZigbeeInterfaceDeconzReply *reply = m_controller->requestVersion(); - 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()) << "Version request finished" << reply->statusCode() << ZigbeeUtils::convertByteArrayToHexString(reply->responseData()); - // Note: version is an uint32 value, little endian, but we can read the individual bytes in reversed order - 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)); - QString firmwareVersion = QString("%1.%2").arg(majorVersion).arg(minorVersion); - qCDebug(dcZigbeeNetwork()) << "Firmware version" << firmwareVersion << platform; - - // 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." << reply->statusCode(); - // FIXME: set an appropriate error - return; - } - - qCDebug(dcZigbeeNetwork()) << "Reading network parameters finished successfully."; - - - - - }); - }); + startNetworkInternally(); } } @@ -124,23 +178,6 @@ void ZigbeeNetworkDeconz::startNetwork() { loadNetwork(); - // 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); @@ -157,15 +194,26 @@ void ZigbeeNetworkDeconz::startNetwork() 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." << reply->statusCode(); + // FIXME: set an appropriate error + return; + } + qCDebug(dcZigbeeNetwork()) << "Network left successfully"; + setState(StateOffline); + }); } void ZigbeeNetworkDeconz::reset() { - + // TODO } void ZigbeeNetworkDeconz::factoryResetNetwork() { - + // Wipe settings, and reconfigure network } diff --git a/libnymea-zigbee/deconz/zigbeenetworkdeconz.h b/libnymea-zigbee/deconz/zigbeenetworkdeconz.h index 606dd52..743a465 100644 --- a/libnymea-zigbee/deconz/zigbeenetworkdeconz.h +++ b/libnymea-zigbee/deconz/zigbeenetworkdeconz.h @@ -30,7 +30,7 @@ #include #include "zigbeenetwork.h" - +#include "zigbeechannelmask.h" #include "zigbeebridgecontrollerdeconz.h" class ZigbeeNetworkDeconz : public ZigbeeNetwork @@ -44,11 +44,14 @@ public: private: ZigbeeBridgeControllerDeconz *m_controller = nullptr; bool m_networkRunning = false; + bool m_createNewNetwork = false; protected: ZigbeeNode *createNode(QObject *parent) override; void setPermitJoiningInternal(bool permitJoining) override; + void startNetworkInternally(); + private slots: void onControllerAvailableChanged(bool available); diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index 5558592..9ed1937 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -17,6 +17,7 @@ SOURCES += \ nxp/zigbeenodeendpointnxp.cpp \ nxp/zigbeenodenxp.cpp \ zigbeebridgecontroller.cpp \ + zigbeechannelmask.cpp \ zigbeecluster.cpp \ zigbeeclusterattribute.cpp \ zigbeemanufacturer.cpp \ @@ -47,6 +48,7 @@ HEADERS += \ nxp/zigbeenodeendpointnxp.h \ nxp/zigbeenodenxp.h \ zigbeebridgecontroller.h \ + zigbeechannelmask.h \ zigbeecluster.h \ zigbeeclusterattribute.h \ zigbeemanufacturer.h \ diff --git a/libnymea-zigbee/zigbee.h b/libnymea-zigbee/zigbee.h index 10cdf2c..a15faea 100644 --- a/libnymea-zigbee/zigbee.h +++ b/libnymea-zigbee/zigbee.h @@ -53,21 +53,22 @@ public: Q_ENUM(ZigbeeProfile) enum ZigbeeChannel { - ZigbeeChannel11, - ZigbeeChannel12, - ZigbeeChannel13, - ZigbeeChannel14, - ZigbeeChannel15, - ZigbeeChannel16, - ZigbeeChannel17, - ZigbeeChannel18, - ZigbeeChannel19, - ZigbeeChannel20, - ZigbeeChannel21, - ZigbeeChannel22, - ZigbeeChannel23, - ZigbeeChannel24, - ZigbeeChannel25 + ZigbeeChannel11 = 0x00000800, + ZigbeeChannel12 = 0x00001000, + ZigbeeChannel13 = 0x00002000, + ZigbeeChannel14 = 0x00004000, + ZigbeeChannel15 = 0x00008000, + ZigbeeChannel16 = 0x00010000, + ZigbeeChannel17 = 0x00020000, + ZigbeeChannel18 = 0x00040000, + ZigbeeChannel19 = 0x00080000, + ZigbeeChannel20 = 0x00100000, + ZigbeeChannel21 = 0x00200000, + ZigbeeChannel22 = 0x00400000, + ZigbeeChannel23 = 0x00800000, + ZigbeeChannel24 = 0x01000000, + ZigbeeChannel25 = 0x02000000, + ZigbeeChannel26 = 0x04000000 }; Q_ENUM(ZigbeeChannel) Q_DECLARE_FLAGS(ZigbeeChannels, ZigbeeChannel) @@ -540,6 +541,13 @@ public: }; Q_ENUM(DestinationAddressMode) + enum SourceAddressMode { + SourceAddressModeShortAddress = 0x02, + SourceAddressModeIeeeAddress = 0x03, + SourceAddressModeShortAndIeeeAddress = 0x04 + }; + Q_ENUM(SourceAddressMode) + enum ZigbeeZclStatus { }; diff --git a/libnymea-zigbee/zigbeechannelmask.cpp b/libnymea-zigbee/zigbeechannelmask.cpp new file mode 100644 index 0000000..5ea9a07 --- /dev/null +++ b/libnymea-zigbee/zigbeechannelmask.cpp @@ -0,0 +1,106 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeechannelmask.h" +#include "zigbeeutils.h" + +ZigbeeChannelMask::ZigbeeChannelMask() +{ + +} + +ZigbeeChannelMask::ZigbeeChannelMask(quint32 channelMask) : + m_channelMask(channelMask) +{ + +} + +ZigbeeChannelMask::ZigbeeChannelMask(Zigbee::ZigbeeChannels channels) +{ + m_channelMask = static_cast(channels); +} + +quint32 ZigbeeChannelMask::toUInt32() const +{ + return m_channelMask; +} + +Zigbee::ZigbeeChannels ZigbeeChannelMask::channels() const +{ + return static_cast(m_channelMask); +} + +bool ZigbeeChannelMask::isSet(Zigbee::ZigbeeChannel channel) const +{ + return channels().testFlag(channel); +} + +void ZigbeeChannelMask::setChannel(Zigbee::ZigbeeChannel channel) +{ + // Set channel bit + m_channelMask |= 1 << channel; +} + +void ZigbeeChannelMask::unsetChannel(Zigbee::ZigbeeChannel channel) +{ + // Clear channel bit + m_channelMask &= ~(1 << channel); +} + +ZigbeeChannelMask &ZigbeeChannelMask::operator=(const ZigbeeChannelMask &other) +{ + m_channelMask = other.toUInt32(); + return *this; +} + +bool ZigbeeChannelMask::operator==(const ZigbeeChannelMask &other) const +{ + return m_channelMask == other.toUInt32(); +} + +bool ZigbeeChannelMask::operator!=(const ZigbeeChannelMask &other) const +{ + return !operator==(other); +} + +QDebug operator<<(QDebug debug, const ZigbeeChannelMask &channelMaks) +{ + debug.nospace() << "ChannelMask(" << ZigbeeUtils::convertUint32ToHexString(channelMaks.toUInt32()); + debug.nospace() << ", ["; + for (int i = 11; i <= 25; i++) { + if (channelMaks.isSet(static_cast(i))) { + if (i < 25) { + debug.nospace() << i << ", "; + } else { + debug.nospace() << i; + } + } + } + + debug.nospace() << "])"; + return debug.space(); +} diff --git a/libnymea-zigbee/zigbeechannelmask.h b/libnymea-zigbee/zigbeechannelmask.h new file mode 100644 index 0000000..a961fd3 --- /dev/null +++ b/libnymea-zigbee/zigbeechannelmask.h @@ -0,0 +1,71 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 +* +* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ + +#ifndef ZIGBEECHANNELMASK_H +#define ZIGBEECHANNELMASK_H + +#include +#include + +#include "zigbee.h" + +class ZigbeeChannelMask +{ + Q_GADGET + +public: + enum ChannelConfiguration { + ChannelConfigurationNoChannel = 0x00000000, + ChannelConfigurationPrimaryLightLink = 0x02108800, + ChannelConfigurationAllChannels = 0x07fff800 + }; + Q_ENUM(ChannelConfiguration) + + ZigbeeChannelMask(); + ZigbeeChannelMask(quint32 channelMask); + ZigbeeChannelMask(Zigbee::ZigbeeChannels channels); + + quint32 toUInt32() const; + + Zigbee::ZigbeeChannels channels() const; + + bool isSet(Zigbee::ZigbeeChannel channel) const; + void setChannel(Zigbee::ZigbeeChannel channel); + void unsetChannel(Zigbee::ZigbeeChannel channel); + + ZigbeeChannelMask &operator=(const ZigbeeChannelMask &other); + bool operator==(const ZigbeeChannelMask &other) const; + bool operator!=(const ZigbeeChannelMask &other) const; + +private: + quint32 m_channelMask = 0; + +}; + +QDebug operator<<(QDebug debug, const ZigbeeChannelMask &channelMaks); + +#endif // ZIGBEECHANNELMASK_H diff --git a/libnymea-zigbee/zigbeenetwork.cpp b/libnymea-zigbee/zigbeenetwork.cpp index 7abe63e..2d1a823 100644 --- a/libnymea-zigbee/zigbeenetwork.cpp +++ b/libnymea-zigbee/zigbeenetwork.cpp @@ -115,6 +115,20 @@ void ZigbeeNetwork::setChannel(quint32 channel) emit channelChanged(m_channel); } +ZigbeeChannelMask ZigbeeNetwork::channelMask() const +{ + return m_channelMask; +} + +void ZigbeeNetwork::setChannelMask(const ZigbeeChannelMask &channelMask) +{ + if (m_channelMask == channelMask) + return; + + m_channelMask = channelMask; + emit channelMaskChanged(m_channelMask); +} + ZigbeeSecurityConfiguration ZigbeeNetwork::securityConfiguration() const { return m_securityConfiguration; diff --git a/libnymea-zigbee/zigbeenetwork.h b/libnymea-zigbee/zigbeenetwork.h index 09e5655..da06da5 100644 --- a/libnymea-zigbee/zigbeenetwork.h +++ b/libnymea-zigbee/zigbeenetwork.h @@ -32,6 +32,7 @@ #include #include "zigbeenode.h" +#include "zigbeechannelmask.h" #include "zigbeebridgecontroller.h" #include "zigbeesecurityconfiguration.h" @@ -81,6 +82,9 @@ public: quint32 channel() const; void setChannel(quint32 channel); + ZigbeeChannelMask channelMask() const; + void setChannelMask(const ZigbeeChannelMask &channelMask); + ZigbeeSecurityConfiguration securityConfiguration() const; void setSecurityConfiguration(const ZigbeeSecurityConfiguration &securityConfiguration); @@ -107,6 +111,7 @@ private: // Network configurations quint64 m_extendedPanId = 0; quint32 m_channel = 0; + ZigbeeChannelMask m_channelMask = ZigbeeChannelMask(ZigbeeChannelMask::ChannelConfigurationAllChannels); ZigbeeNode::NodeType m_nodeType = ZigbeeNode::NodeTypeCoordinator; QString m_settingsFileName = "/etc/nymea/nymea-zigbee.conf"; @@ -148,6 +153,7 @@ signals: void extendedPanIdChanged(quint64 extendedPanId); void channelChanged(uint channel); + void channelMaskChanged(const ZigbeeChannelMask &channelMask); void securityConfigurationChanged(const ZigbeeSecurityConfiguration &securityConfiguration); void nodeAdded(ZigbeeNode *node); diff --git a/libnymea-zigbee/zigbeenode.cpp b/libnymea-zigbee/zigbeenode.cpp index c7328f2..30dfbea 100644 --- a/libnymea-zigbee/zigbeenode.cpp +++ b/libnymea-zigbee/zigbeenode.cpp @@ -505,7 +505,6 @@ void ZigbeeNode::onClusterAttributeChanged(const ZigbeeClusterAttribute &attribu // qCDebug(dcZigbeeNode()) << " Data:" << data; //} - QDebug operator<<(QDebug debug, ZigbeeNode *node) { debug.nospace().noquote() << "ZigbeeNode(" << ZigbeeUtils::convertUint16ToHexString(node->shortAddress());