/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
* 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 ^= static_cast(data.at(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));
//qCDebug(dcZigbeeInterfaceTraffic()) << "[in] " << ZigbeeUtils::convertByteToHexString(byte);
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() - 1);
quint8 receivedChecksum = frame.at(frame.length() - 1);
quint8 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);
// for (int i = 0; i < data.length(); i++) {
// qCDebug(dcZigbeeInterfaceTraffic()) << "[out]" << ZigbeeUtils::convertByteToHexString(data.at(i));
// }
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";
}