diff --git a/libnymea-zigbee/backends/deconz/interface/zigbeeinterfacedeconz.cpp b/libnymea-zigbee/backends/deconz/interface/zigbeeinterfacedeconz.cpp index 1dfae99..fbb43dd 100644 --- a/libnymea-zigbee/backends/deconz/interface/zigbeeinterfacedeconz.cpp +++ b/libnymea-zigbee/backends/deconz/interface/zigbeeinterfacedeconz.cpp @@ -48,6 +48,16 @@ ZigbeeInterfaceDeconz::~ZigbeeInterfaceDeconz() } +bool ZigbeeInterfaceDeconz::available() const +{ + return m_available; +} + +QString ZigbeeInterfaceDeconz::serialPort() const +{ + return m_serialPort->portName(); +} + quint16 ZigbeeInterfaceDeconz::calculateCrc(const QByteArray &data) { quint16 crc = 0; @@ -251,7 +261,7 @@ bool ZigbeeInterfaceDeconz::enable(const QString &serialPort, qint32 baudrate) 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))); + connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onError(QSerialPort::SerialPortError)), Qt::QueuedConnection); if (!m_serialPort->open(QSerialPort::ReadWrite)) { qCWarning(dcZigbeeInterface()) << "Could not open serial port" << serialPort << baudrate << m_serialPort->errorString(); diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterface.cpp b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterface.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterface.cpp rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterface.cpp diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterface.h b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterface.h similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterface.h rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterface.h diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacemessage.cpp b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacemessage.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterfacemessage.cpp rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacemessage.cpp diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacemessage.h b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacemessage.h similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterfacemessage.h rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacemessage.h diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacereply.cpp b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacereply.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterfacereply.cpp rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacereply.cpp diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacereply.h b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacereply.h similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterfacereply.h rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacereply.h diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacerequest.cpp b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacerequest.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterfacerequest.cpp rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacerequest.cpp diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacerequest.h b/libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacerequest.h similarity index 100% rename from libnymea-zigbee/backends/nxp/interface/zigbeeinterfacerequest.h rename to libnymea-zigbee/backends/nxp-old/interface/zigbeeinterfacerequest.h diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp b/libnymea-zigbee/backends/nxp-old/zigbeebridgecontrollernxp.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.cpp rename to libnymea-zigbee/backends/nxp-old/zigbeebridgecontrollernxp.cpp diff --git a/libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h b/libnymea-zigbee/backends/nxp-old/zigbeebridgecontrollernxp.h similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeebridgecontrollernxp.h rename to libnymea-zigbee/backends/nxp-old/zigbeebridgecontrollernxp.h diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp b/libnymea-zigbee/backends/nxp-old/zigbeenetworknxp.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeenetworknxp.cpp rename to libnymea-zigbee/backends/nxp-old/zigbeenetworknxp.cpp diff --git a/libnymea-zigbee/backends/nxp/zigbeenetworknxp.h b/libnymea-zigbee/backends/nxp-old/zigbeenetworknxp.h similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeenetworknxp.h rename to libnymea-zigbee/backends/nxp-old/zigbeenetworknxp.h diff --git a/libnymea-zigbee/backends/nxp/zigbeenodeendpointnxp.cpp b/libnymea-zigbee/backends/nxp-old/zigbeenodeendpointnxp.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeenodeendpointnxp.cpp rename to libnymea-zigbee/backends/nxp-old/zigbeenodeendpointnxp.cpp diff --git a/libnymea-zigbee/backends/nxp/zigbeenodeendpointnxp.h b/libnymea-zigbee/backends/nxp-old/zigbeenodeendpointnxp.h similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeenodeendpointnxp.h rename to libnymea-zigbee/backends/nxp-old/zigbeenodeendpointnxp.h diff --git a/libnymea-zigbee/backends/nxp/zigbeenodenxp.cpp b/libnymea-zigbee/backends/nxp-old/zigbeenodenxp.cpp similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeenodenxp.cpp rename to libnymea-zigbee/backends/nxp-old/zigbeenodenxp.cpp diff --git a/libnymea-zigbee/backends/nxp/zigbeenodenxp.h b/libnymea-zigbee/backends/nxp-old/zigbeenodenxp.h similarity index 100% rename from libnymea-zigbee/backends/nxp/zigbeenodenxp.h rename to libnymea-zigbee/backends/nxp-old/zigbeenodenxp.h diff --git a/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacenxp.cpp b/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacenxp.cpp new file mode 100644 index 0000000..574acb0 --- /dev/null +++ b/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacenxp.cpp @@ -0,0 +1,302 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 "zigbeeinterfacenxp.h" +#include "zigbee.h" +#include "zigbeeutils.h" +#include "loggingcategory.h" + +#include + +// SLIP: https://tools.ietf.org/html/rfc1055 + +ZigbeeInterfaceNxp::ZigbeeInterfaceNxp(QObject *parent) : QObject(parent) +{ + m_reconnectTimer = new QTimer(this); + m_reconnectTimer->setSingleShot(true); + m_reconnectTimer->setInterval(5000); + + connect(m_reconnectTimer, &QTimer::timeout, this, &ZigbeeInterfaceNxp::onReconnectTimeout); +} + +ZigbeeInterfaceNxp::~ZigbeeInterfaceNxp() +{ + +} + +bool ZigbeeInterfaceNxp::available() const +{ + return m_available; +} + +QString ZigbeeInterfaceNxp::serialPort() const +{ + return m_serialPort->portName(); +} + +quint8 ZigbeeInterfaceNxp::calculateCrc(const QByteArray &data) +{ + quint8 crc = 0; + for(int i = 0; i < data.length(); i++) { + crc ^= data[i]; + } + + return crc; +} + +QByteArray ZigbeeInterfaceNxp::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 ZigbeeInterfaceNxp::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 ZigbeeInterfaceNxp::setAvailable(bool available) +{ + if (m_available == available) + return; + + // Clear the data buffer in any case + if (m_available) { + m_dataBuffer.clear(); + } + + m_available = available; + emit availableChanged(m_available); +} + +void ZigbeeInterfaceNxp::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 ZigbeeInterfaceNxp::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; + m_dataBuffer.clear(); + continue; + } + + // Checksum verified, we got valid data + qCDebug(dcZigbeeInterface()) << "Received frame" << ZigbeeUtils::convertByteArrayToHexString(frame); + emit packageReceived(package); + } + m_dataBuffer.clear(); + } else { + m_dataBuffer.append(data.at(i)); + } + } +} + +void ZigbeeInterfaceNxp::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 ZigbeeInterfaceNxp::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 + quint8 checksum = calculateCrc(package); + + QByteArray frame = package; + QDataStream frameStream(&frame, QIODevice::WriteOnly | QIODevice::Append); + frameStream.setByteOrder(QDataStream::LittleEndian); + frameStream << checksum; + + qCDebug(dcZigbeeInterface()) << "Send frame" << ZigbeeUtils::convertByteArrayToHexString(frame); + + // 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); + } +} + +bool ZigbeeInterfaceNxp::enable(const QString &serialPort, qint32 baudrate) +{ + qCDebug(dcZigbeeInterface()) << "Start UART interface " << serialPort << baudrate; + + if (m_serialPort) { + delete m_serialPort; + m_serialPort = nullptr; + } + + 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, &ZigbeeInterfaceNxp::onReadyRead); + connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onError(QSerialPort::SerialPortError)), Qt::QueuedConnection); + + if (!m_serialPort->open(QSerialPort::ReadWrite)) { + qCWarning(dcZigbeeInterface()) << "Could not open serial port" << serialPort << baudrate << m_serialPort->errorString(); + m_reconnectTimer->start(); + return false; + } + + qCDebug(dcZigbeeInterface()) << "Interface enabled successfully on" << serialPort << baudrate; + m_serialPort->clear(); + + setAvailable(true); + return true; +} + +void ZigbeeInterfaceNxp::reconnectController() +{ + if (!m_serialPort) + return; + + if (m_serialPort->isOpen()) + m_serialPort->close(); + + delete m_serialPort; + m_serialPort = nullptr; + setAvailable(false); + m_reconnectTimer->start(); +} + +void ZigbeeInterfaceNxp::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/backends/nxp/interface/zigbeeinterfacenxp.h b/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacenxp.h new file mode 100644 index 0000000..0d36ddf --- /dev/null +++ b/libnymea-zigbee/backends/nxp/interface/zigbeeinterfacenxp.h @@ -0,0 +1,83 @@ +/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * +* +* 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 ZIGBEEINTERFACENXP_H +#define ZIGBEEINTERFACENXP_H + +#include +#include +#include + +class ZigbeeInterfaceNxp : public QObject +{ + Q_OBJECT + +public: + enum ProtocolByte { + ProtocolByteEnd = 0xC0, + ProtocolByteEsc = 0xDB, + ProtocolByteTransposedEnd = 0xDC, + ProtocolByteTransposedEsc = 0xDD + }; + Q_ENUM(ProtocolByte) + + explicit ZigbeeInterfaceNxp(QObject *parent = nullptr); + ~ZigbeeInterfaceNxp(); + + bool available() const; + QString serialPort() const; + +private: + QTimer *m_reconnectTimer = nullptr; + QSerialPort *m_serialPort = nullptr; + bool m_available = false; + QByteArray m_dataBuffer; + + quint8 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 reconnectController(); + void disable(); + +}; + +#endif // ZIGBEEINTERFACENXP_H diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro index ef0b73a..0919e6a 100644 --- a/libnymea-zigbee/libnymea-zigbee.pro +++ b/libnymea-zigbee/libnymea-zigbee.pro @@ -8,6 +8,7 @@ SOURCES += \ backends/deconz/interface/zigbeeinterfacedeconzreply.cpp \ backends/deconz/zigbeebridgecontrollerdeconz.cpp \ backends/deconz/zigbeenetworkdeconz.cpp \ + backends/nxp/interface/zigbeeinterfacenxp.cpp \ zcl/general/zigbeeclusteridentify.cpp \ zcl/general/zigbeeclusterlevelcontrol.cpp \ zcl/general/zigbeeclusteronoff.cpp \ @@ -59,6 +60,7 @@ HEADERS += \ backends/deconz/interface/zigbeeinterfacedeconzreply.h \ backends/deconz/zigbeebridgecontrollerdeconz.h \ backends/deconz/zigbeenetworkdeconz.h \ + backends/nxp/interface/zigbeeinterfacenxp.h \ zcl/general/zigbeeclusteridentify.h \ zcl/general/zigbeeclusterlevelcontrol.h \ zcl/general/zigbeeclusteronoff.h \