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);