diff --git a/docs/deCONZ-BHB-en.pdf b/docs/deCONZ-BHB-en.pdf new file mode 100644 index 0000000..64b43b9 Binary files /dev/null and b/docs/deCONZ-BHB-en.pdf differ diff --git a/libnymea-zigbee/deconz/interface/deconz.h b/libnymea-zigbee/deconz/interface/deconz.h new file mode 100644 index 0000000..6f799ac --- /dev/null +++ b/libnymea-zigbee/deconz/interface/deconz.h @@ -0,0 +1,112 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 DECONZ_H +#define DECONZ_H + +#include + +class Deconz +{ + Q_GADGET + +public: + enum StatusCode { + StatusCodeSuccess = 0x00, + StatusCodeFailure = 0x01, + StatusCodeBusy = 0x02, + StatusCodeTimeout = 0x03, + StatusCodeUnsupported = 0x04, + StatusCodeError = 0x05, + StatusCodeNoNetwork = 0x06, + StatusCodeInvalidValue = 0x07 + }; + Q_ENUM(StatusCode) + + enum NetworkState { + NetworkStateOffline = 0x00, + NetworkStateJoining = 0x01, + NetworkStateConnected = 0x02, + NetworkStateLeaving = 0x03 + }; + Q_ENUM(NetworkState) + + enum Command { + CommandDeviceState = 0x07, + CommandChangeNetworkState = 0x08, + CommandReadParameter = 0x0A, + CommandWriteParameter = 0x0B, + CommandDeviceStateChanged = 0x0E, + CommandVersion = 0x0D, + CommandApsDataRequest = 0x12, + CommandApsDataConfirm = 0x04, + CommandApsDataIndication = 0x17 + }; + Q_ENUM(Command) + + enum Parameter { + ParameterMacAddress = 0x01, // R + ParameterPanId = 0x05, // R + ParameterNetworkAddress = 0x07, // R + ParameterNetworkExtendedPanId = 0x08, // R + ParameterNodeType = 0x09, //RW + ParameterChannelMask = 0x0A, // RW + ParameterApsExtendedPanId = 0x0B, //RW + ParameterTrustCenterAddress = 0x0E, // RW + ParameterSecurityMode = 0x10, // RW + ParameterNetworkKey = 0x18, //W + ParameterCurrentChannel = 0x1c, // R + ParameterPermitJoin = 0x21, // RW + ParameterProtocolVersion = 0x22, // R + ParameterNetworkUpdateId = 0x24, // RW + ParameterWatchdogTtl = 0x26 // RW since protocol version 0x0108 + }; + Q_ENUM(Parameter) + + enum NodeType { + NodeTypeRouter = 0x00, + NodeTypeCoordinator = 0x01 + }; + Q_ENUM(NodeType) + + enum SecurityMode { + SecurityModeNoSecurity = 0x00, + SecurityModePreconfiguredNetworkKey = 0x01, + SecurityModeNetworkKeyFromTrustCenter = 0x02, + SecurityModeNoMasterButTrustCenterKey = 0x03 + }; + Q_ENUM(SecurityMode) + + enum Platform { + PlatformConbeeRaspbee = 0x05, + PlatformConbeeII = 0x07 + }; + Q_ENUM(Platform) + +}; + +#endif // DECONZ_H diff --git a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconz.cpp b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconz.cpp new file mode 100644 index 0000000..bb25933 --- /dev/null +++ b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconz.cpp @@ -0,0 +1,272 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeinterfacedeconz.h" +#include "zigbee.h" +#include "zigbeeutils.h" +#include "loggingcategory.h" + +#include + +// SLIP: https://tools.ietf.org/html/rfc1055 + +ZigbeeInterfaceDeconz::ZigbeeInterfaceDeconz(QObject *parent) : QObject(parent) +{ + m_reconnectTimer = new QTimer(this); + m_reconnectTimer->setSingleShot(true); + m_reconnectTimer->setInterval(2000); + + connect(m_reconnectTimer, &QTimer::timeout, this, &ZigbeeInterfaceDeconz::onReconnectTimeout); +} + +ZigbeeInterfaceDeconz::~ZigbeeInterfaceDeconz() +{ + +} + +quint16 ZigbeeInterfaceDeconz::calculateCrc(const QByteArray &data) +{ + quint16 crc = 0; + for (int i = 0; i < data.length(); i++) { + crc += static_cast(data.at(i)); + } + quint8 crc0 = (~crc + 1) & 0xFF; + quint8 crc1 = ((~crc + 1) >> 8) & 0xFF; + return (static_cast(crc1 << 8) | static_cast(crc0)); +} + +QByteArray ZigbeeInterfaceDeconz::unescapeData(const QByteArray &data) +{ + QByteArray deserializedData; + // Parse serial data and built InterfaceMessage + bool escaped = false; + for (int i = 0; i < data.length(); i++) { + quint8 byte = static_cast(data.at(i)); + + if (escaped) { + if (byte == ProtocolByteTransposedEnd) { + deserializedData.append(static_cast(ProtocolByteEnd)); + } else if (byte == ProtocolByteTransposedEsc) { + deserializedData.append(static_cast(ProtocolByteEsc)); + } else { + qCWarning(dcZigbeeInterfaceTraffic()) << "Error while deserialing data. Escape character received but the escaped character was not recognized."; + return QByteArray(); + } + + escaped = false; + continue; + } + + // If escape byte, the next byte has to be a modified byte + if (byte == ProtocolByteEsc) { + escaped = true; + } else { + deserializedData.append(static_cast(byte)); + } + } + + return deserializedData; +} + +QByteArray ZigbeeInterfaceDeconz::escapeData(const QByteArray &data) +{ + QByteArray serializedData; + QDataStream stream(&serializedData, QIODevice::WriteOnly); + + for (int i = 0; i < data.length(); i++) { + quint8 byte = static_cast(data.at(i)); + switch (byte) { + case ProtocolByteEnd: + stream << static_cast(ProtocolByteEsc); + stream << static_cast(ProtocolByteTransposedEnd); + break; + case ProtocolByteEsc: + stream << static_cast(ProtocolByteEsc); + stream << static_cast(ProtocolByteTransposedEsc); + break; + default: + stream << byte; + break; + } + } + + return serializedData; +} + +void ZigbeeInterfaceDeconz::setAvailable(bool available) +{ + if (m_available == available) + return; + + m_available = available; + emit availableChanged(m_available); + + // Clear the data buffer in any case + m_dataBuffer.clear(); +} + +void ZigbeeInterfaceDeconz::onReconnectTimeout() +{ + if (m_serialPort && !m_serialPort->isOpen()) { + if (!m_serialPort->open(QSerialPort::ReadWrite)) { + setAvailable(false); + m_reconnectTimer->start(); + } else { + qCDebug(dcZigbeeInterface()) << "Interface reconnected successfully on" << m_serialPort->portName() << m_serialPort->baudRate(); + m_serialPort->clear(); + setAvailable(true); + } + } +} + +void ZigbeeInterfaceDeconz::onReadyRead() +{ + QByteArray data = m_serialPort->readAll(); + // Read each byte until we get END byte, then unescape the package + for (int i = 0; i < data.length(); i++) { + quint8 byte = static_cast(data.at(i)); + if (byte == ProtocolByteEnd) { + // If there is no data...continue since it might be a starting END byte + if (m_dataBuffer.isEmpty()) + continue; + + qCDebug(dcZigbeeInterfaceTraffic()) << "<--" << ZigbeeUtils::convertByteArrayToHexString(m_dataBuffer); + QByteArray frame = unescapeData(m_dataBuffer); + if (frame.isNull()) { + qCWarning(dcZigbeeInterface()) << "Received inconsistant message. Ignoring data" << ZigbeeUtils::convertByteArrayToHexString(m_dataBuffer); + } else { + QByteArray package = frame.left(frame.length() - 2); + QByteArray checksumBytes = frame.right(2); + QDataStream stream(&checksumBytes, QIODevice::ReadOnly); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 receivedChecksum = 0; + stream >> receivedChecksum; + quint16 calculatedChecksum = calculateCrc(package); + if (receivedChecksum != calculatedChecksum) { + qCWarning(dcZigbeeInterfaceTraffic()) << "Checksum verification failed for frame" << ZigbeeUtils::convertByteArrayToHexString(m_dataBuffer) << receivedChecksum << "!=" << calculatedChecksum; + continue; + } + + // Checksum verified, we got valid data + emit packageReceived(package); + } + m_dataBuffer.clear(); + } else { + m_dataBuffer.append(data.at(i)); + } + } +} + +void ZigbeeInterfaceDeconz::onError(const QSerialPort::SerialPortError &error) +{ + if (error != QSerialPort::NoError && m_serialPort->isOpen()) { + qCWarning(dcZigbeeInterface()) << "Serial port error:" << error << m_serialPort->errorString(); + m_reconnectTimer->start(); + m_serialPort->close(); + setAvailable(false); + } +} + +void ZigbeeInterfaceDeconz::sendPackage(const QByteArray &package) +{ + if (!m_available) { + qCWarning(dcZigbeeInterface()) << "Can not send data. The interface is not available"; + return; + } + + // Build the frame and escape the package data and crc + quint16 checksum = calculateCrc(package); + + QByteArray frame = package; + QDataStream frameStream(&frame, QIODevice::WriteOnly | QIODevice::Append); + frameStream.setByteOrder(QDataStream::LittleEndian); + frameStream << checksum; + + // Escape data according to SLIP for transfere + QByteArray serializedData = escapeData(frame); + + // Build transport data + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly); + stream << static_cast(ProtocolByteEnd); // Start with SLIP END character + for (int i = 0; i < serializedData.length(); i++) + stream << static_cast(serializedData.at(i)); + + stream << static_cast(ProtocolByteEnd); // End with SLIP END character + + // Send the data + qCDebug(dcZigbeeInterfaceTraffic()) << "-->" << ZigbeeUtils::convertByteArrayToHexString(data); + if (m_serialPort->write(data) < 0) { + qCWarning(dcZigbeeInterface()) << "Could not stream byte" << ZigbeeUtils::convertByteArrayToHexString(data); + } + + m_serialPort->flush(); +} + +bool ZigbeeInterfaceDeconz::enable(const QString &serialPort, qint32 baudrate) +{ + if (m_serialPort) { + delete m_serialPort; + m_serialPort = nullptr; + } + + setAvailable(false); + + m_serialPort = new QSerialPort(serialPort, this); + m_serialPort->setBaudRate(baudrate); + m_serialPort->setDataBits(QSerialPort::Data8); + m_serialPort->setStopBits(QSerialPort::OneStop); + m_serialPort->setParity(QSerialPort::NoParity); + m_serialPort->setFlowControl(QSerialPort::NoFlowControl); + + connect(m_serialPort, &QSerialPort::readyRead, this, &ZigbeeInterfaceDeconz::onReadyRead); + connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onError(QSerialPort::SerialPortError))); + + if (!m_serialPort->open(QSerialPort::ReadWrite)) { + qCWarning(dcZigbeeInterface()) << "Could not open serial port" << serialPort << baudrate; + m_reconnectTimer->start(); + return false; + } + + qCDebug(dcZigbeeInterface()) << "Interface enabled successfully on" << serialPort << baudrate; + setAvailable(true); + return true; +} + +void ZigbeeInterfaceDeconz::disable() +{ + if (!m_serialPort) + return; + + if (m_serialPort->isOpen()) + m_serialPort->close(); + + delete m_serialPort; + m_serialPort = nullptr; + setAvailable(false); + qCDebug(dcZigbeeInterface()) << "Interface disabled"; +} diff --git a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconz.h b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconz.h new file mode 100644 index 0000000..58af5b7 --- /dev/null +++ b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconz.h @@ -0,0 +1,82 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEEINTERFACEDECONZ_H +#define ZIGBEEINTERFACEDECONZ_H + +#include +#include +#include + +class ZigbeeInterfaceDeconz : public QObject +{ + Q_OBJECT +public: + enum ProtocolByte { + ProtocolByteEnd = 0xC0, + ProtocolByteEsc = 0xDB, + ProtocolByteTransposedEnd = 0xDC, + ProtocolByteTransposedEsc = 0xDD + }; + Q_ENUM(ProtocolByte) + + explicit ZigbeeInterfaceDeconz(QObject *parent = nullptr); + ~ZigbeeInterfaceDeconz(); + + bool available() const; + QString serialPort() const; + +private: + QTimer *m_reconnectTimer = nullptr; + QSerialPort *m_serialPort = nullptr; + bool m_available = false; + QByteArray m_dataBuffer; + + quint16 calculateCrc(const QByteArray &data); + QByteArray unescapeData(const QByteArray &data); + QByteArray escapeData(const QByteArray &data); + + void setAvailable(bool available); + +signals: + void availableChanged(bool available); + void packageReceived(const QByteArray &package); + +private slots: + void onReconnectTimeout(); + void onReadyRead(); + void onError(const QSerialPort::SerialPortError &error); + +public slots: + void sendPackage(const QByteArray &package); + + bool enable(const QString &serialPort = "/dev/ttyS0", qint32 baudrate = 115200); + void disable(); + +}; + +#endif // ZIGBEEINTERFACEDECONZ_H diff --git a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp new file mode 100644 index 0000000..3469fd5 --- /dev/null +++ b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.cpp @@ -0,0 +1,56 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeinterfacedeconzreply.h" + +Deconz::Command ZigbeeInterfaceDeconzReply::command() const +{ + return m_command; +} + +quint8 ZigbeeInterfaceDeconzReply::sequenceNumber() const +{ + return m_sequenceNumber; +} + +QByteArray ZigbeeInterfaceDeconzReply::responseData() const +{ + return m_responseData; +} + +Deconz::StatusCode ZigbeeInterfaceDeconzReply::statusCode() const +{ + return m_statusCode; +} + +ZigbeeInterfaceDeconzReply::ZigbeeInterfaceDeconzReply(Deconz::Command command, quint8 sequenceNumber, QObject *parent) : + QObject(parent), + m_command(command), + m_sequenceNumber(sequenceNumber) +{ + +} diff --git a/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h new file mode 100644 index 0000000..2a67a5f --- /dev/null +++ b/libnymea-zigbee/deconz/interface/zigbeeinterfacedeconzreply.h @@ -0,0 +1,66 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEEINTERFACEDECONZREPLY_H +#define ZIGBEEINTERFACEDECONZREPLY_H + +#include + +#include "deconz.h" + +class ZigbeeInterfaceDeconzReply : public QObject +{ + Q_OBJECT + + friend class ZigbeeBridgeControllerDeconz; + +public: + // Request content + Deconz::Command command() const; + quint8 sequenceNumber() const; + QByteArray responseData() const; + + // Response content + Deconz::StatusCode statusCode() const; + +private: + explicit ZigbeeInterfaceDeconzReply(Deconz::Command command, quint8 sequenceNumber, QObject *parent = nullptr); + + // Request content + Deconz::Command m_command; + quint8 m_sequenceNumber = 0; + + // Response content + Deconz::StatusCode m_statusCode = Deconz::StatusCodeError; + QByteArray m_responseData; + +signals: + void finished(); + +}; + +#endif // ZIGBEEINTERFACEDECONZREPLY_H diff --git a/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp new file mode 100644 index 0000000..c1fc5d6 --- /dev/null +++ b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.cpp @@ -0,0 +1,589 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeebridgecontrollerdeconz.h" +#include "loggingcategory.h" +#include "zigbeeutils.h" + +#include + +ZigbeeBridgeControllerDeconz::ZigbeeBridgeControllerDeconz(QObject *parent) : + ZigbeeBridgeController(parent) +{ + m_interface = new ZigbeeInterfaceDeconz(this); + connect(m_interface, &ZigbeeInterfaceDeconz::availableChanged, this, &ZigbeeBridgeControllerDeconz::onInterfaceAvailableChanged); + connect(m_interface, &ZigbeeInterfaceDeconz::packageReceived, this, &ZigbeeBridgeControllerDeconz::onInterfacePackageReceived); + + 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); +} + +ZigbeeBridgeControllerDeconz::~ZigbeeBridgeControllerDeconz() +{ + qCDebug(dcZigbeeController()) << "Destroy controller"; +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestVersion() +{ + quint8 sequenceNumber = generateSequenceNumber(); + qCDebug(dcZigbeeController()) << "Request version. SQN:" << sequenceNumber; + + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Deconz::CommandVersion); + stream << static_cast(sequenceNumber); + stream << static_cast(0); // Reserverd + stream << static_cast(5); // Frame length + + ZigbeeInterfaceDeconzReply *reply = new ZigbeeInterfaceDeconzReply(Deconz::CommandVersion, sequenceNumber, this); + connect(reply, &ZigbeeInterfaceDeconzReply::finished, reply, &ZigbeeInterfaceDeconzReply::deleteLater); + m_pendingReplies.insert(sequenceNumber, reply); + + m_interface->sendPackage(message); + return reply; +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestDeviceState() +{ + quint8 sequenceNumber = generateSequenceNumber(); + qCDebug(dcZigbeeController()) << "Request device state. SQN:" << sequenceNumber; + + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Deconz::CommandDeviceState); + stream << static_cast(sequenceNumber); + stream << static_cast(0); // Reserverd + stream << static_cast(8); // Frame length + stream << static_cast(0); // Reserverd + stream << static_cast(0); // Reserverd + stream << static_cast(0); // Reserverd + + ZigbeeInterfaceDeconzReply *reply = new ZigbeeInterfaceDeconzReply(Deconz::CommandDeviceState, sequenceNumber, this); + connect(reply, &ZigbeeInterfaceDeconzReply::finished, reply, &ZigbeeInterfaceDeconzReply::deleteLater); + m_pendingReplies.insert(sequenceNumber, reply); + + m_interface->sendPackage(message); + + return createReply(Deconz::CommandDeviceState, sequenceNumber, this); +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestReadParameter(Deconz::Parameter parameter) +{ + quint8 sequenceNumber = generateSequenceNumber(); + qCDebug(dcZigbeeController()) << "Request read parameter. SQN:" << sequenceNumber << parameter; + + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Deconz::CommandReadParameter); + stream << static_cast(sequenceNumber); + stream << static_cast(0); // Reserverd + stream << static_cast(8); // Frame length 7 + 1 payload + stream << static_cast(1); // Payload length + stream << static_cast(parameter); + + m_interface->sendPackage(message); + + return createReply(Deconz::CommandReadParameter, sequenceNumber, this); +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestWriteParameter(Deconz::Parameter parameter, const QByteArray &data) +{ + quint8 sequenceNumber = generateSequenceNumber(); + qCDebug(dcZigbeeController()) << "Request write parameter. SQN:" << sequenceNumber << parameter << ZigbeeUtils::convertByteArrayToHexString(data); + + 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(parameter); + for (int i = 0; i < data.length(); i++) { + stream << static_cast(data.at(i)); + } + + m_interface->sendPackage(message); + + return createReply(Deconz::CommandWriteParameter, sequenceNumber, this); +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::requestChangeNetworkState(Deconz::NetworkState networkState) +{ + quint8 sequenceNumber = generateSequenceNumber(); + qCDebug(dcZigbeeController()) << "Request change network state. SQN:" << sequenceNumber << networkState; + + QByteArray message; + QDataStream stream(&message, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << static_cast(Deconz::CommandChangeNetworkState); + stream << static_cast(sequenceNumber); + stream << static_cast(0); // Reserverd + stream << static_cast(6); // Frame length + stream << static_cast(networkState); + + m_interface->sendPackage(message); + + return createReply(Deconz::CommandChangeNetworkState, sequenceNumber, this); +} + + +quint8 ZigbeeBridgeControllerDeconz::generateSequenceNumber() +{ + return m_sequenceNumber++; +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::createReply(Deconz::Command command, quint8 sequenceNumber, QObject *parent) +{ + // Create the reply + ZigbeeInterfaceDeconzReply *reply = new ZigbeeInterfaceDeconzReply(command, sequenceNumber, parent); + + // Auto delete the object on finished + connect(reply, &ZigbeeInterfaceDeconzReply::finished, reply, &ZigbeeInterfaceDeconzReply::deleteLater); + + // Add it to the pending list + m_pendingReplies.insert(sequenceNumber, reply); + + return reply; +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::readNetworkParameters() +{ + qCDebug(dcZigbeeController()) << "Start reading network parameters"; + + // This method reads all network configuration parameters sequentially. This method returns a reply which will be finished either + // when a read request failes or all requests finished successfully. + // If read request failes, this mehtod returns the status code of the failed request. + + // Create an independent reply for finishing the entire read sequence + ZigbeeInterfaceDeconzReply *readNetworkParametersReply = new ZigbeeInterfaceDeconzReply(Deconz::CommandReadParameter, m_sequenceNumber, this); + connect(readNetworkParametersReply, &ZigbeeInterfaceDeconzReply::finished, readNetworkParametersReply, &ZigbeeInterfaceDeconzReply::deleteLater); + + // Read MAC address of the bridge + ZigbeeInterfaceDeconzReply *replyMacAddress = requestReadParameter(Deconz::ParameterMacAddress); + connect(replyMacAddress, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyMacAddress](){ + if (replyMacAddress->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyMacAddress->command() << Deconz::ParameterMacAddress + << "finished with error" << replyMacAddress->statusCode(); + + readNetworkParametersReply->m_statusCode = replyMacAddress->statusCode(); + readNetworkParametersReply->finished(); + return; + } + QDataStream stream(replyMacAddress->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint64 macAddress = 0; + stream >> payloadLenght >> parameter >> macAddress; + + m_networkConfiguration.ieeeAddress = ZigbeeAddress(macAddress); + qCDebug(dcZigbeeController()) << "Request" << replyMacAddress->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << "IEEE address:" << m_networkConfiguration.ieeeAddress.toString(); + + // Read PAN ID + ZigbeeInterfaceDeconzReply *replyPanId = requestReadParameter(Deconz::ParameterPanId); + connect(replyPanId, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyPanId](){ + if (replyPanId->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyPanId->command() << Deconz::ParameterPanId + << "finished with error" << replyPanId->statusCode(); + readNetworkParametersReply->m_statusCode = replyPanId->statusCode(); + readNetworkParametersReply->finished(); + return; + } + QDataStream stream(replyPanId->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint16 panId = 0; + stream >> payloadLenght >> parameter >> panId; + + m_networkConfiguration.panId = panId; + qCDebug(dcZigbeeController()) << "Request" << replyPanId->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << "PAN ID:" << ZigbeeUtils::convertUint16ToHexString(m_networkConfiguration.panId); + + // Read short address + ZigbeeInterfaceDeconzReply *replyShortAddress = requestReadParameter(Deconz::ParameterNetworkAddress); + connect(replyShortAddress, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyShortAddress](){ + if (replyShortAddress->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyShortAddress->command() << Deconz::ParameterNetworkAddress + << "finished with error" << replyShortAddress->statusCode(); + readNetworkParametersReply->m_statusCode = replyShortAddress->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyShortAddress->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint16 shortAddress = 0; + stream >> payloadLenght >> parameter >> shortAddress; + m_networkConfiguration.shortAddress = shortAddress; + qCDebug(dcZigbeeController()) << "Request" << replyShortAddress->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << ZigbeeUtils::convertUint16ToHexString(m_networkConfiguration.shortAddress); + + // Read extended PAN ID + ZigbeeInterfaceDeconzReply *replyExtendedPanId = requestReadParameter(Deconz::ParameterNetworkExtendedPanId); + connect(replyExtendedPanId, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyExtendedPanId](){ + if (replyExtendedPanId->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyExtendedPanId->command() << Deconz::ParameterNetworkExtendedPanId + << "finished with error" << replyExtendedPanId->statusCode(); + readNetworkParametersReply->m_statusCode = replyExtendedPanId->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyExtendedPanId->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint64 networkExtendedPanId = 0; + stream >> payloadLenght >> parameter >> networkExtendedPanId; + m_networkConfiguration.extendedPanId = networkExtendedPanId; + qCDebug(dcZigbeeController()) << "Request" << replyExtendedPanId->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << ZigbeeUtils::convertUint64ToHexString(m_networkConfiguration.extendedPanId); + + // Read device type + ZigbeeInterfaceDeconzReply *replyNodeType = requestReadParameter(Deconz::ParameterNodeType); + connect(replyNodeType, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyNodeType](){ + if (replyNodeType->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyNodeType->command() << Deconz::ParameterNodeType + << "finished with error" << replyNodeType->statusCode(); + readNetworkParametersReply->m_statusCode = replyNodeType->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyNodeType->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint8 nodeType = 0; + stream >> payloadLenght >> parameter >> nodeType; + + m_networkConfiguration.nodeType = static_cast(nodeType); + qCDebug(dcZigbeeController()) << "Request" << replyNodeType->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << m_networkConfiguration.nodeType; + + // Read channel mask + ZigbeeInterfaceDeconzReply *replyChannelMask = requestReadParameter(Deconz::ParameterChannelMask); + connect(replyChannelMask, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyChannelMask](){ + if (replyChannelMask->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyChannelMask->command() << Deconz::ParameterChannelMask + << "finished with error" << replyChannelMask->statusCode(); + readNetworkParametersReply->m_statusCode = replyChannelMask->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyChannelMask->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint32 channelMask = 0; + stream >> payloadLenght >> parameter >> channelMask; + + m_networkConfiguration.channelMask = channelMask; + qCDebug(dcZigbeeController()) << "Request" << replyChannelMask->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << ZigbeeUtils::convertUint32ToHexString(m_networkConfiguration.channelMask); + + // Read APS extended PAN ID + ZigbeeInterfaceDeconzReply *replyApsExtendedPanId = requestReadParameter(Deconz::ParameterApsExtendedPanId); + connect(replyApsExtendedPanId, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyApsExtendedPanId](){ + if (replyApsExtendedPanId->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyApsExtendedPanId->command() << Deconz::ParameterApsExtendedPanId + << "finished with error" << replyApsExtendedPanId->statusCode(); + readNetworkParametersReply->m_statusCode = replyApsExtendedPanId->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyApsExtendedPanId->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint64 apsExtendedPanId = 0; + stream >> payloadLenght >> parameter >> apsExtendedPanId; + + m_networkConfiguration.apsExtendedPanId = apsExtendedPanId; + qCDebug(dcZigbeeController()) << "Request" << replyApsExtendedPanId->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << ZigbeeUtils::convertUint64ToHexString(m_networkConfiguration.apsExtendedPanId); + + // Read trust center address + ZigbeeInterfaceDeconzReply *replyTrustCenterAddress = requestReadParameter(Deconz::ParameterTrustCenterAddress); + connect(replyTrustCenterAddress, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyTrustCenterAddress](){ + if (replyTrustCenterAddress->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyTrustCenterAddress->command() << Deconz::ParameterTrustCenterAddress + << "finished with error" << replyTrustCenterAddress->statusCode(); + readNetworkParametersReply->m_statusCode = replyTrustCenterAddress->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyTrustCenterAddress->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint64 trustCenterAddress = 0; + stream >> payloadLenght >> parameter >> trustCenterAddress; + + m_networkConfiguration.trustCenterAddress = ZigbeeAddress(trustCenterAddress); + qCDebug(dcZigbeeController()) << "Request" << replyTrustCenterAddress->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << m_networkConfiguration.trustCenterAddress; + + // Read security mode + ZigbeeInterfaceDeconzReply *replySecurityMode = requestReadParameter(Deconz::ParameterSecurityMode); + connect(replySecurityMode, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replySecurityMode](){ + if (replySecurityMode->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replySecurityMode->command() << Deconz::ParameterSecurityMode + << "finished with error" << replySecurityMode->statusCode(); + readNetworkParametersReply->m_statusCode = replySecurityMode->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replySecurityMode->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint8 securityMode = 0; + stream >> payloadLenght >> parameter >> securityMode; + + m_networkConfiguration.securityMode = static_cast(securityMode); + qCDebug(dcZigbeeController()) << "Request" << replySecurityMode->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << m_networkConfiguration.securityMode; + + // Note: reading the network key returns "InavlidParameter". Might be for security reasons which is good! + + // Read channel + ZigbeeInterfaceDeconzReply *replyChannel = requestReadParameter(Deconz::ParameterCurrentChannel); + connect(replyChannel, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyChannel](){ + if (replyChannel->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyChannel->command() << Deconz::ParameterCurrentChannel + << "finished with error" << replyChannel->statusCode(); + readNetworkParametersReply->m_statusCode = replyChannel->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyChannel->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint8 channel = 0; + stream >> payloadLenght >> parameter >> channel; + m_networkConfiguration.currentChannel = channel; + qCDebug(dcZigbeeController()) << "Request" << replyChannel->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << "Current channel:" << m_networkConfiguration.currentChannel; + + + // Read permit join status + ZigbeeInterfaceDeconzReply *replyPermitJoin = requestReadParameter(Deconz::ParameterPermitJoin); + connect(replyPermitJoin, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyPermitJoin](){ + if (replyPermitJoin->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyPermitJoin->command() << Deconz::ParameterPermitJoin + << "finished with error" << replyPermitJoin->statusCode(); + readNetworkParametersReply->m_statusCode = replyPermitJoin->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyPermitJoin->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; + stream >> payloadLenght >> parameter; + //m_networkConfiguration.currentChannel = channel; + qCDebug(dcZigbeeController()) << "Request" << replyPermitJoin->command() << static_cast(parameter) + << "finished successfully" << ZigbeeUtils::convertByteArrayToHexString(replyPermitJoin->responseData()); + + + // Read protocol version + ZigbeeInterfaceDeconzReply *replyProtocolVersion = requestReadParameter(Deconz::ParameterProtocolVersion); + connect(replyProtocolVersion, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyProtocolVersion](){ + if (replyProtocolVersion->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyProtocolVersion->command() << Deconz::ParameterProtocolVersion + << "finished with error" << replyProtocolVersion->statusCode(); + readNetworkParametersReply->m_statusCode = replyProtocolVersion->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyProtocolVersion->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint16 protocolVersion = 0; + stream >> payloadLenght >> parameter >> protocolVersion; + m_networkConfiguration.protocolVersion = protocolVersion; + qCDebug(dcZigbeeController()) << "Request" << replyProtocolVersion->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << "Protocol version:" << ZigbeeUtils::convertUint16ToHexString(m_networkConfiguration.protocolVersion); + + // Read network updat id + ZigbeeInterfaceDeconzReply *replyNetworkUpdateId = requestReadParameter(Deconz::ParameterNetworkUpdateId); + connect(replyNetworkUpdateId, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyNetworkUpdateId](){ + if (replyNetworkUpdateId->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyNetworkUpdateId->command() << Deconz::ParameterNetworkUpdateId + << "finished with error" << replyNetworkUpdateId->statusCode(); + readNetworkParametersReply->m_statusCode = replyNetworkUpdateId->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyNetworkUpdateId->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint8 networkUpdateId = 0; + stream >> payloadLenght >> parameter >> networkUpdateId; + m_networkConfiguration.networkUpdateId = networkUpdateId; + qCDebug(dcZigbeeController()) << "Request" << replyNetworkUpdateId->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << "Network update ID:" << m_networkConfiguration.networkUpdateId; + + // Make sure the watchdog is available for this version + if (m_networkConfiguration.protocolVersion < 0x0108) { + qCDebug(dcZigbeeController()) << "The watchdog api is available since protocol version 0x0108. The watchdog is not required for this version"; + m_watchdogTimer->stop(); + + // Finished reading all parameters. Finish the independent reply in order to indicate the process has finished + readNetworkParametersReply->m_statusCode = Deconz::StatusCodeSuccess; + readNetworkParametersReply->finished(); + return; + } + + // Read watchdog timeout + ZigbeeInterfaceDeconzReply *replyWatchdogTimeout = requestReadParameter(Deconz::ParameterWatchdogTtl); + connect(replyWatchdogTimeout, &ZigbeeInterfaceDeconzReply::finished, this, [this, readNetworkParametersReply, replyWatchdogTimeout](){ + if (replyWatchdogTimeout->statusCode() != Deconz::StatusCodeSuccess) { + qCWarning(dcZigbeeController()) << "Request" << replyWatchdogTimeout->command() << Deconz::ParameterWatchdogTtl + << "finished with error" << replyWatchdogTimeout->statusCode(); + readNetworkParametersReply->m_statusCode = replyWatchdogTimeout->statusCode(); + readNetworkParametersReply->finished(); + return; + } + + QDataStream stream(replyWatchdogTimeout->responseData()); + stream.setByteOrder(QDataStream::LittleEndian); + quint16 payloadLenght = 0; quint8 parameter = 0; quint32 watchdogTimeout = 0; + stream >> payloadLenght >> parameter >> watchdogTimeout; + m_networkConfiguration.watchdogTimeout = watchdogTimeout; + qCDebug(dcZigbeeController()) << "Request" << replyWatchdogTimeout->command() << static_cast(parameter) + << "finished successfully"; + qCDebug(dcZigbeeController()) << "Watchdog timeout:" << m_networkConfiguration.watchdogTimeout; + + // Note: this value describes how much seconds are left until the watchdog triggers. Reset it right the way + if (watchdogTimeout < 15) { + onWatchdogTimerTimeout(); + } + + // Finished reading all parameters. Finish the independent reply in order to indicate the process has finished + readNetworkParametersReply->m_statusCode = Deconz::StatusCodeSuccess; + readNetworkParametersReply->finished(); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + }); + + return readNetworkParametersReply; +} + +ZigbeeInterfaceDeconzReply *ZigbeeBridgeControllerDeconz::resetWatchdog() +{ + QByteArray parameterData; + QDataStream stream(¶meterData, QIODevice::WriteOnly); + stream.setByteOrder(QDataStream::LittleEndian); + stream << m_watchdogTimeout; + return requestWriteParameter(Deconz::ParameterWatchdogTtl, parameterData); +} + +void ZigbeeBridgeControllerDeconz::onInterfaceAvailableChanged(bool available) +{ + if (available) { + m_watchdogTimer->start(); + } else { + m_watchdogTimer->stop(); + } + + setAvailable(available); +} + +void ZigbeeBridgeControllerDeconz::onInterfacePackageReceived(const QByteArray &package) +{ + QDataStream stream(package); + stream.setByteOrder(QDataStream::LittleEndian); + quint8 command = 0; quint8 sequenceNumber = 0; quint8 status = 0; quint16 frameLength = 0; + stream >> command >> sequenceNumber >> status >> frameLength; + + qCDebug(dcZigbeeController()) << "Interface message received" + << static_cast(command) + << "SQN:" << sequenceNumber + << static_cast(status) + << "Frame length:" << frameLength; + + // 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->finished(); + return; + } + + // Note: we got a notification, lets set the current sequence number to the notification id, + // so the next request will be a continuouse increase + + m_sequenceNumber = sequenceNumber; + + // No request for this data, lets check which notification and process the data +} + +void ZigbeeBridgeControllerDeconz::onWatchdogTimerTimeout() +{ + qCDebug(dcZigbeeController()) << "Reset application watchdog on the deCONZ controller"; + ZigbeeInterfaceDeconzReply *reply = resetWatchdog(); + 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(); + return; + } + qCDebug(dcZigbeeController()) << "Reset application watchdog on the deCONZ controller successfully"; + }); +} + +bool ZigbeeBridgeControllerDeconz::enable(const QString &serialPort, qint32 baudrate) +{ + return m_interface->enable(serialPort, baudrate); +} + +void ZigbeeBridgeControllerDeconz::disable() +{ + m_interface->disable(); +} diff --git a/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h new file mode 100644 index 0000000..456cade --- /dev/null +++ b/libnymea-zigbee/deconz/zigbeebridgecontrollerdeconz.h @@ -0,0 +1,110 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEEBRIDGECONTROLLERDECONZ_H +#define ZIGBEEBRIDGECONTROLLERDECONZ_H + +#include +#include +#include + +#include "zigbee.h" +#include "zigbeeaddress.h" +#include "zigbeenetworkkey.h" +#include "zigbeebridgecontroller.h" + +#include "interface/deconz.h" +#include "interface/zigbeeinterfacedeconz.h" +#include "interface/zigbeeinterfacedeconzreply.h" + +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 + ZigbeeAddress trustCenterAddress; // RW + Deconz::SecurityMode securityMode; // RW + ZigbeeNetworkKey networkKey; // RW + quint8 currentChannel; // R + quint16 protocolVersion; // R + quint8 networkUpdateId; // RW + quint32 watchdogTimeout; // RW +} DeconzNetworkConfiguration; + + +class ZigbeeBridgeControllerDeconz : public ZigbeeBridgeController +{ + Q_OBJECT + + friend class ZigbeeNetworkDeconz; + +public: + explicit ZigbeeBridgeControllerDeconz(QObject *parent = nullptr); + ~ZigbeeBridgeControllerDeconz() override; + + ZigbeeInterfaceDeconzReply *requestVersion(); + ZigbeeInterfaceDeconzReply *requestDeviceState(); + ZigbeeInterfaceDeconzReply *requestReadParameter(Deconz::Parameter parameter); + ZigbeeInterfaceDeconzReply *requestWriteParameter(Deconz::Parameter parameter, const QByteArray &data); + ZigbeeInterfaceDeconzReply *requestStartJoinNetwork(); + +private: + ZigbeeInterfaceDeconz *m_interface = nullptr; + quint8 m_sequenceNumber = 0; + quint32 m_watchdogTimeout = 85; + int m_watchdogResetTimout = 60; + QHash m_pendingReplies; + DeconzNetworkConfiguration m_networkConfiguration; + QTimer *m_watchdogTimer = nullptr; + + quint8 generateSequenceNumber(); + + ZigbeeInterfaceDeconzReply *createReply(Deconz::Command command, quint8 sequenceNumber, QObject *parent); + + // Note: this method reads all parameters individual. The returned reply it self will not send or receive any data. + // The data can be fetched from m_networkConfiguration on success. + ZigbeeInterfaceDeconzReply *readNetworkParameters(); + + ZigbeeInterfaceDeconzReply *resetWatchdog(); + +signals: + +private slots: + void onInterfaceAvailableChanged(bool available); + void onInterfacePackageReceived(const QByteArray &package); + + void onWatchdogTimerTimeout(); + +public slots: + bool enable(const QString &serialPort, qint32 baudrate); + void disable(); +}; + +#endif // ZIGBEEBRIDGECONTROLLERDECONZ_H diff --git a/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp b/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp new file mode 100644 index 0000000..f56e32b --- /dev/null +++ b/libnymea-zigbee/deconz/zigbeenetworkdeconz.cpp @@ -0,0 +1,171 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeenetworkdeconz.h" +#include "loggingcategory.h" +#include "zigbeeutils.h" + +ZigbeeNetworkDeconz::ZigbeeNetworkDeconz(QObject *parent) : + ZigbeeNetwork(parent) +{ + m_controller = new ZigbeeBridgeControllerDeconz(this); + //connect(m_controller, &ZigbeeBridgeControllerDeconz::messageReceived, this, &ZigbeeNetworkDeconz::onMessageReceived); + connect(m_controller, &ZigbeeBridgeControllerDeconz::availableChanged, this, &ZigbeeNetworkDeconz::onControllerAvailableChanged); +} + +ZigbeeBridgeController *ZigbeeNetworkDeconz::bridgeController() const +{ + if (m_controller) + return qobject_cast(m_controller); + + return nullptr; +} + +ZigbeeNode *ZigbeeNetworkDeconz::createNode(QObject *parent) +{ + //FIXME + Q_UNUSED(parent) + return nullptr; +} + +void ZigbeeNetworkDeconz::setPermitJoiningInternal(bool permitJoining) +{ + //FIXME + Q_UNUSED(permitJoining) +} + +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."; + + + + + }); + }); + } +} + +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); + setState(StateOffline); + //setStartingState(StartingStateNone); + setError(ErrorHardwareUnavailable); + return; + } + + m_permitJoining = false; + emit permitJoiningChanged(m_permitJoining); + // Note: wait for the controller available signal and start the initialization there +} + +void ZigbeeNetworkDeconz::stopNetwork() +{ + +} + +void ZigbeeNetworkDeconz::reset() +{ + +} + +void ZigbeeNetworkDeconz::factoryResetNetwork() +{ + +} diff --git a/libnymea-zigbee/deconz/zigbeenetworkdeconz.h b/libnymea-zigbee/deconz/zigbeenetworkdeconz.h new file mode 100644 index 0000000..606dd52 --- /dev/null +++ b/libnymea-zigbee/deconz/zigbeenetworkdeconz.h @@ -0,0 +1,63 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEENETWORKDECONZ_H +#define ZIGBEENETWORKDECONZ_H + +#include +#include "zigbeenetwork.h" + +#include "zigbeebridgecontrollerdeconz.h" + +class ZigbeeNetworkDeconz : public ZigbeeNetwork +{ + Q_OBJECT +public: + explicit ZigbeeNetworkDeconz(QObject *parent = nullptr); + + ZigbeeBridgeController *bridgeController() const override; + +private: + ZigbeeBridgeControllerDeconz *m_controller = nullptr; + bool m_networkRunning = false; + +protected: + ZigbeeNode *createNode(QObject *parent) override; + void setPermitJoiningInternal(bool permitJoining) override; + +private slots: + void onControllerAvailableChanged(bool available); + +public slots: + void startNetwork() override; + void stopNetwork() override; + void reset() override; + void factoryResetNetwork() override; + +}; + +#endif // ZIGBEENETWORKDECONZ_H diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index c7d8a43..5558592 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -4,6 +4,10 @@ TARGET = nymea-zigbee1 TEMPLATE = lib SOURCES += \ + deconz/interface/zigbeeinterfacedeconz.cpp \ + deconz/interface/zigbeeinterfacedeconzreply.cpp \ + deconz/zigbeebridgecontrollerdeconz.cpp \ + deconz/zigbeenetworkdeconz.cpp \ nxp/interface/zigbeeinterface.cpp \ nxp/interface/zigbeeinterfacemessage.cpp \ nxp/interface/zigbeeinterfacerequest.cpp \ @@ -29,6 +33,11 @@ SOURCES += \ zigbeeaddress.cpp \ HEADERS += \ + deconz/interface/deconz.h \ + deconz/interface/zigbeeinterfacedeconz.h \ + deconz/interface/zigbeeinterfacedeconzreply.h \ + deconz/zigbeebridgecontrollerdeconz.h \ + deconz/zigbeenetworkdeconz.h \ nxp/interface/zigbeeinterface.h \ nxp/interface/zigbeeinterfacemessage.h \ nxp/interface/zigbeeinterfacerequest.h \ diff --git a/libnymea-zigbee/zigbeenetworkkey.cpp b/libnymea-zigbee/zigbeenetworkkey.cpp index e2ba46f..95f4212 100644 --- a/libnymea-zigbee/zigbeenetworkkey.cpp +++ b/libnymea-zigbee/zigbeenetworkkey.cpp @@ -39,9 +39,9 @@ ZigbeeNetworkKey::ZigbeeNetworkKey(const ZigbeeNetworkKey &other) m_key = other.toByteArray(); } -ZigbeeNetworkKey::ZigbeeNetworkKey(const QString &key) +ZigbeeNetworkKey::ZigbeeNetworkKey(const QString &keyString) { - QString rawKey = QString(key).remove(':'); + QString rawKey = QString(keyString).remove(':'); if (rawKey.isEmpty()) return; @@ -49,8 +49,8 @@ ZigbeeNetworkKey::ZigbeeNetworkKey(const QString &key) m_key = QByteArray::fromHex(rawKey.toLatin1()); } -ZigbeeNetworkKey::ZigbeeNetworkKey(const QByteArray &keyString) : - m_key(keyString) +ZigbeeNetworkKey::ZigbeeNetworkKey(const QByteArray &key) : + m_key(key) { Q_ASSERT_X(isValid(), "ZigbeeNetworkKey", "invalid key length in ZigbeeNetworkKey(QByteArray)."); } diff --git a/libnymea-zigbee/zigbeenetworkkey.h b/libnymea-zigbee/zigbeenetworkkey.h index ce00781..2f9c950 100644 --- a/libnymea-zigbee/zigbeenetworkkey.h +++ b/libnymea-zigbee/zigbeenetworkkey.h @@ -38,8 +38,8 @@ class ZigbeeNetworkKey public: ZigbeeNetworkKey(); ZigbeeNetworkKey(const ZigbeeNetworkKey &other); - ZigbeeNetworkKey(const QString &key); - ZigbeeNetworkKey(const QByteArray &keyString); + ZigbeeNetworkKey(const QString &keyString); + ZigbeeNetworkKey(const QByteArray &key); bool isValid() const; bool isNull() const; diff --git a/libnymea-zigbee/zigbeenetworkmanager.cpp b/libnymea-zigbee/zigbeenetworkmanager.cpp index 4b7aebf..d9f9b97 100644 --- a/libnymea-zigbee/zigbeenetworkmanager.cpp +++ b/libnymea-zigbee/zigbeenetworkmanager.cpp @@ -29,6 +29,7 @@ #include "loggingcategory.h" #include "nxp/zigbeenetworknxp.h" +#include "deconz/zigbeenetworkdeconz.h" #include @@ -40,6 +41,8 @@ ZigbeeNetwork *ZigbeeNetworkManager::createZigbeeNetwork(ZigbeeNetworkManager::B switch (backend) { case BackendTypeNxp: return qobject_cast(new ZigbeeNetworkNxp(parent)); + case BackendTypeDeconz: + return qobject_cast(new ZigbeeNetworkDeconz(parent)); } return nullptr; diff --git a/libnymea-zigbee/zigbeenetworkmanager.h b/libnymea-zigbee/zigbeenetworkmanager.h index 92e6c9c..f99323c 100644 --- a/libnymea-zigbee/zigbeenetworkmanager.h +++ b/libnymea-zigbee/zigbeenetworkmanager.h @@ -36,7 +36,8 @@ class ZigbeeNetworkManager { public: enum BackendType { - BackendTypeNxp + BackendTypeNxp, + BackendTypeDeconz }; static ZigbeeNetwork *createZigbeeNetwork(BackendType backend, QObject *parent = nullptr);