diff --git a/README.md b/README.md
index 79d8087..df75fa1 100644
--- a/README.md
+++ b/README.md
@@ -3,19 +3,37 @@
This repository contains the nymea-zigbee library and tools.
-# Supported hardware
+nymea-zigbee is a general purpose ZigBee coordinator library to build ZigBee coordinators/gateways.
+The provided zigbee-cli is a minimal ZigBee coordinator implementation which allows to host a ZigBee
+network for devices to join and interact with each other but without interacting with the devices.
-Depending on your available hardware following gateway modules are supported
+For a full fetaured ZigBee coordinator/gateway implementation based on this library, please see
+https://github.com/nymea/nymea.
+
+
+# Supported ZigBee adapters
+
+## TI z-Stack
+
+All USB and serial port adapters based on the Texas Instruments CC1352/CC2652
+chipset are supported, provided they are flashed with the z-Stack coordinator firmware.
+
+Pre-built binaries of the firmware are provided by Koenkk:
+https://github.com/Koenkk/Z-Stack-firmware/tree/master/coordinator
## NXP
-> Note: the firmware erquires an entire rework and implement the APS layer
+The following NXP chip based adapters are supported, provided they are flashed with
+the nymea coordinator firmware found in this repository.
* JN5168 (SoM)
* JN5169 (USB Stick)
## deCONZ
+All deCONZ based adapters are supported, with the standard firmware preinstalled.
+It is recommended to update to the latest firmware.
+
* ConBee
* RaspBee
* ConBee II
diff --git a/debian/control b/debian/control
index 57ad2df..78b5a17 100644
--- a/debian/control
+++ b/debian/control
@@ -13,7 +13,8 @@ Build-Depends: debhelper (>= 9.0.0),
qtbase5-dev-tools,
libqt5sql5-sqlite,
libqt5serialport5-dev,
- libudev-dev
+ libudev-dev,
+ libqca-qt5-2-dev,
Package: libnymea-zigbee1
Section: libs
diff --git a/docs/ti/Z-Stack Monitor and Test API.pdf b/docs/ti/Z-Stack Monitor and Test API.pdf
new file mode 100644
index 0000000..52682ec
Binary files /dev/null and b/docs/ti/Z-Stack Monitor and Test API.pdf differ
diff --git a/libnymea-zigbee/backends/ti/interface/ti.h b/libnymea-zigbee/backends/ti/interface/ti.h
new file mode 100644
index 0000000..dab134f
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/interface/ti.h
@@ -0,0 +1,584 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 TI_H
+#define TI_H
+
+#include
+
+#define MT_RPC_DATA_MAX 250
+
+class Ti
+{
+ Q_GADGET
+
+public:
+ enum ZnpVersion {
+ zStack12 = 0x00,
+ zStack3x0 = 0x01,
+ zStack30x = 0x02
+ };
+ Q_ENUM(ZnpVersion)
+
+ enum ResetType {
+ ResetTypeHard = 0x00,
+ ResetTypeSoft = 0x01
+ };
+ Q_ENUM(ResetType)
+
+ enum ResetReason {
+ ResetReasonPowerUp = 0x00,
+ ResetReasonExternal = 0x01,
+ ResetReasonWatchDog = 0x02
+ };
+ Q_ENUM(ResetReason)
+
+ enum StartupMode {
+ StartupModeNormal = 0x00,
+ StartupModeClean = 0x03
+ };
+ Q_ENUM(StartupMode)
+
+ enum DeviceLogicalType {
+ DeviceLogicalTypeCoordinator = 0x00,
+ DeviceLogicalTypeRouter = 0x01,
+ DeviceLogicalTypeEndDevice = 0x02,
+ DeviceLogicalTypeComplexDescriptorAvailable = 0x04,
+ DeviceLogicalTypeUserDescriptorAvailable = 0x08,
+ DeviceLogicalTypeReserved1 = 0x10,
+ DeviceLogicalTypeReserved2 = 0x20,
+ DeviceLogicalTypeReserved3 = 0x40,
+ DeviceLogicalTypeReserved4 = 0x80,
+ };
+ Q_ENUM(DeviceLogicalType)
+
+ enum ControllerCapability {
+ ControllerCapabilityNone = 0x0000,
+ ControllerCapabilitySys = 0x0001,
+ ControllerCapabilityMAC = 0x0002,
+ ControllerCapabilityNWK = 0x0004,
+ ControllerCapabilityAF = 0x0008,
+ ControllerCapabilityZDO = 0x0010,
+ ControllerCapabilitySAPI = 0x0020,
+ ControllerCapabilityUtil = 0x0040,
+ ControllerCapabilityDebug = 0x0080,
+ ControllerCapabilityApp = 0x0100,
+ ControllerCapabilityZOAD = 0x1000
+ };
+ Q_DECLARE_FLAGS(ControllerCapabilities, ControllerCapability)
+ Q_FLAG(ControllerCapabilities)
+
+ enum StatusCode {
+ StatusCodeSuccess = 0x00,
+ StatusCodeFailure = 0x01,
+ StatusCodeBusy = 0x02,
+ StatusCodeTimeout = 0x03,
+ StatusCodeUnsupported = 0x04,
+ StatusCodeError = 0x05,
+ StatusCodeNoNetwork = 0x06,
+ StatusCodeInvalidValue = 0x07
+ };
+ Q_ENUM(StatusCode)
+
+ enum CommandType {
+ CommandTypePoll = 0x00,
+ CommandTypeSReq = 0x20,
+ CommandTypeAReq = 0x40,
+ CommandTypeSRsp = 0x60,
+ };
+ Q_ENUM(CommandType)
+
+ enum SubSystem {
+ SubSystemReserved = 0x00,
+ SubSystemSys = 0x01,
+ SubSystemMAC = 0x02,
+ SubSystemNwk = 0x03,
+ SubSystemAF = 0x04,
+ SubSystemZDO = 0x05,
+ SubSystemSAPI = 0x06,
+ SubSystemUtil = 0x07,
+ SubSystemDebug = 0x08,
+ SubSystemApp = 0x09,
+ SubSystemAppCnf = 0x0F,
+ SubSystemGreenPower = 0x15,
+ };
+ Q_ENUM(SubSystem)
+
+ enum SYSCommand {
+ SYSCommandResetReq = 0x00,
+ SYSCommandPing = 0x01,
+ SYSCommandVersion = 0x02,
+ SYSCommandSetExtAddress = 0x03,
+ SYSCommandGetExtAddress = 0x04,
+ SYSCommandRamRead = 0x05,
+ SYSCommandRamWrite = 0x06,
+ SYSCommandOsalNvItemInit = 0x07,
+ SYSCommandOsalNvRead = 0x08,
+ SYSCommandOsaNvWrite = 0x09,
+ SYSCommandOsalStartTimer = 0x0A,
+ SYSCommandOsalStopTimer = 0x0B,
+ SYSCommandOsalRandom = 0x0C,
+ SYSCommandAdcRead = 0x0D,
+ SYSCommandGpio = 0x0E,
+ SYSCommandStackTune = 0x0F,
+ SYSCommandSetTime = 0x10,
+ SYSCommandGetTime = 0x11,
+ SYSCommandOsalNvDelete = 0x12,
+ SYSCommandOsalNvLength = 0x13,
+ SYSCommandSetTxPower = 0x14,
+ SYSCommandJammerParameters = 0x15,
+ SYSCommandSnifferParameters = 016,
+ SYSCommandZdiagsInitStats = 0x17,
+ SYSCommandZdiagsClearStats = 0x18,
+ SYSCommandZdiagsGetStats = 0x19,
+ SYSCommandZdiagsRestoreStatsNv = 0x1A,
+ SYSCommandZdiagsSaveStatsToNv = 0x1B,
+ SYSCommandOsalNvReadExt = 0x1C,
+ SYSCommandOsalNvWriteExt = 0x01D,
+
+ SYSCommandNvCreate = 0x30,
+ SYSCommandNvDelete = 0x31,
+ SYSCommandNvLength = 0x32,
+ SYSCommandNvRead = 0x33,
+ SYSCommandNvWrite = 0x34,
+ SYSCommandNvUpdate = 0x35,
+ SYSCommandNvCompact = 0x36,
+
+ SYSCommandResetInd = 0x80,
+ SYSCommandOsalTimerExpired = 0x81,
+ SYSCommandJammerInd = 0x82
+ };
+ Q_ENUM(SYSCommand)
+
+ enum MACCommand {
+ MACCommandResetReq = 0x01,
+ MACCommandInit = 0x02,
+ MACCommandStartReq = 0x03,
+ MACCommandSyncReq = 0x04,
+ MACCommandDataReq = 0x05,
+ MACCommandAssociateReq = 0x06,
+ MACCommandDisassociateReq = 0x07,
+ MACCommandGetReq = 0x08,
+ MACCommandSetReq = 0x09,
+
+ MACCommandScanReq = 0x0C,
+ MACCommandPollReq = 0x0D,
+ MACCommandPurgeReq = 0x0E,
+ MACCommandSetRxGainReq = 0x0F,
+
+ MACCommandSecurityGetReq = 0x30,
+ MACCommandSecuritySetReq = 0x31,
+
+ MACCommandAssociateRsp = 0x50,
+ MACCommandOrphanRsp = 0x51,
+
+ MACCommandSyncLossInd = 0x80,
+ MACCommandAssociateInd = 0x81,
+ MACCommandAssociateCnf = 0x82,
+ MACCommandBeaconNotifyInd = 0x83,
+ MACCommandDataCnf = 0x84,
+ MACCommandDataInd = 0x85,
+ MACCommandDisassociateInd = 0x86,
+ MACCommandDisassociateCnf = 0x87,
+
+ MACCommandOrphanInd = 0x8A,
+ MACCommandPollCnf = 0x8B,
+ MACCommandScanCnf = 0x8C,
+ MACCommandCommStatusInd = 0x8D,
+ MACCommandStartCnf = 0x8E,
+ MACCommandRxEnableCnf = 0x8F,
+ MACCommandPurgeCnf = 0x90
+ };
+ Q_ENUM(MACCommand)
+
+ enum AFCommand {
+ AFCommandRegister = 0x00,
+ AFCommandDataRequest = 0x01,
+ AFCommandDataRequestExt = 0x02,
+ AFCommandDataRequestSrcRtg = 0x03,
+ AFCommandDelete = 0x04,
+
+ AFCommandInterPanCtl = 0x10,
+ AFCommandDataStore = 0x11,
+ AFCommandDataRetrieve = 0x12,
+ AFCommandApsfConfigSet = 0x13,
+ AFCommandApsfConfigGet = 0x14,
+
+ AFCommandDataConfirm = 0x80,
+ AFCommandIncomingMsg = 0x81,
+ AFCommandIncomingMsgExt = 0x82,
+ AFCommandReflectError = 0x83,
+ };
+ Q_ENUM(AFCommand)
+
+ enum ZDOCommand {
+ ZDOCommandNwwAddrReq = 0x00,
+ ZDOCommandIeeeAddrReq = 0x01,
+ ZDOCommandNodeDescReq = 0x02,
+ ZDOCommandPowerDescReq = 0x03,
+ ZDOCommandSimpleDescReq = 0x04,
+ ZDOCommandActiveEpReq = 0x05,
+ ZDOCommandMatchDescReq = 0x06,
+ ZDOCommandComplexDescReq = 0x07,
+ ZDOCommandUserDescReq = 0x08,
+
+ ZDOCommandEndDeviceAnnce = 0x0A,
+ ZDOCommandUserDescSet = 0x0B,
+ ZDOCommandServerDiscReq = 0x0C,
+ ZDOCommandEndDeviceTimeoutReq = 0x0D,
+
+ ZDOCommandEndDeviceBindReq = 0x20,
+ ZDOCommandBindReq = 0x21,
+ ZDOCommandUnbindReq = 0x22,
+ ZDOCommandSetLinkKey = 0x23,
+ ZDOCommandRemoveLinkKey = 0x24,
+ ZDOCommandGetLinkKey = 0x25,
+ ZDOCommandNwkDiscoveryReq = 0x26,
+ ZDOCommandJoinReq = 0x27,
+ ZDOCommandSendData = 0x28,
+ ZDOCommandNwkAddrOfInterestReq = 0x26,
+
+ ZDOCommandMgmtNwkDiscReq = 0x30,
+ ZDOCommandMgmtLqiReq = 0x31,
+ ZDOCommandMgmtRtgReq = 0x32,
+ ZDOCommandMgmtBindReq = 0x33,
+ ZDOCommandMgmtLeaveReq = 0x34,
+ ZDOCommandMgmtDirectJoinReq = 0x35,
+ ZDOCommandMgmtPermitJoinReq = 0x36,
+ ZDOCommandMgmtNwkUpdateReq = 0x37,
+
+ ZDOCommandMsgCbRegister = 0x3E,
+ ZDOCommandMsgCbRemove = 0x3F,
+ ZDOCommandStartupFromApp = 0x40,
+ ZDOCommandAutoFindDestination = 0x41,
+ ZDOCommandSecAddLinkKey = 0x42,
+ ZDOCommandSecEntryLookupExt = 0x43,
+ ZDOCommandSecDeviceRemove = 0x044,
+ ZDOCommandExtRouteDisc = 0x45,
+ ZDOCommandExtRouteCheck = 0x46,
+ ZDOCommandExtRemoveGroup = 0x47,
+ ZDOCommandExtRemoveAllGroup = 0x48,
+ ZDOCommandExtFindAllGroupsEndpoint = 0x49,
+ ZDOCommandExtFindGroup = 0x4A,
+ ZDOCommandExtAddGroup = 0x4B,
+ ZDOCommandExtCountAllGroups = 0xAC,
+ ZDOCommandExtRxIdle = 0xAD,
+ ZDOCommandExtUpdateNwkKey = 0xAE,
+ ZDOCommandExtSwitchNwkKey = 0xAF,
+ ZDOCommandExtNwkInfo = 0x50,
+ ZDOCommandExtSecApsRemoveReq = 0x51,
+ ZDOCommandForceContentratorChange = 0x52,
+ ZDOCommandExtSetParams = 0x53,
+
+ ZDOCommandNwkAddrRsp = 0x80,
+ ZDOCommandNwkIeeeAddrRsp = 0x81,
+ ZDOCommandNodeDescRsp = 0x82,
+ ZDOCommandPowerDescRsp = 0x83,
+ ZDOCommandSimpleDescRsp = 0x84,
+ ZDOCommandActiveEpRsp = 0x85,
+ ZDOCommandMatchDescRsp = 0x86,
+ ZDOCommandComplexDescRsp = 0x87,
+ ZDOCommandUserDescRsp = 0x88,
+ ZDOCommandUserDescConf = 0x89,
+ ZDOCommandServerDiscRsp = 0x8A,
+
+ ZDOCommandEndDeviceBindRsp = 0xA0,
+ ZDOCommandBindRsp = 0xA1,
+ ZDOCommandUnbindRsp = 0xA2,
+
+ ZDOCommandMgmtNwkDiscRsp = 0xB0,
+ ZDOCommandMgmtLqiRsp = 0xB1,
+ ZDOCommandMgmtRtgRsp = 0xB2,
+ ZDOCommandMgmtBindRsp = 0xB3,
+ ZDOCommandMgmtLeaveRsp = 0xB4,
+ ZDOCommandMgmtDirectJoinRsp = 0xB5,
+ ZDOCommandMgmtPermitJoinRsp = 0xB6,
+
+ ZDOCommandMgmtNwkUpdateNotify = 0xB8,
+
+ ZDOCommandStateChangeInd = 0xC0,
+ ZDOCommandEndDeviceAnnceInd = 0xC1,
+ ZDOCommandMatchNodeDscRsp = 0xC2,
+ ZDOCommandStatusErrorRsp = 0xC3,
+ ZDOCommandSrcRtgInd = 0xC4,
+ ZDOCommandBeaconNotifyInd = 0xC5,
+ ZDOCommandJoinCnf = 0xC6,
+ ZDOCommandNwkDiscoveryCnf = 0xC7,
+ ZDOCommandConcentratorIndCb = 0xC8,
+ ZDOCommandLeaveInd = 0xC9,
+ ZDOCommandTcDeviceInd = 0xCA,
+ ZDOCommandPermitJoinInd = 0xCB,
+
+ ZDOCommandSetRejoinParametersReq = 0xCC,
+ ZDOCommandMsgCbIncoming = 0xFF
+ };
+ Q_ENUM(ZDOCommand)
+
+ enum SAPICommand {
+ SAPICommandStartRequest = 0x00,
+ SAPICommandSystemReset = 0x09,
+ SAPICommandBindDevice = 0x01,
+ SAPICommandAllowBind = 0x02,
+ SAPICommandSendDataRequest = 0x03,
+ SAPICommandReadConfiguration = 0x04,
+ SAPICommandWriteConfiguration = 0x05,
+ SAPICommandGetDeviceInfo = 0x06,
+ SAPICommandFindDeviceRequest = 0x07,
+ SAPICommandPermitJoiningRequest = 0x08,
+
+ SAPICommandStartConfirm = 0x80,
+ SAPICommandBindConfirm = 0x81,
+ SAPICommandAllowBindConfirm = 0x82,
+ SAPICommandSendDataConfirm = 0x83,
+ SAPICommandFindDeviceConfirm = 0x84,
+
+ SAPICommandReceiveDataIndication = 0x87,
+ };
+ Q_ENUM(SAPICommand)
+
+ enum UtilCommand {
+ UtilCommandGetDeviceInfo = 0x00,
+ UtilCommandGetNvInfo = 0x01,
+ UtilCommandSetPanId = 0x02,
+ UtilCommandSetChannels = 0x03,
+ UtilCommandSetSecLevel = 0x04,
+ UtilCommandSetPreCfgKey = 0x05,
+ UtilCommandCallbackSubCmd = 0x06,
+ UtilCommandKeyEvent = 0x07,
+ UtilCommandTimeAlive = 0x09,
+ UtilCommandLedControl = 0x0A,
+
+ UtilCommandTestLoopback = 0x10,
+ UtilCommandDataReq = 0x11,
+
+ UtilCommandGpioSetDirection = 0x14,
+ UtilCommandGpioRead = 0x15,
+ UtilCommandGpioWrite = 0x16,
+
+ UtilCommandSrcMatchEnable = 0x20,
+ UtilCommandSrcMatchAddEntry = 0x21,
+ UtilCommandSrcMatchDelEntry = 0x22,
+ UtilCommandSrcMatchCheckSrcAddr = 0x23,
+ UtilCommandSrcMatchAckAllPending = 0x24,
+ UtilCommandSrcMatchCheckAllPending = 0x25,
+
+ UtilCommandAddrMgrExtAddrLookup = 0x40,
+ UtilCommandAddrMgrNwkAddrLookup = 0x41,
+
+ UtilCommandApsmeLinkKeyDataGet = 0x44,
+ UtilCommandApsmeLinkKeyNvIdGet = 0x45,
+
+ UtilCommandAssocCount = 0x48,
+ UtilCommandAssocFindDevice = 0x49,
+ UtilCommandAssocGetWithAddress = 0x4A,
+ UtilCommandApsmeRequestKeyCmd = 0x4B,
+ UtilCommandSrngGen = 0x4C,
+ UtilCommandBindAddKey = 0x4D,
+
+ UtilCommandAssocRemove = 0x63,
+ UtilCommandAssocAdd = 0x64,
+
+ UtilCommandZclKeyEstInitEst = 0x80,
+ UtilCommandZclKeyEstSign = 0x81,
+
+ UtilCommandSyncReq = 0xE0,
+ UtilCommandZclKeyEstablishInd = 0xE1,
+ };
+ Q_ENUM(UtilCommand)
+
+ enum DebugCommand {
+ DebugCommandSetThreshold = 0x00,
+ DebugCommandMsg = 0x80,
+ };
+ Q_ENUM(DebugCommand)
+
+ enum AppCommand {
+ AppCommandMsg = 0x00,
+ AppCommandUserTest = 0x01,
+
+ AppCommandTllTlInd = 0x81,
+ };
+ Q_ENUM(AppCommand)
+
+ enum AppCnfCommand {
+ AppCnfCommandBdbStartCommissioning = 0x05,
+ AppCnfCommandBdbSetChannel = 0x08,
+ AppCnfCommandBdbSetTcRequireKeyExchange = 0x09,
+
+ AppCnfCommandBdbCommissioningNotification = 0x80,
+
+ AppCnfCommandSetNwkFrameCounter = 0xFF
+ };
+ Q_ENUM(AppCnfCommand)
+
+ enum GreenPowerCommand {
+ GreenPowerCommandSecReq = 0x03,
+ };
+ Q_ENUM(GreenPowerCommand)
+
+ enum NvItemId {
+ NvItemIdExtAddr = 0x01,
+ NvItemIdBootCounter = 0x02,
+ NvItemIdStartupOption = 0x03,
+ NvItemIdStartDelay = 0x04,
+ NvItemIdNIB = 0x21,
+ NvItemIdDeviceList = 0x22,
+ NvItemIdAddrMgr = 0x23,
+ NvItemIdPollRate = 0x24,
+ NvItemIdQueuedPollRate = 0x25,
+ NvItemIdResponsePollRate = 0x26,
+ NvItemIdRejoinPollRate = 0x27,
+ NvItemIdDataRetries = 0x28,
+ NvItemIdPollFailureRetries = 0x29,
+ NvItemIdStackProfile = 0x2A,
+ NvItemIdIndirectMsgTimeout = 0x2B,
+ NvItemIdRouteExpiryTime = 0x2C,
+ NvItemIdExtendedPanId = 0x2D,
+ NvItemIdBcastRetries = 0x2E,
+ NvItemIdPassiveAckTimeout = 0x2F,
+ NvItemIdBcastDeliveryTime = 0x30,
+ NvItemIdNwkMode = 0x31,
+ NvItemIdConcentratorEnable = 0x32,
+ NvItemIdConcentratorDiscovery = 0x33,
+ NvItemIdConcentratorRadius = 0x34,
+
+ NvItemIdConcentratorRC = 0x36,
+ NvItemIdMwkMgrMode = 0x37,
+ NvItemIdSrcRtgExpiryTime = 0x38,
+ NvItemIdRouteDiscoveryTime = 0x39,
+ NvItemIdNwkActiveKeyInfo = 0x3A,
+ NvItemIdNwkAlternKeyInfo = 0x3B,
+ NvItemIdRouterOffAssocCleanup = 0x3C,
+ NvItemIdNwkLeaveReqAllowed = 0x3D,
+ NvItemIdNwkChildAgeEnable = 0x3E,
+ NvItemIdDeviceListKaTimeout = 0x3F,
+
+ NvItemIdBindingTable = 0x41,
+ NvItemIdGroupTable = 0x42,
+ NvItemIdApsFrameRetries = 0x43,
+ NvItemIdApsAckWaitDuration = 0x44,
+ NvItemIdApsAckWaitMultiplier = 0x45,
+ NvItemIdBindingTime = 0x46,
+ NvItemIdApsUseExtPanId = 0x47,
+ NvItemIdApsUseInsecureJoin = 0x48,
+ NvItemIdCommisionedNwkAddr = 0x49,
+ NvItemIdApsNonMemberRadious = 0x4B,
+ NvItemIdApsLinkKeyTable = 0x4C,
+ NvItemIdApsDuprejTimeoutInc = 0x4D,
+ NvItemIdApsDuprejTimeoutCount = 0x4E,
+ NvItemIdApsDuprejTableSize = 0x4F,
+ NvItemIdDiagnosticStats = 0x50,
+ NvItemIdBdbNodeIsOnANetwork = 0x55,
+ NvItemIdSecurityLevel = 0x61,
+ NvItemIdPreCfgKey = 0x62,
+ NvItemIdPreCfgKeysEnable = 0x63,
+ NvItemIdSecurityMode = 0x64,
+ NvItemIdSecurePermitJoin = 0x65,
+ NvItemIdApsLinkKeyType = 0x66,
+ NvItemIdApsAllowR19Security = 0x67,
+ NvItemIdImplicitCertificate = 0x69,
+ NvItemIdDevicePrivateKey = 0x6A,
+ NvItemIdCaPublicKey = 0x6B,
+ NvItemIdKeMaxDevices = 0x6C,
+ NvItemIdUseDefaultTclk = 0x6D,
+ NvItemIdRngCounter = 0x6F,
+ NvItemIdRandomSeed = 0x70,
+ NvItemIdTrustcenterAddr = 0x71,
+ NvItemIdLegacyNwkSecMaterialTableStart = 0x74, // Valid for <= Z-Stack 3.0.x
+ NvItemIdExNwkSecMaterialTable = 0x07, // Valid for >= Z-Stack 3.x.0
+ NvItemIdUserDesc = 0x81,
+ NvItemIdNwkKey = 0x82,
+ NvItemIdPanId = 0x83,
+ NvItemIdChanList = 0x84,
+ NvItemIdLeaveCtrl = 0x85,
+ NvItemIdScanDuration = 0x86,
+ NvItemIdLogicalType = 0x87,
+ NvItemIdNwkMgrMinTx = 0x88,
+ NvItemIdNwkMgrAddr = 0x89,
+
+ NvItemIdZdoDirectCb = 0x8F,
+ NvItemIdSceneTable = 0x91,
+ NvItemIdMinFreeNwkAddr = 0x92,
+ NvItemIdMaxFreeNwkAddr = 0x93,
+ NvItemIdMinFreeGrpId = 0x94,
+ NvItemIdMaxFreeGrpId = 0x95,
+ NvItemIdMinGrpIds = 0x96,
+ NvItemIdMaxGrpIds = 0x97,
+ NvItemIdOtaBlockReqDelay = 0x98,
+ NvItemIdSAPIEndpoint = 0xA1,
+ NvItemIdSASShortAddr = 0xB1,
+ NvItemIdSASExtPanId = 0xB2,
+ NvItemIdSASPanId = 0xB3,
+ NvItemIdSASChannelMask = 0xB4,
+ NvItemIdSASProtocolVer = 0xB5,
+ NvItemIdSASStackProfile = 0xB6,
+ NvItemIdSASStartupCtrl = 0xB7,
+ NvItemIdSASTcAddr = 0xC1,
+ NvItemIdSASTcMasterKey = 0xC2,
+ NvItemIdSASNwkKey = 0xC3,
+ NvItemIdSASUseInsecJoin = 0xC4,
+ NvItemIdSASPreCfgLinkKey = 0xC5,
+ NvItemIdSASNwkKeySeqNum = 0xC6,
+ NvItemIdSASNwkKeyType = 0xC7,
+ NvItemIdSASNwkMgrAddr = 0xC8,
+ NvItemIdSASCurrTcMasterKey = 0xD1,
+ NvItemIdSASCurrNwkKey = 0xD2,
+ NvItemIdSASCurrPreCfgLinkKey = 0xD3,
+
+ NvItemIdTclkSeed = 0x101,
+ NvItemIdLegacyTclkTableStart_12 = 0x101, // Keep it for Legacy 1.2 stack
+ NvItemIdLegacyTclkTableStart = 0x111, // Valid for <= Z-Stack 3.0.x
+ NvItemIdExTclkTable = 0x04, // Valid for >= Z-Stack 3.0.x
+ NvItemIdApsLinkKeyDataStart = 0x201,
+ NvItemIdApsLinkKeyDataEnd = 0x2FF,
+ NvItemIdDuplicateBindingTable = 0x300,
+ NvItemIdDuplicateDeviceList = 0x301,
+ NvItemIdDuplicateDeviceListKaTimeout = 0x302,
+ NvItemIdZnpHasConfiguredStack1 = 0xF00,
+ NvItemIdZnpHasConfiguredStack3 = 0x60,
+ NvItemIdZcdNvExApsKeyDataTable = 0x06,
+ NvItemIdZcdNvExAddrMgr = 0x01
+ };
+ Q_ENUM(NvItemId)
+
+ enum TxOption {
+ TxOptionNone = 0x00,
+ TxOptionsWildcardProfileId = 0x02,
+ TxOptionPreprocess = 0x04,
+ TxOptionLimitConcentrator = 0x08,
+ TxOptionApsAck = 0x10,
+ TxOptionSuppressRouteDiscovery = 0x20,
+ TxOptionApsSecurity = 0x40,
+ TxOptionSkipRouting = 0x80,
+ TxOptionBroadCastEndpoint = 0xFF
+ };
+ Q_DECLARE_FLAGS(TxOptions, TxOption)
+ Q_FLAG(TxOption)
+};
+
+
+#endif // TI_H
diff --git a/libnymea-zigbee/backends/ti/interface/zigbeeinterfaceti.cpp b/libnymea-zigbee/backends/ti/interface/zigbeeinterfaceti.cpp
new file mode 100644
index 0000000..06fe741
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/interface/zigbeeinterfaceti.cpp
@@ -0,0 +1,267 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 "zigbeeinterfaceti.h"
+#include "zigbee.h"
+#include "zigbeeutils.h"
+#include "loggingcategory.h"
+
+#include
+
+ZigbeeInterfaceTi::ZigbeeInterfaceTi(QObject *parent) : QObject(parent)
+{
+ m_reconnectTimer = new QTimer(this);
+ m_reconnectTimer->setSingleShot(true);
+ m_reconnectTimer->setInterval(5000);
+
+ connect(m_reconnectTimer, &QTimer::timeout, this, &ZigbeeInterfaceTi::onReconnectTimeout);
+}
+
+ZigbeeInterfaceTi::~ZigbeeInterfaceTi()
+{
+
+}
+
+bool ZigbeeInterfaceTi::available() const
+{
+ return m_available;
+}
+
+QString ZigbeeInterfaceTi::serialPort() const
+{
+ return m_serialPort->portName();
+}
+
+void ZigbeeInterfaceTi::sendMagicByte()
+{
+ QByteArray message;
+ QDataStream stream(&message, QIODevice::WriteOnly);
+ stream << static_cast(0xef);
+ m_serialPort->write(message);
+}
+
+void ZigbeeInterfaceTi::setDTR(bool dtr)
+{
+ m_serialPort->setDataTerminalReady(dtr);
+}
+
+void ZigbeeInterfaceTi::setRTS(bool rts)
+{
+ m_serialPort->setRequestToSend(rts);
+}
+
+quint8 ZigbeeInterfaceTi::calculateChecksum(const QByteArray &data)
+{
+ quint8 checksum = 0;
+ for (int i = 0; i < data.length(); i++) {
+ checksum ^= static_cast(data.at(i));
+ }
+ return checksum;
+}
+
+void ZigbeeInterfaceTi::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 ZigbeeInterfaceTi::onReconnectTimeout()
+{
+ if (m_serialPort && !m_serialPort->isOpen()) {
+ if (!m_serialPort->open(QSerialPort::ReadWrite)) {
+ setAvailable(false);
+ qCDebug(dcZigbeeInterface()) << "Interface reconnected failed" << m_serialPort->portName() << m_serialPort->baudRate();
+ m_reconnectTimer->start();
+ } else {
+ qCDebug(dcZigbeeInterface()) << "Interface reconnected successfully on" << m_serialPort->portName() << m_serialPort->baudRate();
+ m_serialPort->clear();
+ setAvailable(true);
+ }
+ }
+}
+
+void ZigbeeInterfaceTi::onReadyRead()
+{
+ m_dataBuffer.append(m_serialPort->readAll());
+ processBuffer();
+}
+
+void ZigbeeInterfaceTi::processBuffer()
+{
+ if (m_dataBuffer.isEmpty()) {
+ return;
+ }
+
+ qCDebug(dcZigbeeInterfaceTraffic()) << "<--" << m_dataBuffer.toHex();
+ // StartOfFrame
+ if (static_cast(m_dataBuffer.at(0)) != SOF) {
+ qCWarning(dcZigbeeInterface()) << "Data doesn't start with StartOfFrame byte 0xfe. Discarding data...";
+ m_dataBuffer.remove(0, 1);
+ processBuffer();
+ return;
+ }
+
+ // payload length
+ quint8 payloadLength = static_cast(m_dataBuffer[1]);
+ // Packet must be SOF + payload length field + CMD0 + CMD1 + payload length + Checksum
+ if (m_dataBuffer.length() < payloadLength + 5) {
+ qCDebug(dcZigbeeInterface()) << "Not enough data in buffer....";
+ return;
+ }
+ QByteArray packet = m_dataBuffer.left(5 + payloadLength);
+ m_dataBuffer.remove(0, 5 + payloadLength);
+
+ quint8 cmd0 = static_cast(packet[2]);
+ quint8 cmd1 = static_cast(packet[3]);
+ QByteArray payload = packet.mid(4, payloadLength);
+ quint8 checksum = packet.at(4 + payloadLength);
+
+ if (calculateChecksum(packet.mid(1, 3 + payloadLength)) != checksum) {
+ qCWarning(dcZigbeeInterface()) << "Checksum mismatch!";
+ processBuffer();
+ return;
+ }
+// qCDebug(dcZigbeeInterface()) << "packet received:" << payloadLength << cmd0 << command << payload.toHex(' ') << checksum;
+
+ Ti::SubSystem subSystem = static_cast(cmd0 & 0x1F);
+ Ti::CommandType type = static_cast(cmd0 & 0xE0);
+
+ emit packetReceived(subSystem, type, cmd1, payload);
+
+ // In case there's more...
+ processBuffer();
+
+}
+
+void ZigbeeInterfaceTi::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 ZigbeeInterfaceTi::sendPacket(Ti::CommandType type, Ti::SubSystem subSystem, quint8 command, const QByteArray &payload)
+{
+ if (!m_available) {
+ qCWarning(dcZigbeeInterface()) << "Can not send data. The interface is not available";
+ return;
+ }
+
+ quint8 cmd0 = type | subSystem;
+
+ // Build transport data
+ QByteArray data;
+ QDataStream stream(&data, QIODevice::WriteOnly);
+ stream << static_cast(SOF);
+ stream << static_cast(payload.length());
+ stream << cmd0;
+ stream << static_cast(command);
+ for (int i = 0; i < payload.length(); i++) {
+ stream << static_cast(payload.at(i));
+ }
+ stream << calculateChecksum(data.right(data.length() - 1));
+
+ // Send the data
+ qCDebug(dcZigbeeInterfaceTraffic()) << "-->" << data.toHex();
+ if (m_serialPort->write(data) < 0) {
+ qCWarning(dcZigbeeInterface()) << "Could not stream byte" << ZigbeeUtils::convertByteArrayToHexString(data);
+ }
+
+ //m_serialPort->flush();
+}
+
+bool ZigbeeInterfaceTi::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, &ZigbeeInterfaceTi::onReadyRead);
+ typedef void (QSerialPort::* errorSignal)(QSerialPort::SerialPortError);
+ connect(m_serialPort, static_cast(&QSerialPort::error), this, &ZigbeeInterfaceTi::onError);
+
+ 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 ZigbeeInterfaceTi::reconnectController()
+{
+ if (!m_serialPort)
+ return;
+
+ if (m_serialPort->isOpen())
+ m_serialPort->close();
+
+ delete m_serialPort;
+ m_serialPort = nullptr;
+ setAvailable(false);
+ m_reconnectTimer->start();
+}
+
+void ZigbeeInterfaceTi::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/ti/interface/zigbeeinterfaceti.h b/libnymea-zigbee/backends/ti/interface/zigbeeinterfaceti.h
new file mode 100644
index 0000000..fd9577e
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/interface/zigbeeinterfaceti.h
@@ -0,0 +1,80 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 ZIGBEEINTERFACETI_H
+#define ZIGBEEINTERFACETI_H
+
+#include
+#include
+#include
+#include "zigbeeinterfacetireply.h"
+
+#define SOF 0xFE
+
+class ZigbeeInterfaceTi : public QObject
+{
+ Q_OBJECT
+public:
+ explicit ZigbeeInterfaceTi(QObject *parent = nullptr);
+ ~ZigbeeInterfaceTi();
+
+ bool available() const;
+ QString serialPort() const;
+
+ void sendMagicByte();
+ void setDTR(bool dtr);
+ void setRTS(bool rts);
+
+ void sendPacket(Ti::CommandType type, Ti::SubSystem subSystem, quint8 command, const QByteArray &payload);
+
+public slots:
+ bool enable(const QString &serialPort = "/dev/ttyS0", qint32 baudrate = 38400);
+ void reconnectController();
+ void disable();
+
+signals:
+ void availableChanged(bool available);
+ void packetReceived(Ti::SubSystem subSystem, Ti::CommandType type, quint8 command, const QByteArray &payload);
+
+private slots:
+ void onReconnectTimeout();
+ void onReadyRead();
+ void onError(const QSerialPort::SerialPortError &error);
+ void processBuffer();
+
+private:
+ QTimer *m_reconnectTimer = nullptr;
+ QSerialPort *m_serialPort = nullptr;
+ bool m_available = false;
+ QByteArray m_dataBuffer;
+
+ quint8 calculateChecksum(const QByteArray &data);
+
+ void setAvailable(bool available);
+};
+
+#endif // ZIGBEEINTERFACETI_H
diff --git a/libnymea-zigbee/backends/ti/interface/zigbeeinterfacetireply.cpp b/libnymea-zigbee/backends/ti/interface/zigbeeinterfacetireply.cpp
new file mode 100644
index 0000000..cad4ba7
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/interface/zigbeeinterfacetireply.cpp
@@ -0,0 +1,103 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 "zigbeeinterfacetireply.h"
+
+quint8 ZigbeeInterfaceTiReply::command() const
+{
+ return m_command;
+}
+
+Ti::SubSystem ZigbeeInterfaceTiReply::subSystem() const
+{
+ return m_subSystem;
+}
+
+QByteArray ZigbeeInterfaceTiReply::requestPayload() const
+{
+ return m_requestPayload;
+}
+
+QByteArray ZigbeeInterfaceTiReply::responsePayload() const
+{
+ return m_responsePayload;
+}
+
+Ti::StatusCode ZigbeeInterfaceTiReply::statusCode() const
+{
+ return m_statusCode;
+}
+
+bool ZigbeeInterfaceTiReply::timendOut() const
+{
+ return m_timeout;
+}
+
+bool ZigbeeInterfaceTiReply::aborted() const
+{
+ return m_aborted;
+}
+
+void ZigbeeInterfaceTiReply::abort()
+{
+ m_timer->stop();
+ m_aborted = true;
+ emit finished();
+}
+
+ZigbeeInterfaceTiReply::ZigbeeInterfaceTiReply(QObject *parent, int timeout):
+ QObject(parent),
+ m_timer(new QTimer(this))
+{
+ m_timer->setInterval(timeout);
+ m_timer->setSingleShot(true);
+ connect(m_timer, &QTimer::timeout, this, &ZigbeeInterfaceTiReply::onTimeout);
+
+ // We'll auto-delete ourselves.
+ connect(this, &ZigbeeInterfaceTiReply::finished, this, &QObject::deleteLater, Qt::QueuedConnection);
+}
+
+ZigbeeInterfaceTiReply::ZigbeeInterfaceTiReply(Ti::SubSystem subSystem, quint8 command, QObject *parent, const QByteArray &requestPayload, int timeout) :
+ ZigbeeInterfaceTiReply(parent, timeout)
+{
+ m_subSystem = subSystem;
+ m_command = command;
+ m_requestPayload = requestPayload;
+}
+
+void ZigbeeInterfaceTiReply::finish(Ti::StatusCode statusCode)
+{
+ m_statusCode = statusCode;
+ emit finished();
+}
+
+void ZigbeeInterfaceTiReply::onTimeout()
+{
+ m_timeout = true;
+ emit timeout();
+ emit finished();
+}
diff --git a/libnymea-zigbee/backends/ti/interface/zigbeeinterfacetireply.h b/libnymea-zigbee/backends/ti/interface/zigbeeinterfacetireply.h
new file mode 100644
index 0000000..b9bec18
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/interface/zigbeeinterfacetireply.h
@@ -0,0 +1,88 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 ZIGBEEINTERFACETIREPLY_H
+#define ZIGBEEINTERFACETIREPLY_H
+
+#include
+#include
+
+#include "ti.h"
+#include "zigbeenetworkrequest.h"
+
+class ZigbeeInterfaceTiReply: public QObject
+{
+ Q_OBJECT
+
+ friend class ZigbeeBridgeControllerTi;
+
+public:
+ explicit ZigbeeInterfaceTiReply(QObject *parent = nullptr, int timeout = 5000);
+ explicit ZigbeeInterfaceTiReply(Ti::SubSystem subSystem, quint8 command, QObject *parent = nullptr, const QByteArray &requestPayload = QByteArray(), int timeout = 5000);
+
+ // Request content
+ Ti::SubSystem subSystem() const;
+ quint8 command() const;
+ QByteArray requestPayload() const;
+
+ QByteArray responsePayload() const;
+
+ // Response content
+ Ti::StatusCode statusCode() const;
+
+ bool timendOut() const;
+ bool aborted() const;
+ void abort();
+
+signals:
+ void timeout();
+ void finished();
+
+private slots:
+ void onTimeout();
+ void finish(Ti::StatusCode statusCode = Ti::StatusCodeSuccess);
+
+private:
+ QTimer *m_timer = nullptr;
+ bool m_timeout = false;
+ bool m_aborted = false;
+
+ void setSequenceNumber(quint8 sequenceNumber);
+
+ // Request content
+ Ti::SubSystem m_subSystem = Ti::SubSystemSys;
+ quint8 m_command = 0;
+ QByteArray m_requestPayload;
+
+ // Response content
+ Ti::StatusCode m_statusCode = Ti::StatusCodeError;
+ QByteArray m_responsePayload;
+
+
+};
+
+#endif // ZIGBEEINTERFACETIREPLY_H
diff --git a/libnymea-zigbee/backends/ti/zigbeebridgecontrollerti.cpp b/libnymea-zigbee/backends/ti/zigbeebridgecontrollerti.cpp
new file mode 100644
index 0000000..b05d5fb
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/zigbeebridgecontrollerti.cpp
@@ -0,0 +1,1057 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 "zigbeeutils.h"
+#include "loggingcategory.h"
+#include "zigbeechannelmask.h"
+#include "zdo/zigbeedeviceprofile.h"
+#include "zigbeebridgecontrollerti.h"
+
+#include
+#include
+
+#define NEW_PAYLOAD QByteArray payload; QDataStream stream(&payload, QIODevice::WriteOnly); stream.setByteOrder(QDataStream::LittleEndian);
+#define PAYLOAD_STREAM(x) QDataStream stream(x); stream.setByteOrder(QDataStream::LittleEndian);
+
+ZigbeeBridgeControllerTi::ZigbeeBridgeControllerTi(QObject *parent) :
+ ZigbeeBridgeController(parent)
+{
+ m_interface = new ZigbeeInterfaceTi(this);
+ connect(m_interface, &ZigbeeInterfaceTi::availableChanged, this, &ZigbeeBridgeControllerTi::onInterfaceAvailableChanged);
+ connect(m_interface, &ZigbeeInterfaceTi::packetReceived, this, &ZigbeeBridgeControllerTi::onInterfacePacketReceived);
+
+ m_permitJoinTimer.setSingleShot(true);
+ connect(&m_permitJoinTimer, &QTimer::timeout, this, [=]{emit permitJoinStateChanged(0);});
+}
+
+ZigbeeBridgeControllerTi::~ZigbeeBridgeControllerTi()
+{
+ qCDebug(dcZigbeeController()) << "Destroying controller";
+}
+
+TiNetworkConfiguration ZigbeeBridgeControllerTi::networkConfiguration() const
+{
+ return m_networkConfiguration;
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::setLed(bool on)
+{
+ NEW_PAYLOAD;
+ stream << static_cast(0x03); // LED ID
+ stream << static_cast(on);
+ return sendCommand(Ti::SubSystemUtil, Ti::UtilCommandLedControl, payload);
+}
+
+ZigbeeInterfaceTiReply* ZigbeeBridgeControllerTi::reset()
+{
+ NEW_PAYLOAD
+ stream << static_cast(Ti::ResetTypeSoft);
+ ZigbeeInterfaceTiReply *resetReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandResetReq, payload);
+ waitFor(resetReply, Ti::SubSystemSys, Ti::SYSCommandResetInd);
+ return resetReply;
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::init()
+{
+ ZigbeeInterfaceTiReply *initReply = new ZigbeeInterfaceTiReply(this, 10000);
+
+ ZigbeeInterfaceTiReply *resetReply = reset();
+ connect(resetReply, &ZigbeeInterfaceTiReply::finished, initReply, [=]() {
+
+ qCDebug(dcZigbeeController()) << "Skipping CC2530/CC2531 bootloader.";
+ m_interface->sendMagicByte();
+
+ QTimer::singleShot(1000, initReply, [=]{
+ qCDebug(dcZigbeeController()) << "Trying to ping controller.";
+ ZigbeeInterfaceTiReply *pingReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandPing, QByteArray(), 1000);
+ connect(pingReply, &ZigbeeInterfaceTiReply::finished, initReply, [=]() {
+ if (pingReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Error pinging controller.";
+
+ qCDebug(dcZigbeeInterface()) << "Skipping CC2652/CC1352 bootloader.";
+ m_interface->setDTR(false);
+ m_interface->setRTS(false);
+ QTimer::singleShot(150, initReply, [=]{
+ m_interface->setRTS(true);
+ QTimer::singleShot(150, initReply, [=]{
+ m_interface->setRTS(false);
+ QTimer::singleShot(150, initReply, [=]{
+ initPhase2(initReply, 0);
+ });
+ });
+ });
+ return;
+ }
+
+ qCDebug(dcZigbeeController()) << "Controller ping succeeded.";
+ initPhase2(initReply, 0);
+ });
+ });
+ });
+
+ return initReply;
+}
+
+void ZigbeeBridgeControllerTi::initPhase2(ZigbeeInterfaceTiReply *initReply, int attempt)
+{
+ qCDebug(dcZigbeeController()) << "Trying to ping controller... (" << (attempt + 1) << "/ 10 )";
+ ZigbeeInterfaceTiReply *pingReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandPing, QByteArray(), 1000);
+
+ connect(pingReply, &ZigbeeInterfaceTiReply::finished, initReply, [=]() {
+ if (pingReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Error pinging controller.";
+ if (attempt < 9) {
+ initPhase2(initReply, attempt+1);
+ } else {
+ qCWarning(dcZigbeeController()) << "Giving up...";
+ initReply->finish(Ti::StatusCodeFailure);
+ }
+ return;
+ }
+
+ PAYLOAD_STREAM(pingReply->responsePayload());
+ quint16 caps;
+ stream >> caps;
+ Ti::ControllerCapabilities capabilities = static_cast(caps);
+ qCDebug(dcZigbeeController()) << "Controller ping succeeded! Capabilities:" << capabilities;
+
+ Ti::ControllerCapabilities requiredCapabilities = Ti::ControllerCapabilityNone;
+ requiredCapabilities |= Ti::ControllerCapabilitySys;
+ requiredCapabilities |= Ti::ControllerCapabilityUtil;
+ requiredCapabilities |= Ti::ControllerCapabilityZDO;
+ requiredCapabilities |= Ti::ControllerCapabilityAF;
+
+ if ((capabilities & requiredCapabilities) != requiredCapabilities) {
+ qCCritical(dcZigbeeController()) << "Controller doesn't support all required capabilities:" << capabilities;
+ initReply->finish(Ti::StatusCodeUnsupported);
+ return;
+ }
+
+ qCDebug(dcZigbeeController()) << "Fetching firmware information...";
+ ZigbeeInterfaceTiReply *versionReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandVersion);
+ connect(versionReply, &ZigbeeInterfaceTiReply::finished, initReply, [=]() {
+ if (versionReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeInterface()) << "Error reading controller version";
+ initReply->finish(versionReply->statusCode());
+ return;
+ }
+ PAYLOAD_STREAM(versionReply->responsePayload());
+ quint8 transportRevision, product, majorRelease, minorRelease, maintRelease;
+ quint32 revision;
+ stream >> transportRevision >> product >> majorRelease >> minorRelease >> maintRelease >> revision;
+ qCDebug(dcZigbeeNetwork()).nospace().noquote() << "Controller versions: Transport rev: " << transportRevision << " Product: " << product
+ << " Version: " << majorRelease << "." << minorRelease << "." << maintRelease
+ << " Revision: " << revision;
+
+ m_networkConfiguration.znpVersion = static_cast(product);
+ setFirmwareVersion(QString("%0(%1) - %2.%3.%4.%5")
+ .arg(QMetaEnum::fromType().valueToKey(product))
+ .arg(transportRevision)
+ .arg(majorRelease)
+ .arg(minorRelease)
+ .arg(maintRelease)
+ .arg(revision));
+
+ qCDebug(dcZigbeeController()) << "Reading IEEE address";
+
+ ZigbeeInterfaceTiReply *getIeeeAddrReply = readNvItem(Ti::NvItemIdPanId);
+ connect(getIeeeAddrReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+
+ ZigbeeInterfaceTiReply *getExtAddrReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandGetExtAddress);
+ connect(getExtAddrReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ if (getExtAddrReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Call to getDeviceInfo failed:" << getExtAddrReply->statusCode();
+ initReply->finish(getExtAddrReply->statusCode());
+ return;
+ }
+
+ PAYLOAD_STREAM(getExtAddrReply->responsePayload());
+ quint64 ieeeAddress;
+ stream >> ieeeAddress;
+ m_networkConfiguration.ieeeAddress = ZigbeeAddress(ieeeAddress);
+ qCDebug(dcZigbeeController()) << "IEEE address:" << m_networkConfiguration.ieeeAddress.toString();
+ initReply->finish();
+
+ m_controllerState = ControllerStateInitialized;
+ emit controllerStateChanged(ControllerStateInitialized);
+ });
+ });
+ });
+ });
+}
+
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::commission(Ti::DeviceLogicalType deviceType, quint16 panId, const ZigbeeChannelMask &channelMask)
+{
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(this, 30000);
+
+ ZigbeeInterfaceTiReply *resetReply = factoryReset();
+ connect(resetReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ // Make sure the controller is set to normal startup mode, so it will keep the commissioned settings on next reboot
+ NEW_PAYLOAD;
+ stream << static_cast(Ti::StartupModeNormal);
+ ZigbeeInterfaceTiReply *startupOptionReply = writeNvItem(Ti::NvItemIdStartupOption, payload);
+ connect(startupOptionReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << static_cast(deviceType);
+ ZigbeeInterfaceTiReply *deviceTypeReply = writeNvItem(Ti::NvItemIdLogicalType, payload);
+ connect(deviceTypeReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << static_cast(0x01);
+ ZigbeeInterfaceTiReply *deviceTypeReply = writeNvItem(Ti::NvItemIdZdoDirectCb, payload);
+ connect(deviceTypeReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << panId;
+ ZigbeeInterfaceTiReply *panIdReply = writeNvItem(Ti::NvItemIdPanId, payload);
+ connect(panIdReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << ZigbeeUtils::generateRandomPanId();
+ stream << ZigbeeUtils::generateRandomPanId();
+ stream << ZigbeeUtils::generateRandomPanId();
+ stream << ZigbeeUtils::generateRandomPanId();
+ ZigbeeInterfaceTiReply *panIdReply = writeNvItem(Ti::NvItemIdExtendedPanId, payload);
+ connect(panIdReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ ZigbeeInterfaceTiReply *panIdReply = writeNvItem(Ti::NvItemIdApsUseExtPanId, payload);
+ connect(panIdReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << channelMask.toUInt32();
+ ZigbeeInterfaceTiReply *channelsReply = writeNvItem(Ti::NvItemIdChanList, payload);
+ connect(channelsReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ // TODO: commission nwk key
+ // The adapter will generate a key, but we could provision our own so we could re-apply when restoring a backup
+// NEW_PAYLOAD;
+// stream << static_cast(0x01);
+// ZigbeeInterfaceTiReply *channelsReply = writeNvItem(Ti::NvItemIdPreCfgKeysEnable, payload);
+// connect(channelsReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+// NEW_PAYLOAD;
+// stream << <128 bit data>;
+// ZigbeeInterfaceTiReply *channelsReply = writeNvItem(Ti::NvItemIdPreCfgKey, payload);
+// connect(channelsReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ // For zStack12 we're done here.
+ if (m_networkConfiguration.znpVersion == Ti::zStack12) {
+ reply->finish();
+ return;
+ }
+
+ // zStack3x requires channels to be commissioned via AppCnf subsystem BdbCommissioning
+ NEW_PAYLOAD;
+ stream << static_cast(1); // Primary channel
+ stream << static_cast(channelMask.toUInt32());
+ ZigbeeInterfaceTiReply *commissionReply = sendCommand(Ti::SubSystemAppCnf, Ti::AppCnfCommandBdbSetChannel, payload);
+ connect(commissionReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << static_cast(0); // Non-primary channel
+ stream << static_cast(0);
+ ZigbeeInterfaceTiReply *commissionReply = sendCommand(Ti::SubSystemAppCnf, Ti::AppCnfCommandBdbSetChannel, payload);
+ connect(commissionReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << static_cast(0x04);;
+ ZigbeeInterfaceTiReply *commissionReply = sendCommand(Ti::SubSystemAppCnf, Ti::AppCnfCommandBdbStartCommissioning, payload);
+ connect(commissionReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ reply->finish();
+ });
+ });
+ });
+// });
+// });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ });
+ return reply;
+}
+
+void ZigbeeBridgeControllerTi::postStartup()
+{
+ // Reading Network Info
+ ZigbeeInterfaceTiReply *networkInfoReply = sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandExtNwkInfo);
+ connect(networkInfoReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ if (networkInfoReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Failed to read network info" << networkInfoReply->statusCode();
+ return;
+ }
+ quint8 devState, channel;
+ quint16 shortAddr, panId, parentAddr;
+ quint64 extendedPanId, parentExtAddr;
+ {
+ PAYLOAD_STREAM(networkInfoReply->responsePayload());
+ stream >> shortAddr >> devState >> panId >> parentAddr >> extendedPanId >> parentExtAddr >> channel;
+ }
+
+ m_networkConfiguration.panId = panId;
+ m_networkConfiguration.extendedPanId = extendedPanId;
+ m_networkConfiguration.currentChannel = channel;
+ m_networkConfiguration.shortAddress = shortAddr;
+ qCDebug(dcZigbeeController()) << "PAN ID:" << ZigbeeUtils::convertUint16ToHexString(m_networkConfiguration.panId);
+ qCDebug(dcZigbeeController()) << "Short addr:" << ZigbeeUtils::convertUint16ToHexString(m_networkConfiguration.shortAddress);
+ qCDebug(dcZigbeeController()) << "Extended Pan ID:" << ZigbeeUtils::convertUint64ToHexString(m_networkConfiguration.extendedPanId);
+ qCDebug(dcZigbeeController()) << "Device state:" << devState;
+ qCDebug(dcZigbeeController()) << "Channel:" << channel;
+ qCDebug(dcZigbeeController()) << "IEEE address:" << m_networkConfiguration.ieeeAddress.toString();
+
+ // Registering for the ZDO raw message callback
+ NEW_PAYLOAD;
+ stream << static_cast(ZigbeeClusterLibrary::ClusterIdUnknown);
+ ZigbeeInterfaceTiReply *registerCallbackReply = sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandMsgCbRegister, payload);
+ connect(registerCallbackReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ if (registerCallbackReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeInterface()) << "Failed to register ZDO msg callback";
+ return;
+ }
+ qCDebug(dcZigbeeController()) << "ZDO message callback registered";
+
+ // Fetching active endpoints from controller
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(this);
+ NEW_PAYLOAD;
+ stream << static_cast(0x0000); // dstaddr
+ stream << static_cast(0x0000); // networkOfInterest
+ sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandActiveEpReq, payload);
+ waitFor(reply, Ti::SubSystemZDO, Ti::ZDOCommandActiveEpRsp);
+ connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ PAYLOAD_STREAM(reply->responsePayload());
+ quint8 status, activeEpCount;
+ quint16 srcAddr, nwkAddr;
+ stream >> srcAddr >> status >> nwkAddr >> activeEpCount;
+
+ for (int i = 0; i < activeEpCount; i++) {
+ quint8 endpointId;
+ stream >> endpointId;
+ m_registeredEndpointIds.append(endpointId);
+ }
+
+ m_controllerState = ControllerStateRunning;
+ emit controllerStateChanged(ControllerStateRunning);
+ });
+
+ });
+ });
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::factoryReset()
+{
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(this);
+
+ // Setting startup option to 4 to perform a clear on reset
+ // Sending a reset request
+ // Setting startup option back to 0 to boot into normal mode again
+
+ ZigbeeInterfaceTiReply *deleteNIBReply = deleteNvItem(Ti::NvItemIdNIB);
+ connect(deleteNIBReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << (quint8)Ti::StartupModeClean;
+ ZigbeeInterfaceTiReply *writeStartupOptionReply = writeNvItem(Ti::NvItemIdStartupOption, payload);
+ connect(writeStartupOptionReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ if (writeStartupOptionReply->statusCode() != Ti::StatusCodeSuccess) {
+ reply->finish(writeStartupOptionReply->statusCode());
+ return;
+ }
+
+ ZigbeeInterfaceTiReply *resetReply = reset();
+ connect(resetReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+
+ NEW_PAYLOAD;
+ stream << (quint8)Ti::StartupModeNormal;
+ ZigbeeInterfaceTiReply *writeStartupOptionReply = writeNvItem(Ti::NvItemIdStartupOption, payload);
+ connect(writeStartupOptionReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ reply->finish(writeStartupOptionReply->statusCode());
+ });
+ });
+ });
+ });
+
+ return reply;
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::requestSendRequest(const ZigbeeNetworkRequest &request)
+{
+ Ti::TxOptions tiTxOptions = Ti::TxOptionNone;
+ tiTxOptions |= (request.txOptions().testFlag(Zigbee::ZigbeeTxOptionAckTransmission) ? Ti::TxOptionApsAck : Ti::TxOptionNone);
+ tiTxOptions |= (request.txOptions().testFlag(Zigbee::ZigbeeTxOptionSecurityEnabled) ? Ti::TxOptionApsSecurity : Ti::TxOptionNone);
+
+ NEW_PAYLOAD;
+ stream << static_cast(request.destinationAddressMode());
+ if (request.destinationAddressMode() == Zigbee::DestinationAddressModeIeeeAddress) {
+ stream << request.destinationIeeeAddress().toUInt64();
+ } else {
+ stream << static_cast(request.destinationShortAddress());
+ }
+
+ stream << request.destinationEndpoint();
+ stream << static_cast(0x0000); // Intra-pan
+ stream << request.sourceEndpoint();
+ stream << request.clusterId();
+ stream << request.requestId();
+ stream << static_cast(tiTxOptions);
+ stream << request.radius();
+ stream << static_cast(request.asdu().length());
+
+ QByteArray asdu = request.asdu();
+
+ // If the the entire packet fits into the MTU, can send it as is
+ // otherwise we'll have to send the packet without payload and provide the payload using StoreData instead
+ if (asdu.length() < MT_RPC_DATA_MAX - 20) {
+ for (int i = 0; i < asdu.length(); i++) {
+ stream << static_cast(asdu.at(i));
+ }
+ return sendCommand(Ti::SubSystemAF, Ti::AFCommandDataRequestExt, payload);
+ }
+
+ // NOTE: Leaving those prints as warnings for now as I didn't get the chance to test this much
+ // so if anything goes wrong, it would appear in the logs unconditionally.
+ qCWarning(dcZigbeeController()) << "Splitting huge packet into chunks!";
+ qCWarning(dcZigbeeController()) << "Full packet payload:" << asdu.toHex() << "LEN:" << asdu.length();
+ ZigbeeInterfaceTiReply *lastReply = nullptr;
+ int i = 0;
+ while (!asdu.isEmpty()) {
+ QByteArray chunk = asdu.left(qMin(asdu.length(), 252));
+ asdu.remove(0, chunk.size());
+ qCWarning(dcZigbeeController()) << "Chunk" << i << ":" << chunk.toHex() << "LEN:" << chunk.length();
+
+ NEW_PAYLOAD;
+ stream << static_cast(i++);
+ stream << static_cast(chunk.length());
+ lastReply = sendCommand(Ti::SubSystemAF, Ti::AFCommandDataStore, chunk);
+ }
+ return lastReply;
+}
+
+void ZigbeeBridgeControllerTi::sendNextRequest()
+{
+ // Check if there is a reply request to send
+ if (m_replyQueue.isEmpty())
+ return;
+
+ // Check if there is currently a running reply
+ if (m_currentReply)
+ return;
+
+ m_currentReply = m_replyQueue.dequeue();
+ qCDebug(dcZigbeeController()) << "-->" << m_currentReply->subSystem() << QHash({
+ { Ti::SubSystemSys, QMetaEnum::fromType() },
+ { Ti::SubSystemMAC, QMetaEnum::fromType() },
+ { Ti::SubSystemAF, QMetaEnum::fromType() },
+ { Ti::SubSystemZDO, QMetaEnum::fromType() },
+ { Ti::SubSystemSAPI, QMetaEnum::fromType() },
+ { Ti::SubSystemUtil, QMetaEnum::fromType() },
+ { Ti::SubSystemDebug, QMetaEnum::fromType() },
+ { Ti::SubSystemApp, QMetaEnum::fromType() },
+ { Ti::SubSystemAppCnf, QMetaEnum::fromType() },
+ { Ti::SubSystemGreenPower, QMetaEnum::fromType() }
+ }).value(m_currentReply->subSystem()).valueToKey(m_currentReply->command())
+ << m_currentReply->requestPayload().toHex();
+
+ m_interface->sendPacket(Ti::CommandTypeSReq, m_currentReply->subSystem(), m_currentReply->command(), m_currentReply->requestPayload());
+ m_currentReply->m_timer->start();
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::sendCommand(Ti::SubSystem subSystem, quint8 command, const QByteArray &payload, int timeout)
+{
+ // Create the reply
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(subSystem, command, this, payload, timeout);
+
+ // Make sure we clean up on timeout
+ connect(reply, &ZigbeeInterfaceTiReply::timeout, this, [reply](){
+ qCWarning(dcZigbeeController()) << "Reply timeout" << reply;
+ // Note: send next reply with the finished signal
+ });
+
+ // Auto delete the object on finished
+ connect(reply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply](){
+ if (m_currentReply == reply) {
+ m_currentReply = nullptr;
+ QMetaObject::invokeMethod(this, "sendNextRequest", Qt::QueuedConnection);
+ }
+ });
+
+ m_replyQueue.enqueue(reply);
+
+ QMetaObject::invokeMethod(this, "sendNextRequest", Qt::QueuedConnection);
+ return reply;
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::readNvItem(Ti::NvItemId itemId, quint16 offset)
+{
+ NEW_PAYLOAD;
+ stream << static_cast(itemId);
+ stream << offset;
+ return sendCommand(Ti::SubSystemSys, Ti::SYSCommandOsalNvReadExt, payload);
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::writeNvItem(Ti::NvItemId itemId, const QByteArray &data, quint16 offset)
+{
+ qCDebug(dcZigbeeController()) << "Writing NV item:" << itemId << data.toHex();
+ NEW_PAYLOAD;
+ stream << static_cast(itemId);
+ stream << offset;
+ stream << static_cast(data.length());
+ payload.append(data);
+ return sendCommand(Ti::SubSystemSys, Ti::SYSCommandOsalNvWriteExt, payload);
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::deleteNvItem(Ti::NvItemId itemId)
+{
+ qCDebug(dcZigbeeController()) << "Deleting NV item:" << itemId;
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(this);
+
+ NEW_PAYLOAD;
+ stream << static_cast(itemId);
+ ZigbeeInterfaceTiReply *getLengthReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandOsalNvLength, payload);
+ connect(getLengthReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ if (getLengthReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Error getting NV item length.";
+ reply->finish(getLengthReply->statusCode());
+ return;
+ }
+ quint16 length;
+ {
+ PAYLOAD_STREAM(getLengthReply->responsePayload());
+ stream >> length;
+ }
+
+ NEW_PAYLOAD;
+ stream << static_cast(itemId);
+ stream << length;
+ ZigbeeInterfaceTiReply *deleteReply = sendCommand(Ti::SubSystemSys, Ti::SYSCommandOsalNvDelete, payload);
+ connect(deleteReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ if (deleteReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Error deleting NV item";
+ }
+ reply->finish(deleteReply->statusCode());
+ });
+ });
+ return reply;
+}
+
+void ZigbeeBridgeControllerTi::retrieveHugeMessage(const Zigbee::ApsdeDataIndication &pendingIndication, quint32 timestamp, quint16 dataLength)
+{
+ // Suppressing clang analyzer warning about leaking "indication", since we'll actually
+ // clean it up in the capturing lambda when the last request finishes.
+#ifndef __clang_analyzer__
+ Zigbee::ApsdeDataIndication *indication = new Zigbee::ApsdeDataIndication(pendingIndication);
+#endif
+
+ quint8 chunkSize = 0;
+ quint16 maxChunkSize = 253;
+ for (quint16 i = 0; i * maxChunkSize < dataLength; i++) {
+ chunkSize = qMin(maxChunkSize, static_cast(dataLength - i));
+ NEW_PAYLOAD;
+ stream << timestamp;
+ stream << i;
+ stream << chunkSize;
+ ZigbeeInterfaceTiReply *reply = sendCommand(Ti::SubSystemAF, Ti::AFCommandDataRetrieve, payload);
+ // Note, capturing copies of i and chunksize, but a
+ connect(reply, &ZigbeeInterfaceTiReply::finished, this, [this, reply, indication, i, maxChunkSize, dataLength](){
+ PAYLOAD_STREAM(reply->responsePayload());
+ quint8 status, len;
+ stream >> status >> len;
+ if (status != 0x00) {
+ qCWarning(dcZigbeeController()) << "Failed to retrieve large payload chunk!" << status;
+ }
+ indication->asdu.append(reply->responsePayload().right(len));
+
+ if (i * maxChunkSize >= dataLength) {
+ // This is the last one...
+ emit apsDataIndicationReceived(*indication);
+ delete indication;
+ }
+ });
+ }
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::start()
+{
+ NEW_PAYLOAD;
+ stream << static_cast(100); // Startup delay
+ return sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandStartupFromApp, payload);
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::registerEndpoint(quint8 endpointId, Zigbee::ZigbeeProfile profile, quint16 deviceId, quint8 deviceVersion)
+{
+ if (m_registeredEndpointIds.contains(endpointId)) {
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(this);
+ QTimer::singleShot(0, reply, [=](){
+ reply->finish(Ti::StatusCodeSuccess);
+ });
+ return reply;
+ }
+
+ NEW_PAYLOAD;
+ stream << endpointId;
+ stream << static_cast(profile);
+ stream << deviceId;
+ stream << deviceVersion;
+ stream << static_cast(0x00); // latency requirement
+ stream << static_cast(0x00); // num input clusters
+// stream << static_cast(0x0000); // input clusters
+ stream << static_cast(0x00); // num outout clusters
+// stream << static_cast(0x0000); // output clusters
+
+
+ ZigbeeInterfaceTiReply *reply = sendCommand(Ti::SubSystemAF, Ti::AFCommandRegister, payload);
+ connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ PAYLOAD_STREAM(reply->responsePayload());
+ quint8 status;
+ stream >> status;
+ if (status == Ti::StatusCodeSuccess) {
+ m_registeredEndpointIds.append(endpointId);
+ }
+ reply->m_statusCode = static_cast(status);
+ });
+ return reply;
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::addEndpointToGroup(quint8 endpointId, quint16 groupId)
+{
+ ZigbeeInterfaceTiReply *reply = new ZigbeeInterfaceTiReply(this);
+ NEW_PAYLOAD;
+ stream << endpointId;
+ stream << groupId;
+ stream << static_cast(0x00); // Group name length
+ ZigbeeInterfaceTiReply *findGroupReply = sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandExtFindGroup, payload);
+ connect(findGroupReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ quint8 status;
+ PAYLOAD_STREAM(findGroupReply->responsePayload());
+ stream >> status;
+ if (status == 0x00) {
+ qCDebug(dcZigbeeController()) << "Group already existing.";
+ reply->finish();
+ } else {
+ NEW_PAYLOAD;
+ stream << endpointId;
+ stream << groupId;
+ stream << static_cast(0x00);
+ ZigbeeInterfaceTiReply *addGroupReply = sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandExtAddGroup, payload);
+ connect(addGroupReply, &ZigbeeInterfaceTiReply::finished, reply, [=](){
+ reply->finish(addGroupReply->statusCode());
+ });
+ }
+ });
+ return reply;
+}
+
+ZigbeeInterfaceTiReply *ZigbeeBridgeControllerTi::requestPermitJoin(quint8 seconds, const quint16 &networkAddress)
+{
+ NEW_PAYLOAD;
+ stream << static_cast(networkAddress == 0 || networkAddress == 0xFFFC ? 0x0F : 0x02);
+ stream << static_cast(networkAddress == 0 ? 0xFFFC : networkAddress);
+ stream << seconds;
+ stream << static_cast(0x00); // tcsignificance
+ ZigbeeInterfaceTiReply *reply = sendCommand(Ti::SubSystemZDO, Ti::ZDOCommandMgmtPermitJoinReq, payload);
+
+ ZigbeeInterfaceTiReply *waitForJoinRsp = new ZigbeeInterfaceTiReply(this);
+ waitFor(waitForJoinRsp, Ti::SubSystemZDO, Ti::ZDOCommandMgmtPermitJoinRsp);
+ connect(waitForJoinRsp, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ // zStack actually has an indication for permit join state changes which, when working,
+ // gives the current permit join state and the remaining seconds.
+ // Sadly, this doesn't seem to work for zStack3x0 and also seems a bit buggy on zStack12.
+ // So instead of relying on that, let's try to stay in sync with a timer :/
+ emit permitJoinStateChanged(seconds);
+ m_permitJoinTimer.start(seconds * 1000);
+ });
+
+ return reply;
+}
+
+void ZigbeeBridgeControllerTi::waitFor(ZigbeeInterfaceTiReply *reply, Ti::SubSystem subSystem, quint8 command)
+{
+ WaitData waitData;
+ waitData.subSystem = subSystem;
+ waitData.command = command;
+
+ m_waitFors.insert(reply, waitData);
+ connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ m_waitFors.remove(reply);
+ });
+}
+
+void ZigbeeBridgeControllerTi::waitFor(ZigbeeInterfaceTiReply *reply, Ti::SubSystem subSystem, quint8 command, const QByteArray &payload)
+{
+ WaitData waitData;
+ waitData.subSystem = subSystem;
+ waitData.command = command;
+ waitData.payload = payload;
+ waitData.comparePayload = true;
+
+ m_waitFors.insert(reply, waitData);
+ connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ m_waitFors.remove(reply);
+ });
+}
+
+void ZigbeeBridgeControllerTi::onInterfaceAvailableChanged(bool available)
+{
+ qCDebug(dcZigbeeController()) << "Interface available changed" << available;
+ if (!available) {
+ // Clean up any pending replies
+ while (!m_replyQueue.isEmpty()) {
+ ZigbeeInterfaceTiReply *reply = m_replyQueue.dequeue();
+ reply->abort();
+ }
+ }
+
+ setAvailable(available);
+ sendNextRequest();
+}
+
+void ZigbeeBridgeControllerTi::onInterfacePacketReceived(Ti::SubSystem subSystem, Ti::CommandType commandType, quint8 command, const QByteArray &payload)
+{
+
+ qCDebug(dcZigbeeController()) << "<--" << subSystem << commandType <<
+ QHash({
+ { Ti::SubSystemSys, QMetaEnum::fromType() },
+ { Ti::SubSystemMAC, QMetaEnum::fromType() },
+ { Ti::SubSystemAF, QMetaEnum::fromType() },
+ { Ti::SubSystemZDO, QMetaEnum::fromType() },
+ { Ti::SubSystemSAPI, QMetaEnum::fromType() },
+ { Ti::SubSystemUtil, QMetaEnum::fromType() },
+ { Ti::SubSystemDebug, QMetaEnum::fromType() },
+ { Ti::SubSystemApp, QMetaEnum::fromType() },
+ { Ti::SubSystemAppCnf, QMetaEnum::fromType() },
+ { Ti::SubSystemGreenPower, QMetaEnum::fromType() }
+ }).value(subSystem).valueToKey(command)
+ << payload.toHex();
+
+ if (commandType == Ti::CommandTypeSRsp) {
+ if (m_currentReply && m_currentReply->command() == command) {
+ m_currentReply->m_statusCode = Ti::StatusCodeSuccess;
+ m_currentReply->m_responsePayload = payload;
+ emit m_currentReply->finished();
+ } else {
+ qCWarning(dcZigbeeController()) << "Received a reply while not expecting it!";
+ }
+ return;
+ }
+
+ if (commandType == Ti::CommandTypeAReq) {
+
+ switch (subSystem) {
+ case Ti::SubSystemSys:
+ switch (command) {
+ case Ti::SYSCommandResetInd: {
+ PAYLOAD_STREAM(payload);
+ quint8 reason, transportRev, productId, majorRel, minorRel, hwRev;
+ stream >> reason >> transportRev >> productId >> majorRel >> minorRel >> hwRev;
+ qCDebug(dcZigbeeController()) << "Controller reset:" << static_cast(reason);
+ qCDebug(dcZigbeeController()) << "Transport revision:" << transportRev << "Product ID:" << productId << "Major:" << majorRel << "Minor:" << minorRel << "HW Rev:" << hwRev;
+ break;
+ }
+ default:
+ qCDebug(dcZigbeeController()) << "Unhandled system command";
+ }
+ break;
+ case Ti::SubSystemZDO:
+ switch (command) {
+ case Ti::ZDOCommandStateChangeInd:
+ qCDebug(dcZigbeeController()) << "Device state changed!" << payload.at(0);
+ if (payload.at(0) == 0x09) {
+ // We're not emitting state changed right away, need to do some post setup routine
+ postStartup();
+ }
+ break;
+ case Ti::ZDOCommandPermitJoinInd:
+ qCDebug(dcZigbeeController()) << "Permit join indication" << payload.at(0);
+ // Note: This doesn't seem to work for zStack3x0 and also is a bit buggy for zStack12
+ // See requestPermitJoin() for the workaround.
+ emit permitJoinStateChanged(payload.at(0));
+ break;
+ case Ti::ZDOCommandMgmtPermitJoinRsp:
+ qCDebug(dcZigbeeController()) << "PermitJoinRsp received:" << payload;
+ // Silencing this. We'll use PermitJoinInd to update the state as that indicates start and end
+ // In theory we'd need to check if the network address in the payload matches with what we reqeusted....
+ break;
+ case Ti::ZDOCommandTcDeviceInd:
+ qCDebug(dcZigbeeController()) << "Device join indication recived:" << payload;
+ break;
+ case Ti::ZDOCommandEndDeviceAnnceInd: {
+ PAYLOAD_STREAM(payload);
+ quint16 shortAddress, nwkAddress;
+ quint64 ieeeAddress;
+ quint8 capabilities;
+ stream >> shortAddress >> nwkAddress >> ieeeAddress >> capabilities;
+ qCDebug(dcZigbeeController()) << "End device announce indication received:" << payload;
+ emit deviceIndication(shortAddress, ZigbeeAddress(ieeeAddress), capabilities);
+ break;
+ }
+ case Ti::ZDOCommandLeaveInd: {
+ PAYLOAD_STREAM(payload);
+ quint16 srcAddr;
+ quint64 srcIeeeAddr;
+ quint8 request, remove, rejoin;
+ stream >> srcAddr >> srcIeeeAddr >> request >> remove >> rejoin;
+ emit nodeLeft(ZigbeeAddress(srcIeeeAddr), request, remove, rejoin);
+ break;
+ }
+ case Ti::ZDOCommandNodeDescRsp:
+ case Ti::ZDOCommandPowerDescRsp:
+ case Ti::ZDOCommandSimpleDescRsp:
+ case Ti::ZDOCommandActiveEpRsp:
+ case Ti::ZDOCommandBindRsp:
+ // silencing these as we're using the raw data in MsgCbIncoming instead
+ // nymea-zigbee parses this on its own.
+ break;
+ case Ti::ZDOCommandMsgCbIncoming: {
+ qCDebug(dcZigbeeController()) << "Incoming ZDO message:" << payload.toHex();
+ quint16 srcAddr, clusterId, macDstAddr;
+ quint8 wasBroadcast, securityInUse, transactionSequenceNumber;
+
+ PAYLOAD_STREAM(payload);
+ stream >> srcAddr;
+ stream >> wasBroadcast;
+ stream >> clusterId;
+ stream >> securityInUse;
+ stream >> transactionSequenceNumber;
+ stream >> macDstAddr;
+
+ QByteArray adpu = payload.right(payload.length() - 9);
+
+ if (clusterId == ZigbeeDeviceProfile::ZdoCommand::DeviceAnnounce
+ || clusterId == ZigbeeDeviceProfile::ZdoCommand::MgmtPermitJoinResponse) {
+ // Silencing those as we're using the proper z-Stack API for them
+ qCDebug(dcZigbeeController()) << "Ignoring raw ZDO message for command" << static_cast(clusterId);
+ return;
+ }
+
+ QByteArray asdu;
+ QDataStream asduStream(&asdu, QIODevice::WriteOnly);
+ asduStream.setByteOrder(QDataStream::LittleEndian);
+ asduStream << transactionSequenceNumber;
+ asdu.append(adpu);
+
+ Zigbee::ApsdeDataIndication indication;
+ indication.destinationAddressMode = Zigbee::DestinationAddressModeShortAddress;
+ indication.sourceAddressMode = Zigbee::DestinationAddressModeShortAddress;
+ indication.asdu = asdu;
+ indication.clusterId = clusterId;
+ indication.sourceShortAddress = srcAddr;
+ emit apsDataIndicationReceived(indication);
+
+ break;
+ }
+ case Ti::ZDOCommandSrcRtgInd: {
+ PAYLOAD_STREAM(payload);
+ quint16 srcAddr;
+ quint8 relayCount;
+ stream >> srcAddr;
+ stream >> relayCount;
+ QDebug dbg = qDebug(dcZigbeeController());
+ dbg << "Node" << ZigbeeUtils::convertUint16ToHexString(srcAddr) << "is routed" << (relayCount == 0 ? "directly" : "via " + QString::number(relayCount) + " hops:" );
+ for (int i = 0; i < relayCount; i++) {
+ quint16 relayAddr;
+ stream >> relayAddr;
+ dbg << ZigbeeUtils::convertUint16ToHexString(relayAddr);
+ }
+ break;
+ }
+ default:
+ qCWarning(dcZigbeeController()) << "Unhandled ZDO AREQ notification";
+ }
+ break;
+ case Ti::SubSystemAF:
+ switch (command) {
+ case Ti::AFCommandIncomingMsg: {
+ quint8 srcEndpoint, dstEndpoint, wasBroadcast, lqi, securityUse, transactionSequenceNumber, dataLen, status;
+ quint16 groupId, clusterId, srcAddr, addrOfInterest;
+ quint32 timestamp;
+
+ PAYLOAD_STREAM(payload);
+ stream >> groupId;
+ stream >> clusterId;
+ stream >> srcAddr;
+ stream >> srcEndpoint;
+ stream >> dstEndpoint;
+ stream >> wasBroadcast;
+ stream >> lqi;
+ stream >> securityUse;
+ stream >> timestamp;
+ stream >> transactionSequenceNumber;
+ stream >> dataLen;
+
+ QByteArray asdu;
+ for (int i = 0; i < dataLen; i++) {
+ quint8 byte;
+ stream >> byte;
+ asdu.append(byte);
+ }
+
+ stream >> addrOfInterest;
+ stream >> status;
+
+ Zigbee::ApsdeDataIndication indication;
+ indication.destinationAddressMode = Zigbee::DestinationAddressModeShortAddress;
+ indication.sourceAddressMode = Zigbee::DestinationAddressModeShortAddress;
+ indication.sourceEndpoint = srcEndpoint;
+ indication.sourceShortAddress = srcAddr;
+ indication.destinationEndpoint = dstEndpoint;
+ indication.clusterId = clusterId;
+ indication.profileId = Zigbee::ZigbeeProfileHomeAutomation;
+ indication.lqi = lqi;
+ indication.asdu = asdu;
+ emit apsDataIndicationReceived(indication);
+ break;
+ }
+ case Ti::AFCommandIncomingMsgExt: {
+ quint8 srcAddrMode, srcEndpoint, dstEndpoint, wasBroadcast, lqi, securityUse, transactionSequenceNumber, macSrcAddr, radius;
+ quint16 groupId, clusterId, srcPanId, dataLen;
+ quint32 timestamp;
+ quint64 srcAddr;
+
+ // If the payload is missing (meaning the overall packet size is exactly 29), the payload is huge and is
+ // not contained in thie packet but needs to be retrieved separately.
+ bool hugePacket = payload.length() == 29;
+
+ PAYLOAD_STREAM(payload);
+ stream >> groupId;
+ stream >> clusterId;
+ stream >> srcAddrMode;
+ stream >> srcAddr;
+ stream >> srcEndpoint;
+ stream >> srcPanId;
+ stream >> dstEndpoint;
+ stream >> wasBroadcast;
+ stream >> lqi;
+ stream >> securityUse;
+ stream >> timestamp;
+ stream >> transactionSequenceNumber;
+ stream >> dataLen;
+
+ QByteArray asdu;
+ if (!hugePacket) {
+ for (int i = 0; i < dataLen; i++) {
+ quint8 byte;
+ stream >> byte;
+ asdu.append(byte);
+ }
+ }
+
+ stream >> macSrcAddr;
+ stream >> radius;
+
+ Zigbee::ApsdeDataIndication indication;
+ indication.destinationAddressMode = Zigbee::DestinationAddressModeShortAddress;
+ indication.sourceAddressMode = static_cast(srcAddrMode);
+ indication.sourceEndpoint = srcEndpoint;
+ indication.sourceIeeeAddress = srcAddr;
+ indication.destinationEndpoint = dstEndpoint;
+ indication.clusterId = clusterId;
+ indication.profileId = Zigbee::ZigbeeProfileHomeAutomation;
+ indication.lqi = lqi;
+ indication.asdu = asdu;
+ if (!hugePacket) {
+ emit apsDataIndicationReceived(indication);
+ } else {
+ retrieveHugeMessage(indication, timestamp, dataLen);
+ }
+ break;
+ }
+ case Ti::AFCommandDataConfirm: {
+ quint8 status, endpoint, transactionSequenceNumber;
+ PAYLOAD_STREAM(payload);
+ stream >> status >> endpoint >> transactionSequenceNumber;
+ Zigbee::ApsdeDataConfirm confirm;
+ confirm.requestId = transactionSequenceNumber;
+ confirm.destinationEndpoint = endpoint;
+ confirm.zigbeeStatusCode = status;
+ emit apsDataConfirmReceived(confirm);
+ break;
+ }
+ default:
+ qCWarning(dcZigbeeController()) << "Unhandled AF AREQ notification";
+ }
+ break;
+ case Ti::SubSystemAppCnf:
+ switch (command) {
+ case Ti::AppCnfCommandBdbCommissioningNotification:{
+ PAYLOAD_STREAM(payload);
+ quint8 status, commissioningMode, remainingCommissioningModes;
+ stream >> status >> commissioningMode >> remainingCommissioningModes;
+ qCDebug(dcZigbeeController()) << "BDB commissioning notification received. Status:" << status << "Mode:" << commissioningMode << "Remaining:" << remainingCommissioningModes;
+ break;
+ }
+ default:
+ qCWarning(dcZigbeeController()) << "Unhandled AppCnf AREQ notification";
+ }
+ break;
+ default:
+ qCWarning(dcZigbeeController()) << "Unhandled AREQ notification";
+ }
+
+ foreach (ZigbeeInterfaceTiReply *reply, m_waitFors.keys()) {
+ WaitData waitData = m_waitFors.value(reply);
+ if (waitData.subSystem == subSystem && waitData.command == command) {
+ if (!waitData.comparePayload || waitData.payload == payload) {
+ qCDebug(dcZigbeeController()) << "awaited event received.";
+ reply->m_responsePayload = payload;
+ reply->finish();
+ }
+ }
+ }
+ }
+
+ else if (commandType == Ti::CommandTypeSReq) {
+ qCWarning(dcZigbeeController()) << "Unhandled incoming SREQ command:" << subSystem << command;
+ }
+}
+
+bool ZigbeeBridgeControllerTi::enable(const QString &serialPort, qint32 baudrate)
+{
+ return m_interface->enable(serialPort, baudrate);
+}
+
+void ZigbeeBridgeControllerTi::disable()
+{
+ m_interface->disable();
+}
+
+QDebug operator<<(QDebug debug, const TiNetworkConfiguration &configuration)
+{
+ debug.nospace() << "Network configuration: " << "\n";
+ debug.nospace() << " - IEEE address: " << configuration.ieeeAddress.toString() << "\n";
+ debug.nospace() << " - NWK address: " << ZigbeeUtils::convertUint16ToHexString(configuration.shortAddress) << "\n";
+ debug.nospace() << " - PAN ID: " << ZigbeeUtils::convertUint16ToHexString(configuration.panId) << " (" << configuration.panId << ")\n";
+ debug.nospace() << " - Extended PAN ID: " << ZigbeeUtils::convertUint64ToHexString(configuration.extendedPanId) << "\n";
+ debug.nospace() << " - Channel mask: " << ZigbeeChannelMask(configuration.channelMask) << "\n";
+ debug.nospace() << " - Channel: " << configuration.currentChannel << "\n";
+ debug.nospace() << " - ZNP version: " << ZigbeeUtils::convertUint16ToHexString(configuration.znpVersion) << "\n";
+ return debug.space();
+}
+
diff --git a/libnymea-zigbee/backends/ti/zigbeebridgecontrollerti.h b/libnymea-zigbee/backends/ti/zigbeebridgecontrollerti.h
new file mode 100644
index 0000000..ecb410a
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/zigbeebridgecontrollerti.h
@@ -0,0 +1,154 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 ZIGBEEBRIDGECONTROLLERTI_H
+#define ZIGBEEBRIDGECONTROLLERTI_H
+
+#include
+#include
+#include
+#include
+
+#include "zigbee.h"
+#include "zigbeenetwork.h"
+#include "zigbeeaddress.h"
+#include "zigbeenetworkkey.h"
+#include "zigbeenetworkrequest.h"
+#include "zigbeebridgecontroller.h"
+
+#include "interface/ti.h"
+#include "interface/zigbeeinterfaceti.h"
+#include "interface/zigbeeinterfacetireply.h"
+
+typedef struct TiNetworkConfiguration {
+ ZigbeeAddress ieeeAddress; // R
+ quint16 panId = 0; // R
+ quint16 shortAddress = 0; // R
+ quint64 extendedPanId = 0; // R
+ ZigbeeChannelMask channelMask = 0; // RW
+ ZigbeeNetworkKey networkKey; // RW
+ quint8 currentChannel = 0; // R
+ Ti::ZnpVersion znpVersion = Ti::zStack12;
+} TiNetworkConfiguration;
+
+class ZigbeeBridgeControllerTi : public ZigbeeBridgeController
+{
+ Q_OBJECT
+
+ friend class ZigbeeNetworkTi;
+
+public:
+ enum ControllerState {
+ ControllerStateDown,
+ ControllerStateInitialized,
+ ControllerStateRunning,
+ };
+ Q_ENUM(ControllerState)
+
+ explicit ZigbeeBridgeControllerTi(QObject *parent = nullptr);
+ ~ZigbeeBridgeControllerTi() override;
+
+ ControllerState state() const;
+
+ ZigbeeInterfaceTiReply *init();
+ ZigbeeInterfaceTiReply *commission(Ti::DeviceLogicalType deviceType, quint16 panId, const ZigbeeChannelMask &channelMask);
+ ZigbeeInterfaceTiReply *start();
+ ZigbeeInterfaceTiReply *reset();
+ ZigbeeInterfaceTiReply *factoryReset();
+
+ // Network config will be available after initialisation.
+ TiNetworkConfiguration networkConfiguration() const;
+
+ // Anything else is available once running
+ ZigbeeInterfaceTiReply *setLed(bool on);
+
+ ZigbeeInterfaceTiReply *requestPermitJoin(quint8 seconds, const quint16 &networkAddress);
+ ZigbeeInterfaceTiReply *registerEndpoint(quint8 endpointId, Zigbee::ZigbeeProfile profile, quint16 deviceId, quint8 deviceVersion);
+ ZigbeeInterfaceTiReply *addEndpointToGroup(quint8 endpointId, quint16 groupId);
+
+ // Send APS request data
+ ZigbeeInterfaceTiReply *requestSendRequest(const ZigbeeNetworkRequest &request);
+
+public slots:
+ bool enable(const QString &serialPort, qint32 baudrate);
+ void disable();
+
+signals:
+ void controllerStateChanged(ControllerState state);
+
+ void permitJoinStateChanged(qint8 duration);
+ void deviceIndication(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, quint8 macCapabilities);
+ void nodeLeft(const ZigbeeAddress &ieeeAddress, bool request, bool remove, bool rejoin);
+
+ void deviceProfileIndicationReceived(const Zigbee::ApsdeDataIndication &indication);
+ void clusterLibraryIndicationReceived(const Zigbee::ApsdeDataIndication &indication);
+
+private slots:
+ void onInterfaceAvailableChanged(bool available);
+ void onInterfacePacketReceived(Ti::SubSystem subSystem, Ti::CommandType commandType, quint8 command, const QByteArray &payload);
+ void sendNextRequest();
+
+ void initPhase2(ZigbeeInterfaceTiReply* initReply, int attempt);
+ void postStartup();
+
+private:
+ ZigbeeInterfaceTiReply *sendCommand(Ti::SubSystem subSystem, quint8 command, const QByteArray &payload = QByteArray(), int timeout = 5000);
+ ZigbeeInterfaceTiReply *readNvItem(Ti::NvItemId itemId, quint16 offset = 0);
+ ZigbeeInterfaceTiReply *writeNvItem(Ti::NvItemId itemId, const QByteArray &data, quint16 offset = 0);
+ ZigbeeInterfaceTiReply *deleteNvItem(Ti::NvItemId itemId);
+ void retrieveHugeMessage(const Zigbee::ApsdeDataIndication &pendingIndication, quint32 timestamp, quint16 dataLength);
+
+ void waitFor(ZigbeeInterfaceTiReply *reply, Ti::SubSystem subSystem, quint8 command);
+ void waitFor(ZigbeeInterfaceTiReply *reply, Ti::SubSystem subSystem, quint8 command, const QByteArray &payload);
+ struct WaitData {
+ Ti::SubSystem subSystem;
+ quint8 command;
+ QByteArray payload;
+ bool comparePayload = false;
+ };
+ QHash m_waitFors;
+
+ ZigbeeInterfaceTi *m_interface = nullptr;
+
+ TiNetworkConfiguration m_networkConfiguration;
+ ControllerState m_controllerState = ControllerStateDown;
+
+ ZigbeeInterfaceTiReply *m_currentReply = nullptr;
+
+ QQueue m_replyQueue;
+
+ QTimer m_permitJoinTimer;
+
+ QList m_registeredEndpointIds;
+
+ void finishRequest(Ti::StatusCode statusCode = Ti::StatusCodeSuccess);
+};
+
+QDebug operator<<(QDebug debug, const TiNetworkConfiguration &configuration);
+
+
+#endif // ZIGBEEBRIDGECONTROLLERTI_H
diff --git a/libnymea-zigbee/backends/ti/zigbeenetworkti.cpp b/libnymea-zigbee/backends/ti/zigbeenetworkti.cpp
new file mode 100644
index 0000000..8e02181
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/zigbeenetworkti.cpp
@@ -0,0 +1,541 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 "zdo/zigbeedeviceprofile.h"
+#include "zigbeenetworkti.h"
+#include "loggingcategory.h"
+#include "zigbeeutils.h"
+#include "zigbeenetworkdatabase.h"
+
+#include
+#include
+
+ZigbeeNetworkTi::ZigbeeNetworkTi(const QUuid &networkUuid, QObject *parent) :
+ ZigbeeNetwork(networkUuid, parent)
+{
+ m_controller = new ZigbeeBridgeControllerTi(this);
+ connect(m_controller, &ZigbeeBridgeControllerTi::availableChanged, this, &ZigbeeNetworkTi::onControllerAvailableChanged);
+ connect(m_controller, &ZigbeeBridgeControllerTi::controllerStateChanged, this, &ZigbeeNetworkTi::onControllerStateChanged);
+ connect(m_controller, &ZigbeeBridgeControllerTi::firmwareVersionChanged, this, &ZigbeeNetworkTi::firmwareVersionChanged);
+ connect(m_controller, &ZigbeeBridgeControllerTi::permitJoinStateChanged, this, &ZigbeeNetworkTi::onPermitJoinStateChanged);
+ connect(m_controller, &ZigbeeBridgeControllerTi::deviceIndication, this, &ZigbeeNetworkTi::onDeviceIndication);
+ connect(m_controller, &ZigbeeBridgeControllerTi::apsDataIndicationReceived, this, &ZigbeeNetworkTi::onApsDataIndicationReceived);
+ connect(m_controller, &ZigbeeBridgeControllerTi::apsDataConfirmReceived, this, &ZigbeeNetworkTi::onApsDataConfirmReceived);
+ connect(m_controller, &ZigbeeBridgeControllerTi::nodeLeft, this, &ZigbeeNetworkTi::onNodeLeaveIndication);
+}
+
+ZigbeeBridgeController *ZigbeeNetworkTi::bridgeController() const
+{
+ if (!m_controller)
+ return nullptr;
+
+ return m_controller;
+}
+
+Zigbee::ZigbeeBackendType ZigbeeNetworkTi::backendType() const
+{
+ return Zigbee::ZigbeeBackendTypeTi;
+}
+
+ZigbeeNetworkReply *ZigbeeNetworkTi::sendRequest(const ZigbeeNetworkRequest &request)
+{
+ ZigbeeNetworkReply *reply = createNetworkReply(request);
+
+ // Finish the reply right away if the network is offline
+ if (!m_controller->available() || state() == ZigbeeNetwork::StateOffline || state() == ZigbeeNetwork::StateStopping) {
+ finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNetworkOffline);
+ return reply;
+ }
+
+ if (state() == ZigbeeNetwork::StateStarting) {
+ m_requestQueue.append(reply);
+ return reply;
+ }
+
+ ZigbeeInterfaceTiReply *interfaceReply = m_controller->requestSendRequest(request);
+ connect(interfaceReply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply, interfaceReply](){
+ if (interfaceReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Could send request to controller." << interfaceReply->statusCode();
+ finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError);
+ return;
+ }
+
+ finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNoError);
+ });
+
+ return reply;
+}
+
+void ZigbeeNetworkTi::setPermitJoining(quint8 duration, quint16 address)
+{
+ if (duration > 0) {
+ qCDebug(dcZigbeeNetwork()) << "Set permit join for" << duration << "s on" << ZigbeeUtils::convertUint16ToHexString(address);
+ } else {
+ qCDebug(dcZigbeeNetwork()) << "Disable permit join on"<< ZigbeeUtils::convertUint16ToHexString(address);
+ }
+
+ setPermitJoiningDuration(duration);
+
+ ZigbeeInterfaceTiReply *requestPermitJoinReply = m_controller->requestPermitJoin(duration, address);
+ connect(requestPermitJoinReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ if (requestPermitJoinReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeNetwork()) << "Could not set permit join to" << duration << ZigbeeUtils::convertUint16ToHexString(address) << requestPermitJoinReply->statusCode();
+ return;
+ }
+ qCDebug(dcZigbeeNetwork()) << "Permit join request finished successfully";
+
+ // Opening the green power network too
+ // Todo: This should probably be somewhere else, but not yet sure how other backeds deal with this
+ QByteArray payload;
+ QDataStream stream(&payload, QIODevice::WriteOnly);
+ stream.setByteOrder(QDataStream::LittleEndian);
+ stream << static_cast(0x0b); // options
+ stream << static_cast(duration);
+
+
+ // Build ZCL frame control
+ ZigbeeClusterLibrary::FrameControl frameControl;
+ frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
+ frameControl.manufacturerSpecific = false;
+ frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient;
+ frameControl.disableDefaultResponse = true;
+
+ // Build ZCL header
+ ZigbeeClusterLibrary::Header header;
+ header.frameControl = frameControl;
+ header.command = 0x02;// TODO: ZigbeeClusterGreenPower::ClusterCommandCommissioningMode;
+ header.transactionSequenceNumber = generateSequenceNumber();
+
+ // Build ZCL frame
+ ZigbeeClusterLibrary::Frame frame;
+ frame.header = header;
+ frame.payload = payload;
+
+ ZigbeeNetworkRequest request;
+ request.setRequestId(generateSequenceNumber());
+ request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
+ request.setDestinationShortAddress(0xFFFD);
+ request.setDestinationEndpoint(242); // Green Power Endpoint
+ request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
+ request.setClusterId(0x21);
+ request.setTxOptions(static_cast(0x00)); // FIXME: There should be TxOptionsNone I guess...
+ request.setSourceEndpoint(242); // Green Power Endpoint
+ request.setRadius(30); // FIXME: There should be a more clever way to figure out the radius
+ request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
+
+ m_controller->requestSendRequest(request);
+ });
+}
+
+void ZigbeeNetworkTi::initController()
+{
+ qCDebug(dcZigbeeNetwork()) << "Initializing controller";
+ setState(StateStarting);
+ setError(ErrorNoError);
+
+ ZigbeeInterfaceTiReply *initReply = m_controller->init();
+ connect(initReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ if (initReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeNetwork()) << "Error initializing controller";
+ setState(StateUninitialized);
+ setError(ErrorZigbeeError);
+ return;
+ }
+ });
+}
+
+void ZigbeeNetworkTi::commissionController()
+{
+ qCDebug(dcZigbeeNetwork()) << "Commissioning controller";
+
+ quint16 panId = ZigbeeUtils::generateRandomPanId();
+
+ ZigbeeInterfaceTiReply *commissionReply = m_controller->commission(Ti::DeviceLogicalTypeCoordinator, panId, channelMask());
+ connect(commissionReply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ if (commissionReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeNetwork()) << "Error commissioning controller.";
+ setState(StateUninitialized);
+ setError(ErrorZigbeeError);
+ return;
+ }
+
+ qCDebug(dcZigbeeNetwork()) << "Controller commissioned";
+
+ startControllerNetwork();
+ setMacAddress(m_controller->networkConfiguration().ieeeAddress);
+ });
+}
+
+void ZigbeeNetworkTi::startControllerNetwork()
+{
+ setState(StateStarting);
+ qCDebug(dcZigbeeNetwork()) << "Starting network on controller...";
+ ZigbeeInterfaceTiReply *startReply = m_controller->start();
+ connect(startReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
+ if (startReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeNetwork()) << "Error starting network.";
+ setState(StateOffline);
+ setError(ErrorZigbeeError);
+ return;
+ }
+ });
+}
+
+void ZigbeeNetworkTi::processGreenPowerFrame(const Zigbee::ApsdeDataIndication &indication)
+{
+ ZigbeeClusterLibrary::Frame inputFrame = ZigbeeClusterLibrary::parseFrameData(indication.asdu);
+ QDataStream inputStream(inputFrame.payload);
+ inputStream.setByteOrder(QDataStream::LittleEndian);
+ quint8 commandId, payloadSize;
+ quint16 options;
+ quint32 srcId, frameCounter;
+ inputStream >> options >> srcId >> frameCounter >> commandId >> payloadSize;
+ QByteArray commandFrame = inputFrame.payload.right(payloadSize);
+ qCWarning(dcZigbeeNetwork()) << "Green Power frame:" << options << srcId << frameCounter << commandId << payloadSize << commandFrame.toHex();
+
+ if (commandId == 0xE0) {
+ qCWarning(dcZigbeeNetwork()) << "Green power commissioning";
+ QDataStream commandStream(commandFrame);
+ commandStream.setByteOrder(QDataStream::LittleEndian);
+ quint8 deviceId, inputOptions, extendedOptions;
+ QByteArray securityKey;
+ quint32 keyMic;
+ quint32 outgoingCounter;
+ commandStream >> deviceId >> inputOptions >> extendedOptions;
+ for (int i = 0; i < 16; i++) {
+ quint8 byte;
+ commandStream >> byte;
+ securityKey.append(byte);
+ }
+ commandStream >> keyMic >> outgoingCounter;
+
+ // Send commissioning reply
+ QByteArray payload;
+ QDataStream stream(&payload, QIODevice::WriteOnly);
+ stream.setByteOrder(QDataStream::LittleEndian);
+ ZigbeeDataType options = ZigbeeDataType(static_cast(0x00e548), Zigbee::Uint24); // options
+ for (int i = 0; i < options.data().length(); i++) {
+ stream << static_cast(options.data().at(i));
+ }
+ stream << srcId;
+ stream << static_cast(0x0b84); // Green Power group as configured during startup
+ stream << deviceId;
+ stream << outgoingCounter;
+ payload.append(encryptSecurityKey(srcId, securityKey));
+
+ // Build ZCL frame control
+ ZigbeeClusterLibrary::FrameControl frameControl;
+ frameControl.frameType = ZigbeeClusterLibrary::FrameTypeClusterSpecific;
+ frameControl.manufacturerSpecific = false;
+ frameControl.direction = ZigbeeClusterLibrary::DirectionServerToClient;
+ frameControl.disableDefaultResponse = true;
+
+ // Build ZCL header
+ ZigbeeClusterLibrary::Header header;
+ header.frameControl = frameControl;
+ header.command = 0x01; // TODO: ZigbeeClusterGreenPower::ClusterCommandPairing;
+ header.transactionSequenceNumber = generateSequenceNumber() - 1;
+
+ // Build ZCL frame
+ ZigbeeClusterLibrary::Frame frame;
+ frame.header = header;
+ frame.payload = payload;
+
+ ZigbeeNetworkRequest request;
+ request.setRequestId(generateSequenceNumber() + 1);
+ request.setDestinationAddressMode(Zigbee::DestinationAddressModeShortAddress);
+ request.setDestinationShortAddress(0xFFFD);
+ request.setDestinationEndpoint(242); // Green Power Endpoint
+ request.setProfileId(Zigbee::ZigbeeProfileDevice); // ZDP
+ request.setClusterId(0x21);
+ request.setTxOptions(static_cast(0x00)); // FIXME: There should be TxOptionsNone I guess...
+ request.setSourceEndpoint(242); // Green Power Endpoint
+ request.setRadius(30); // FIXME: There should be a more clever way to figure out the radius
+ request.setAsdu(ZigbeeClusterLibrary::buildFrame(frame));
+
+ m_controller->requestSendRequest(request);
+
+ QStringList l;
+ for (int i = 0; i < request.asdu().length(); i++) {
+ l.append(QString::number(static_cast(request.asdu().at(i))));
+ }
+
+ // TODO: create the GreenPower Node here once GreenPower support is added properly
+ // emit greenPowerDeviceJoined(srcId, deviceId, indication.sourceIeeeAddress())
+ }
+}
+
+QByteArray ZigbeeNetworkTi::encryptSecurityKey(quint32 sourceId, const QByteArray &securityKey)
+{
+#if (QCA_VERSION >= QCA_VERSION_CHECK(2, 2, 0))
+ QByteArray sourceIdArray;
+ sourceIdArray.append(static_cast(sourceId & 0x000000ff));
+ sourceIdArray.append(static_cast((sourceId & 0x0000ff00) >> 8));
+ sourceIdArray.append(static_cast((sourceId & 0x00ff0000) >> 16));
+ sourceIdArray.append(static_cast((sourceId & 0xff000000) >> 24));
+
+ QByteArray nonce(13, Qt::Uninitialized);
+ for (int i = 0; i < 3; i++) {
+ for (int j = 0; j < 4; j++) {
+ nonce[4 * i + j] = sourceIdArray.at(j);
+ }
+ }
+ nonce[12] = 0x05;
+
+ QByteArray zigbeeLinkKey = securityConfiguration().globalTrustCenterLinkKey().toByteArray();
+
+ QCA::Initializer init;
+ QCA::Cipher cipher("aes128", QCA::Cipher::CCM, QCA::Cipher::DefaultPadding, QCA::Encode, zigbeeLinkKey, nonce);
+ QByteArray encrypted = cipher.update(securityKey).toByteArray();
+ encrypted.append(cipher.final().toByteArray());
+ return encrypted;
+#else
+ Q_UNUSED(sourceId)
+ qCWarning(dcZigbeeNetwork()) << "Greenpower encryption requires AES-128-CCM (requires qca-qt5-2 >= 2.2.0)";
+ return securityKey;
+#endif
+}
+
+void ZigbeeNetworkTi::onControllerAvailableChanged(bool available)
+{
+ if (!available) {
+ qCWarning(dcZigbeeNetwork()) << "Hardware controller is not available any more.";
+ setError(ErrorHardwareUnavailable);
+ setPermitJoiningEnabled(false);
+ setState(StateOffline);
+ setError(ErrorHardwareUnavailable);
+ } else {
+ setPermitJoiningEnabled(false);
+ setState(StateOffline);
+ setError(ErrorNoError);
+ qCDebug(dcZigbeeNetwork()) << "Hardware controller is now available.";
+ initController();
+ }
+}
+
+void ZigbeeNetworkTi::onControllerStateChanged(ZigbeeBridgeControllerTi::ControllerState state)
+{
+ switch (state) {
+ case ZigbeeBridgeControllerTi::ControllerStateDown:
+ setState(StateOffline);
+ break;
+ case ZigbeeBridgeControllerTi::ControllerStateInitialized:
+
+ // The mac address of the controller will be stored locally when the coordinator node replies
+ // to the node descriptor request which includes the IEEE address.
+ // If we don't know the mac address yet, it means that we've never had the zigbee network up and running
+ // => start the commissioning procedure, else, directly start the network
+ // Note: if the user messes around with the stick by provisioning stuff with another instance/software, it
+ // will not be detected currently and the "wrong" network starts up.
+ qCDebug(dcZigbeeNetwork()) << "Controller initialized";
+ qCDebug(dcZigbeeNetwork()) << "Stored MAC address:" << macAddress() << "Controller MAC address:" << m_controller->networkConfiguration().ieeeAddress;
+
+ if (macAddress() != ZigbeeAddress("00:00:00:00:00:00:00:00") && m_controller->networkConfiguration().ieeeAddress != macAddress()) {
+ qCWarning(dcZigbeeNetwork()) << "The controller MAC address changed. Please connect the original controller or create a new network.";
+ setState(StateUninitialized);
+ setError(ErrorHardwareModuleChanged);
+ stopNetwork();
+ return;
+ }
+ if (macAddress() == ZigbeeAddress("00:00:00:00:00:00:00")) {
+ // Controller network is not commissioned yet
+ qCDebug(dcZigbeeNetwork()) << "Controller is not comissioned yet. Comissioning now...";
+ commissionController();
+ return;
+ }
+
+ qCDebug(dcZigbeeNetwork()) << "Controller is ready and commissioned.";
+ startControllerNetwork();
+
+ break;
+ case ZigbeeBridgeControllerTi::ControllerStateRunning: {
+ qCDebug(dcZigbeeNetwork()) << "Controller network running. Registering endpoints on controller..";
+
+ setPanId(m_controller->networkConfiguration().panId);
+ setExtendedPanId(m_controller->networkConfiguration().extendedPanId);
+ setChannel(m_controller->networkConfiguration().currentChannel);
+
+ // TODO: This should be public API of libnymea-zigbee so that the application layer (e.g. nymea-plugins)
+ // can register the endpoints it needs for the particular application/device.
+ // Fow now we're registering HomeAutomation, LightLink and GreenPower endpoints.
+ m_controller->registerEndpoint(1, Zigbee::ZigbeeProfileHomeAutomation, 5, 0);
+ m_controller->registerEndpoint(12, Zigbee::ZigbeeProfileLightLink, 5, 0);
+
+ // The Green Power endpoing is a bit special, it also needs to be added to a group
+ ZigbeeInterfaceTiReply *registerLLEndpointReply = m_controller->registerEndpoint(242, Zigbee::ZigbeeProfileGreenPower, 5, 0);
+ connect(registerLLEndpointReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
+ if (registerLLEndpointReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeNetwork()) << "Error registering GreenPower endpoint.";
+ setState(StateOffline);
+ return;
+ }
+ qCDebug(dcZigbeeNetwork()) << "Registered GreenPower endpoint on coordinator node.";
+
+ ZigbeeInterfaceTiReply *addGEndpointGroupReply = m_controller->addEndpointToGroup(242, 0x0b84);
+ connect(addGEndpointGroupReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
+ if (addGEndpointGroupReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeNetwork()) << "Error adding GreenPower endpoint to group.";
+ setState(StateOffline);
+ return;
+ }
+
+ // Now we're done. If this is a first start (no coordinator node loaded from configs) we'll add
+ // ourselves now and start the inspection.
+ if (!m_coordinatorNode) {
+ qCDebug(dcZigbeeNetwork()) << "Initializing coordinator node:" << m_controller->networkConfiguration();
+ ZigbeeNode *coordinatorNode = createNode(m_controller->networkConfiguration().shortAddress, m_controller->networkConfiguration().ieeeAddress, this);
+ m_coordinatorNode = coordinatorNode;
+ addUnitializedNode(coordinatorNode);
+ }
+ // Introspecing ourselves on every start. Most of the times this wouldn't be needed, but if the above
+ // endpoints are changed (e.g. on a future upgrade), we'll want to refresh.
+ m_coordinatorNode->startInitialization();
+
+ ZigbeeInterfaceTiReply *ledReply = m_controller->setLed(false);
+ connect(ledReply, &ZigbeeInterfaceTiReply::finished, this, [=]() {
+ setState(StateRunning);
+
+ while (!m_requestQueue.isEmpty()) {
+ ZigbeeNetworkReply *reply = m_requestQueue.takeFirst();
+ ZigbeeInterfaceTiReply *interfaceReply = m_controller->requestSendRequest(reply->request());
+ connect(interfaceReply, &ZigbeeInterfaceTiReply::finished, reply, [this, reply, interfaceReply](){
+ if (interfaceReply->statusCode() != Ti::StatusCodeSuccess) {
+ qCWarning(dcZigbeeController()) << "Could send request to controller." << interfaceReply->statusCode();
+ finishNetworkReply(reply, ZigbeeNetworkReply::ErrorInterfaceError);
+ return;
+ }
+ finishNetworkReply(reply, ZigbeeNetworkReply::ErrorNoError);
+ });
+ }
+ });
+ });
+ });
+
+ break;
+ }
+ }
+}
+
+void ZigbeeNetworkTi::onPermitJoinStateChanged(quint8 duration)
+{
+ setPermitJoiningRemaining(duration);
+ setPermitJoiningEnabled(duration > 0);
+ m_controller->setLed(duration > 0);
+}
+
+void ZigbeeNetworkTi::onDeviceIndication(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, quint8 macCapabilities)
+{
+ onDeviceAnnounced(shortAddress, ieeeAddress, macCapabilities);
+}
+
+void ZigbeeNetworkTi::onNodeLeaveIndication(const ZigbeeAddress &ieeeAddress, bool request, bool remove, bool rejoin)
+{
+ qCDebug(dcZigbeeNetwork()) << "Received node leave indication" << ieeeAddress.toString() << "request:" << request << "remove:" << remove << "rejoining:" << rejoin;
+ if (!hasNode(ieeeAddress)) {
+ qCDebug(dcZigbeeNetwork()) << "Node left the network" << ieeeAddress.toString();
+ return;
+ }
+
+ ZigbeeNode *node = getZigbeeNode(ieeeAddress);
+ qCDebug(dcZigbeeNetwork()) << node << "left the network";
+ removeNode(node);
+}
+
+void ZigbeeNetworkTi::onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm)
+{
+ qCDebug(dcZigbeeNetwork()) << "Data confirm received:" << confirm;
+}
+
+void ZigbeeNetworkTi::onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication)
+{
+ // If it's for the green power endpoint, we'll have to do a commissioning.
+ // TODO: This should probably be in ZigbeeNode or ZigbeeDeviceObject, but not yet sure how other backends deal with it
+ if (indication.destinationEndpoint == 242) {
+ processGreenPowerFrame(indication);
+ }
+
+ // Check if this indocation is related to any pending reply
+ if (indication.profileId == Zigbee::ZigbeeProfileDevice) {
+ handleZigbeeDeviceProfileIndication(indication);
+ return;
+ }
+
+ // Else let the node handle this indication
+ handleZigbeeClusterLibraryIndication(indication);
+}
+
+void ZigbeeNetworkTi::startNetwork()
+{
+ loadNetwork();
+
+ if (!m_controller->enable(serialPortName(), serialBaudrate())) {
+ setPermitJoiningEnabled(false);
+ setState(StateOffline);
+ setError(ErrorHardwareUnavailable);
+ return;
+ }
+
+ setPermitJoiningEnabled(false);
+}
+
+void ZigbeeNetworkTi::stopNetwork()
+{
+ setState(StateStopping);
+ m_controller->disable();
+ setState(StateOffline);
+}
+
+void ZigbeeNetworkTi::reset()
+{
+ qCDebug(dcZigbeeNetwork()) << "Resetting controller.";
+ m_controller->reset();
+}
+
+void ZigbeeNetworkTi::factoryResetNetwork()
+{
+ qCDebug(dcZigbeeNetwork()) << "Factory resetting network and forget all information. This cannot be undone.";
+
+ ZigbeeInterfaceTiReply *reply = m_controller->factoryReset();
+ connect(reply, &ZigbeeInterfaceTiReply::finished, this, [=](){
+ qCDebug(dcZigbeeNetwork()) << "Factory reset reply finished" << reply->statusCode();
+
+ m_controller->disable();
+ clearSettings();
+ setMacAddress(ZigbeeAddress("00:00:00:00:00:00"));
+ setState(StateOffline);
+ setError(ErrorNoError);
+ qCDebug(dcZigbeeNetwork()) << "The factory reset is finished. Restarting with a fresh network.";
+ startNetwork();
+ });
+}
+
+void ZigbeeNetworkTi::destroyNetwork()
+{
+ qCDebug(dcZigbeeNetwork()) << "Destroy network and delete the database";
+ m_controller->disable();
+ clearSettings();
+}
diff --git a/libnymea-zigbee/backends/ti/zigbeenetworkti.h b/libnymea-zigbee/backends/ti/zigbeenetworkti.h
new file mode 100644
index 0000000..b59257f
--- /dev/null
+++ b/libnymea-zigbee/backends/ti/zigbeenetworkti.h
@@ -0,0 +1,85 @@
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+*
+* Copyright 2013 - 2022, 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 ZIGBEENETWORKTI_H
+#define ZIGBEENETWORKTI_H
+
+#include
+
+#include "zigbeenetwork.h"
+#include "zigbeechannelmask.h"
+#include "zcl/zigbeeclusterlibrary.h"
+#include "zigbeebridgecontrollerti.h"
+
+class ZigbeeNetworkTi : public ZigbeeNetwork
+{
+ Q_OBJECT
+public:
+
+ explicit ZigbeeNetworkTi(const QUuid &networkUuid, QObject *parent = nullptr);
+
+ ZigbeeBridgeController *bridgeController() const override;
+ Zigbee::ZigbeeBackendType backendType() const override;
+
+ // Sending an APSDE-DATA.request, will be finished on APSDE-DATA.confirm
+ ZigbeeNetworkReply *sendRequest(const ZigbeeNetworkRequest &request) override;
+
+ void setPermitJoining(quint8 duration, quint16 address = Zigbee::BroadcastAddressAllRouters) override;
+
+public slots:
+ void startNetwork() override;
+ void stopNetwork() override;
+ void reset() override;
+ void factoryResetNetwork() override;
+ void destroyNetwork() override;
+
+
+private slots:
+ void onControllerAvailableChanged(bool available);
+ void onControllerStateChanged(ZigbeeBridgeControllerTi::ControllerState state);
+ void onPermitJoinStateChanged(quint8 duration);
+ void onDeviceIndication(quint16 shortAddress, const ZigbeeAddress &ieeeAddress, quint8 macCapabilities);
+ void onNodeLeaveIndication(const ZigbeeAddress &ieeeAddress, bool request, bool remove, bool rejoin);
+
+ void onApsDataConfirmReceived(const Zigbee::ApsdeDataConfirm &confirm);
+ void onApsDataIndicationReceived(const Zigbee::ApsdeDataIndication &indication);
+
+private:
+ void initController();
+ void commissionController();
+ void startControllerNetwork();
+
+ void processGreenPowerFrame(const Zigbee::ApsdeDataIndication &indication);
+ QByteArray encryptSecurityKey(quint32 sourceId, const QByteArray &securityKey);
+
+private:
+ ZigbeeBridgeControllerTi *m_controller = nullptr;
+
+ QList m_requestQueue;
+};
+
+#endif // ZIGBEENETWORKDECONZ_H
diff --git a/libnymea-zigbee/libnymea-zigbee.pro b/libnymea-zigbee/libnymea-zigbee.pro
index 4c27ca3..7f701c4 100644
--- a/libnymea-zigbee/libnymea-zigbee.pro
+++ b/libnymea-zigbee/libnymea-zigbee.pro
@@ -12,6 +12,8 @@ packagesExist(libudev) {
DEFINES += DISABLE_UDEV
}
+PKGCONFIG += qca2-qt5
+
SOURCES += \
backends/deconz/interface/zigbeeinterfacedeconz.cpp \
backends/deconz/interface/zigbeeinterfacedeconzreply.cpp \
@@ -22,6 +24,10 @@ SOURCES += \
backends/nxp/interface/zigbeeinterfacenxpreply.cpp \
backends/nxp/zigbeebridgecontrollernxp.cpp \
backends/nxp/zigbeenetworknxp.cpp \
+ backends/ti/interface/zigbeeinterfaceti.cpp \
+ backends/ti/interface/zigbeeinterfacetireply.cpp \
+ backends/ti/zigbeebridgecontrollerti.cpp \
+ backends/ti/zigbeenetworkti.cpp \
zcl/closures/zigbeeclusterdoorlock.cpp \
zcl/general/zigbeeclusteranaloginput.cpp \
zcl/general/zigbeeclusteranalogoutput.cpp \
@@ -90,6 +96,11 @@ HEADERS += \
backends/nxp/interface/zigbeeinterfacenxpreply.h \
backends/nxp/zigbeebridgecontrollernxp.h \
backends/nxp/zigbeenetworknxp.h \
+ backends/ti/interface/ti.h \
+ backends/ti/interface/zigbeeinterfaceti.h \
+ backends/ti/interface/zigbeeinterfacetireply.h \
+ backends/ti/zigbeebridgecontrollerti.h \
+ backends/ti/zigbeenetworkti.h \
zcl/closures/zigbeeclusterdoorlock.h \
zcl/general/zigbeeclusteranaloginput.h \
zcl/general/zigbeeclusteranalogoutput.h \
diff --git a/libnymea-zigbee/zigbee.h b/libnymea-zigbee/zigbee.h
index f800ece..d414c0f 100644
--- a/libnymea-zigbee/zigbee.h
+++ b/libnymea-zigbee/zigbee.h
@@ -42,7 +42,8 @@ class Zigbee
public:
enum ZigbeeBackendType {
ZigbeeBackendTypeDeconz,
- ZigbeeBackendTypeNxp
+ ZigbeeBackendTypeNxp,
+ ZigbeeBackendTypeTi
};
Q_ENUM(ZigbeeBackendType)
diff --git a/libnymea-zigbee/zigbeenetworkmanager.cpp b/libnymea-zigbee/zigbeenetworkmanager.cpp
index b76be8f..492b8ee 100644
--- a/libnymea-zigbee/zigbeenetworkmanager.cpp
+++ b/libnymea-zigbee/zigbeenetworkmanager.cpp
@@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
-* Copyright 2013 - 2020, nymea GmbH
+* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea-zigbee.
@@ -30,6 +30,7 @@
#include "backends/nxp/zigbeenetworknxp.h"
#include "backends/deconz/zigbeenetworkdeconz.h"
+#include "backends/ti/zigbeenetworkti.h"
#include
@@ -48,6 +49,8 @@ ZigbeeNetwork *ZigbeeNetworkManager::createZigbeeNetwork(const QUuid &networkUui
return qobject_cast(new ZigbeeNetworkNxp(networkUuid, parent));
case Zigbee::ZigbeeBackendTypeDeconz:
return qobject_cast(new ZigbeeNetworkDeconz(networkUuid, parent));
+ case Zigbee::ZigbeeBackendTypeTi:
+ return qobject_cast(new ZigbeeNetworkTi(networkUuid, parent));
}
return nullptr;
diff --git a/libnymea-zigbee/zigbeeuartadaptermonitor.cpp b/libnymea-zigbee/zigbeeuartadaptermonitor.cpp
index d115a8c..19513be 100644
--- a/libnymea-zigbee/zigbeeuartadaptermonitor.cpp
+++ b/libnymea-zigbee/zigbeeuartadaptermonitor.cpp
@@ -1,6 +1,6 @@
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
-* Copyright 2013 - 2020, nymea GmbH
+* Copyright 2013 - 2021, nymea GmbH
* Contact: contact@nymea.io
*
* This file is part of nymea-zigbee.
@@ -237,6 +237,12 @@ void ZigbeeUartAdapterMonitor::addAdapterInternally(const QString &serialPort)
adapter.setBackendType(Zigbee::ZigbeeBackendTypeNxp);
adapter.setBaudRate(115200);
}
+ QStringList zStackModels = {"cc2530", "cc2531", "cc2538", "cc1352p", "cc2652p", "cc2652r", "cc2652rb", "sonoff zigbee 3.0 usb"};
+ if (QRegExp(".*(" + zStackModels.join("|") + ").*").exactMatch(serialPortInfo.description().toLower())) {
+ adapter.setHardwareRecognized(true);
+ adapter.setBackendType(Zigbee::ZigbeeBackendTypeTi);
+ adapter.setBaudRate(115200);
+ }
qCDebug(dcZigbeeAdapterMonitor()) << "Added" << adapter;
m_availableAdapters.insert(adapter.serialPort(), adapter);