commit 70917203ca4e9a33f09eb75abfc0c3cb9df07520 Author: Simon Stürz Date: Tue Oct 17 14:54:17 2017 +0200 add debian folder for crossbuilder diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fab7372 --- /dev/null +++ b/.gitignore @@ -0,0 +1,73 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# qtcreator generated files +*.pro.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/core.cpp b/core.cpp new file mode 100644 index 0000000..7376347 --- /dev/null +++ b/core.cpp @@ -0,0 +1,9 @@ +#include "core.h" +#include + +Core::Core(QObject *parent) : + QObject(parent) +{ + + +} diff --git a/core.h b/core.h new file mode 100644 index 0000000..3e3777d --- /dev/null +++ b/core.h @@ -0,0 +1,23 @@ +#ifndef CORE_H +#define CORE_H + +#include + +#include "zigbeemanager.h" + +class Core : public QObject +{ + Q_OBJECT +public: + explicit Core(QObject *parent = nullptr); + +private: + ZigbeeManager *m_manager; + +signals: + +public slots: + +}; + +#endif // CORE_H diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..e64eefb --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +zigbee-daemon (0.0.1) xenial; urgency=medium + + * Initial release. + + -- Simon Stürz Mon, 16 Oct 2017 14:02:07 +0200 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..811518e --- /dev/null +++ b/debian/control @@ -0,0 +1,28 @@ +Source: zigbee-daemon +Section: utils +Priority: optional +Maintainer: Simon Stuerz +Standards-Version: 3.9.7 +Homepage: https://guh.io +Vcs-Git: https://github.com/guh/guh.git +Build-Depends: debhelper (>= 9.0.0), + dpkg-dev (>= 1.16.1~), + qt5-default, + qt5-qmake, + qtbase5-dev, + qtbase5-dev-tools, + libqt5serialport5-dev + +Package: zigbee-daemon +Architecture: any +Section: net +Multi-Arch: same +Depends: libqt5network5, + libqt5gui5, + libqt5serialport5, + ${shlibs:Depends}, + ${misc:Depends} +Description: Zigbee bridge controller - daemon + Description... + . + This package will install the daemon. diff --git a/debian/rules b/debian/rules new file mode 100755 index 0000000..7086797 --- /dev/null +++ b/debian/rules @@ -0,0 +1,21 @@ +#!/usr/bin/make -f +# -*- makefile -*- + +export DH_VERBOSE=1 +export QT_QPA_PLATFORM=minimal + +ifneq (,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) + DEB_PARALLEL_JOBS ?= $(patsubst parallel=%,%,$(filter parallel=%,$(DEB_BUILD_OPTIONS))) +else + DEB_PARALLEL_JOBS += $(shell getconf _NPROCESSORS_ONLN) +endif + +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/buildflags.mk + +%: + dh $@ --buildsystem=qmake --parallel + +override_dh_auto_build: + make -j$(DEB_PARALLEL_JOBS) + diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..89ae9db --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (native) diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..9cc984d --- /dev/null +++ b/main.cpp @@ -0,0 +1,11 @@ +#include +#include "core.h" + +int main(int argc, char *argv[]) +{ + QCoreApplication a(argc, argv); + + Core core; + + return a.exec(); +} diff --git a/zigbee-daemon.pro b/zigbee-daemon.pro new file mode 100644 index 0000000..794f98a --- /dev/null +++ b/zigbee-daemon.pro @@ -0,0 +1,22 @@ +QT -= gui +QT += serialport + +CONFIG += c++11 console +CONFIG -= app_bundle + +TARGET = zigbee-daemon + +target.path = /usr/bin +INSTALLS += target + +SOURCES += main.cpp \ + core.cpp \ + zigbeeinterface.cpp \ + zigbeemanager.cpp \ + zigbee.cpp + +HEADERS += \ + core.h \ + zigbeeinterface.h \ + zigbeemanager.h \ + zigbee.h diff --git a/zigbee.cpp b/zigbee.cpp new file mode 100644 index 0000000..98cead9 --- /dev/null +++ b/zigbee.cpp @@ -0,0 +1 @@ +#include "zigbee.h" diff --git a/zigbee.h b/zigbee.h new file mode 100644 index 0000000..ac4766c --- /dev/null +++ b/zigbee.h @@ -0,0 +1,15 @@ +#ifndef ZIGBEE_H +#define ZIGBEE_H + +#include + +class Zigbee +{ + Q_GADGET + +public: + + +}; + +#endif // ZIGBEE_H diff --git a/zigbeeinterface.cpp b/zigbeeinterface.cpp new file mode 100644 index 0000000..0969ed1 --- /dev/null +++ b/zigbeeinterface.cpp @@ -0,0 +1,242 @@ +#include "zigbeeinterface.h" + +#include + +ZigbeeInterface::ZigbeeInterface(QObject *parent) : + QObject(parent), + m_serialPort(nullptr), + m_readingState(WaitForStart) +{ + +} + +bool ZigbeeInterface::available() const +{ + return m_serialPort->isOpen(); +} + +QString ZigbeeInterface::convertByteToHexString(const quint8 &byte) +{ + QString hexString; + QString byteString = QString::number(byte, 16); + if (byteString.count() == 1) { + hexString = QString("0x0%1").arg(byteString); + } else { + hexString = QString("0x%1").arg(byteString); + } + return hexString.toStdString().data(); +} + +QString ZigbeeInterface::convertByteArrayToHexString(const QByteArray &byteArray) +{ + QString hexString; + for (int i = 0; i < byteArray.count(); i++) { + hexString.append(convertByteToHexString((quint8)byteArray.at(i))); + if (i != byteArray.count() -1) { + hexString.append(" "); + } + } + return hexString.toStdString().data(); +} + +QString ZigbeeInterface::convertByte16ToHexString(const quint16 &byte) +{ + quint8 msbByte = (byte >> 8) & 0xff; + quint8 lsbByte = (byte >> 0) & 0xff; + + return convertByteToHexString(msbByte) + convertByteToHexString(lsbByte).remove("0x"); +} + +quint8 ZigbeeInterface::calculateCrc(const quint16 &commandValue, const quint16 &lenghtValue, const QByteArray &data) +{ + quint8 crc = 0; + + crc ^= (commandValue >> 8) & 0xff; + crc ^= (commandValue >> 0) & 0xff; + + crc ^= (lenghtValue >> 8) & 0xff; + crc ^= (lenghtValue >> 0) & 0xff; + + for (int i = 0; i < lenghtValue; i++) { + crc ^= quint8(data.at(i)); + } + + return crc; +} + +void ZigbeeInterface::streamByte(quint8 byte, bool specialCharacter) +{ + if (!specialCharacter && byte < 0x10) { + // Byte stuffing ESC char before stuffed data byte + qDebug() << "[out]" << convertByteToHexString(0x02); + if (m_serialPort->write(QByteArray::fromRawData("\x02", 1)) < 0) { + qWarning() << "Could not stream ESC byte" << convertByteArrayToHexString(QByteArray::fromRawData("\x02", 1)); + } + + byte ^= 0x10; + } + qDebug() << "[out]" << convertByteToHexString(byte); + if (m_serialPort->write(QByteArray(1, (char)byte)) < 0) { + qWarning() << "Could not stream byte" << convertByteToHexString(byte); + } + m_serialPort->flush(); +} + +void ZigbeeInterface::setReadingState(const ZigbeeInterface::ReadingState &state) +{ + if (m_readingState == state) + return; + + m_readingState = state; + qDebug() << m_readingState; +} + +void ZigbeeInterface::onReadyRead() +{ + QByteArray data = m_serialPort->readAll(); + + // Parse serial data and built InterfaceMessage + for (int i = 0; i < data.length(); i++) { + quint8 byte = static_cast(data.at(i)); + + qDebug() << "[ in]" << convertByteToHexString(byte); + + switch (byte) { + case 0x01: + //qDebug() << "START message received"; + m_crcValue = 0; + m_commandValue = 0; + m_lengthValue = 0; + m_escapeDetected = false; + m_data.clear(); + + setReadingState(WaitForTypeMsb); + break; + case 0x02: + //qDebug() << "ESC char received"; + m_escapeDetected = true; + break; + case 0x03: { + quint8 crc = calculateCrc(m_commandValue, m_lengthValue, m_data); + if (crc != m_crcValue) { + qWarning() << "Invalid CRC value" << crc << "!=" << m_crcValue; + } else if (m_data.count() != m_lengthValue) { + qWarning() << "ERROR: Invalid data length" << m_data.count() << "!=" << m_lengthValue; + } else { + qDebug() << "<--" << (Type)m_commandValue << convertByte16ToHexString(m_commandValue) << "CRC:" << convertByteToHexString(m_crcValue) << convertByte16ToHexString(m_lengthValue) << convertByteArrayToHexString(m_data); + emit messageReceived((Type)m_commandValue, m_data); + } + setReadingState(WaitForStart); + break; + } + default: + + // If the previous byte was an escape character, XOR the byte with 0x10 according documentation + if (m_escapeDetected) { + byte ^= 0x10; + m_escapeDetected = false; + } + + switch (m_readingState) { + case WaitForStart: + break; + case WaitForTypeMsb: + m_commandValue = byte; + m_commandValue <<= 8; + setReadingState(WaitForTypeLsb); + break; + case WaitForTypeLsb: + m_commandValue |= byte; + //qDebug() << "Command:" << convertByte16ToHexString(m_commandValue) << (Type)m_commandValue; + setReadingState(WaitForLenghtMsb); + break; + case WaitForLenghtMsb: + m_lengthValue = byte; + m_lengthValue <<= 8; + setReadingState(WaitForLengthLsb); + break; + case WaitForLengthLsb: + m_lengthValue |= byte; + setReadingState(WaitForCrc); + break; + case WaitForCrc: + m_crcValue = byte; + setReadingState(WaitForData); + break; + case WaitForData: + m_data.append(static_cast(byte)); + break; + default: + break; + } + + break; + } + } +} + +void ZigbeeInterface::onError(const QSerialPort::SerialPortError &error) +{ + qWarning() << "Serial port error:" << error << m_serialPort->errorString(); +} + +bool ZigbeeInterface::enable(const QString &serialPort) +{ + if (m_serialPort) { + delete m_serialPort; + m_serialPort = nullptr; + } + + m_serialPort = new QSerialPort(serialPort, this); + m_serialPort->setBaudRate(QSerialPort::Baud115200); + m_serialPort->setDataBits(QSerialPort::Data8); + m_serialPort->setParity(QSerialPort::NoParity); + m_serialPort->setFlowControl(QSerialPort::NoFlowControl); + + connect(m_serialPort, &QSerialPort::readyRead, this, &ZigbeeInterface::onReadyRead); + connect(m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onError(QSerialPort::SerialPortError))); + + if (!m_serialPort->open(QSerialPort::ReadWrite)) { + qWarning() << "Could not open serial port" << serialPort; + delete m_serialPort; + m_serialPort = nullptr; + return false; + } + + qDebug() << "Interface enabled successfully on" << serialPort; + return true; +} + +void ZigbeeInterface::disable() +{ + if (!m_serialPort) + return; + + if (m_serialPort->isOpen()) + m_serialPort->close(); + + delete m_serialPort; + m_serialPort = nullptr; + qDebug() << "Interface disabled"; +} + +void ZigbeeInterface::sendCommand(const ZigbeeInterface::Type &commandType, const QByteArray &data) +{ + quint16 commandValue = static_cast(commandType); + quint16 lengthValue = static_cast(data.count()); + quint8 crcValue = calculateCrc(commandValue, lengthValue, data); + + qDebug() << "-->" << commandType << convertByte16ToHexString(commandValue) << "CRC:" << convertByteToHexString(crcValue) << convertByte16ToHexString(lengthValue) << convertByteArrayToHexString(data); + + streamByte(0x01, true); + streamByte((commandValue >> 8) & 0xff); + streamByte((commandValue >> 0) & 0xff); + streamByte((lengthValue >> 8) & 0xff); + streamByte((lengthValue >> 0) & 0xff); + streamByte(crcValue); + + for (int i = 0; i < data.count(); i++) { + streamByte(data.at(i)); + } + streamByte(0x03, true); +} diff --git a/zigbeeinterface.h b/zigbeeinterface.h new file mode 100644 index 0000000..4ddc8b0 --- /dev/null +++ b/zigbeeinterface.h @@ -0,0 +1,249 @@ +#ifndef ZIGBEEINTERFACE_H +#define ZIGBEEINTERFACE_H + +#include +#include + +class ZigbeeInterface : public QObject +{ + Q_OBJECT +public: + enum ReadingState { + WaitForStart, + WaitForTypeMsb, + WaitForTypeLsb, + WaitForLenghtMsb, + WaitForLengthLsb, + WaitForCrc, + WaitForData + }; + Q_ENUM(ReadingState) + + enum Type { + /* Common Commands */ + Status = 0x8000, + Logging = 0x8001, + + DataIndication = 0x8002, + + NodeClusterList = 0x8003, + NodeAttributeList = 0x8004, + NodeCommandIdList = 0x8005, + RestartProvisioned = 0x8006, + RestartFactoryNew = 0x8007, + GetVersion = 0x0010, + VersionList = 0x8010, + + SetExtendetPanId = 0x0020, + SetChannelMask = 0x0021, + SetSecurity = 0x0022, + SetDeviceType = 0x0023, + StartNetwork = 0x0024, + StartScan = 0x0025, + NetworkJoinedFormed = 0x8024, + NetworkRemoveDevice = 0x0026, + NetworkWhitelistEnable = 0x0027, + AuthenticateDeviceRequest = 0x0028, + AuthenticateDeviceResponse = 0x8028, + OutOfBandCommisioningDataRequest = 0x0029, + OutOfBandCommisioningDataResponse = 0x8029, + + Reset = 0x0011, + ErasePersistentData = 0x0012, + ZllFactoryNew = 0x0013, + GetPermitJoin = 0x0014, + GetPermitJoinResponse = 0x8014, + Bind = 0x0030, + BindResponse = 0x8030, + Unbind = 0x0031, + UnbindResponse = 0x8031, + + NetworkAdressRequest = 0x0040, + NetworkAdressResponse = 0x8040, + IeeeAddressResponse = 0x0041, + IeeeAddressRequest = 0x8041, + NodeDescriptorRequest = 0x0042, + NodeDescriptorRsponse = 0x8042, + SimpleDescriptorRequest = 0x0043, + SimpleDescriptorResponse = 0x8043, + PowerDescriptorRequest = 0x0044, + PowerDescriptorResponse = 0x8044, + ActiveEndpointRequest = 0x0045, + ActiveEndpointResponse = 0x8045, + MatchDescriptorRequest = 0x0046, + MatchDescriptorResponse = 0x8046, + ManagementLeaveRequest = 0x0047, + ManagementLeaveResponse = 0x8047, + LeaveIndication = 0x8048, + PermitJoiningRequest = 0x0049, + ManagementNetworkUpdateRequest = 0x004A, + ManagementNetworkUpdateResponse = 0x804A, + SystemServerDiscoveryRequest = 0x004B, + SystemServerDiscoveryResponse = 0x804B, + DeviceAnnounce = 0x004D, + ManagementLqiRequest = 0x004E, + ManagementLqiResponse = 0x804E, + + /* Group Cluster */ + AddGroupRequest = 0x0060, + AddGroupResponse = 0x8060, + ViewGroupRequest = 0x0061, + ViewGroupResponse = 0x8061, + GetGroupMembershipRequest = 0x0062, + GetGroupMembershipResponse = 0x8062, + RemoveGroupRequest = 0x0063, + RemoveGroupResponse = 0x8063, + RemoveAllGroups = 0x0064, + GroupIfIdentify = 0x0065, + + /* Identify Cluster */ + IdentifySend = 0x0070, + IdentifyQuery = 0x0071, + + /* Level Cluster */ + MoveToLevel = 0x0080, + MoveToLevelOnOff = 0x0081, + MoveStep = 0x0082, + MoveStopMove = 0x0083, + MoveStopMoveOnOff = 0x0084, + + /* Scenes Cluster */ + ViewScene = 0x00A0, + ViewSceneResponse = 0x80A0, + AddScene = 0x00A1, + AddSceneResponse = 0x80A1, + RemoveScene = 0x00A2, + RemoveSceneResponse = 0x80A2, + RemoveAllScenes = 0x00A3, + RemoveAllScenesResponse = 0x80A3, + StoreScene = 0x00A4, + StoreSceneResponse = 0x80A4, + RecallScene = 0x00A5, + SceneMembershipRequest = 0x00A6, + SceneMembershipResponse = 0x80A6, + + /* Colour Cluster */ + MoveToHue = 0x00B0, + MoveHue = 0x00B1, + StepHue = 0x00B2, + MoveToSaturation = 0x00B3, + MoveSaturation = 0x00B4, + StepStaturation = 0x00B5, + MoveToHueSaturation = 0x00B6, + MoveToColor = 0x00B7, + MoveColor = 0x00B8, + StepColor = 0x00B9, + + /* ZLL Commands */ + /* Touchlink */ + InitiateTouchlink = 0x00D0, + TouchlinkStatus = 0x00D1, + TouchlinkFactoryReset = 0x00D2, + + /* Identify Cluster */ + IdentifyTriggerEffect = 0x00E0, + + /* On/Off Cluster */ + CluserOnOff = 0x0092, + CluserOnOffTimed = 0x0093, + CluserOnOffEffects = 0x0094, + CluserOnOffUpdate = 0x8095, + + /* Scenes Cluster */ + AddEnhancedScene = 0x00A7, + ViewEnhancedScene = 0x00A8, + CopyScene = 0x00A9, + + /* Colour Cluster */ + EnhancedMoveToHue = 0x00BA, + EnhancedMoveHue = 0x00BB, + EnhancedStepHue = 0x00BC, + EnhancedMoveToHueSaturation = 0x00BD, + ColourLoopSet = 0x00BE, + StopMoveStep = 0x00BF, + MoveToColorTemperature = 0x00C0, + MoveColorTemperature = 0x00C1, + StepColorTemperature = 0x00C2, + + /* ZHA Commands */ + /* Door Lock Cluster */ + LockUnlockDoor = 0x00F0, + + /* Attributes */ + ReadAttributeRequest = 0x0100, + ReadAttributeResponse = 0x8100, + DefaultResponse = 0x8101, + AttributeReport = 0x8102, + WriteAttributeRequest = 0x0110, + WriteAttributeResponse = 0x8110, + ConfigReportingRequest = 0x0120, + ConfigReportingResponse = 0x8120, + ReportAttributes = 0x8121, + AttributeDiscoveryRequest = 0x0140, + AttributeDiscoveryResponse = 0x8140, + + /* Persistant data manager messages */ + DataManagerAvailableRequest = 0x0300, + DataManagerAvailableResponse = 0x8300, + DataManagerSaveRecordRequest = 0x0200, + DataManagerSaveRecordResponse = 0x8200, + DataManagerLoadRecordRequest = 0x0201, + DataManagerLoadRecordResponse = 0x8201, + DataManagerDeleteAllRecordsRequest = 0x0202, + DataManagerDeleteAllRecordsResponse = 0x8202, + + /* Appliance Statistics Cluster 0x0B03 */ + // http://www.nxp.com/documents/user_manual/JN-UG-3076.pdf + StatisticsClusterLogMessage = 0x0301, // Was 0x0500, was 0x0301 + StatisticsClusterLogMessageResponse = 0x8301, + + /* IAS Cluster */ + SendIasZoneEnroolResponse = 0x0400, + IasZoneStatusChangeNotify = 0x8401, + }; + Q_ENUM(Type) + + explicit ZigbeeInterface(QObject *parent = nullptr); + + bool available() const; + +private: + QSerialPort *m_serialPort; + QByteArray m_messageBuffer; + + // Message parsing + ReadingState m_readingState; + quint8 m_crcValue; + quint8 m_currentValue; + quint16 m_commandValue; + quint16 m_lengthValue; + QByteArray m_data; + bool m_escapeDetected; + + QString convertByteToHexString(const quint8 &byte); + QString convertByteArrayToHexString(const QByteArray &byteArray); + QString convertByte16ToHexString(const quint16 &byte); + + quint8 calculateCrc(const quint16 &commandValue, const quint16 &lenghtValue, const QByteArray &data); + + void streamByte(quint8 byte, bool specialCharacter = false); + + void setReadingState(const ReadingState & state); + +signals: + void availableChanged(const bool &available); + void messageReceived(const Type &commandType, const QByteArray &data = QByteArray()); + +private slots: + void onReadyRead(); + void onError(const QSerialPort::SerialPortError &error); + +public slots: + bool enable(const QString &serialPort = "/dev/ttyS0"); + void disable(); + + void sendCommand(const Type &commandType, const QByteArray &data = QByteArray()); + +}; + +#endif // ZIGBEEINTERFACE_H diff --git a/zigbeemanager.cpp b/zigbeemanager.cpp new file mode 100644 index 0000000..33f9822 --- /dev/null +++ b/zigbeemanager.cpp @@ -0,0 +1,27 @@ +#include "zigbeemanager.h" + +ZigbeeManager::ZigbeeManager(const QString &serialPort, QObject *parent) : + QObject(parent), + m_serialPort(serialPort) +{ + m_interface = new ZigbeeInterface(this); + + + m_interface->enable(m_serialPort); + m_interface->sendCommand(ZigbeeInterface::DataManagerAvailableResponse, QByteArray::fromRawData("\x00\x00", 2)); +} + +QString ZigbeeManager::serialPort() const +{ + return m_serialPort; +} + +void ZigbeeManager::setSerialPort(const QString &serialPort) +{ + if (m_serialPort == serialPort) + return; + + m_serialPort = serialPort; + m_interface->disable(); + m_interface->enable(m_serialPort); +} diff --git a/zigbeemanager.h b/zigbeemanager.h new file mode 100644 index 0000000..7039bad --- /dev/null +++ b/zigbeemanager.h @@ -0,0 +1,27 @@ +#ifndef ZIGBEEMANAGER_H +#define ZIGBEEMANAGER_H + +#include + +#include "zigbeeinterface.h" + +class ZigbeeManager : public QObject +{ + Q_OBJECT +public: + explicit ZigbeeManager(const QString &serialPort = "/dev/ttyS0", QObject *parent = nullptr); + + QString serialPort() const; + void setSerialPort(const QString &serialPort); + +private: + ZigbeeInterface *m_interface; + QString m_serialPort; + +signals: + +public slots: + +}; + +#endif // ZIGBEEMANAGER_H