/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea-zigbee. * This project including source code and documentation is protected by copyright law, and * remains the property of nymea GmbH. All rights, including reproduction, publication, * editing and translation, are reserved. The use of this project is subject to the terms of a * license agreement to be concluded with nymea GmbH in accordance with the terms * of use of nymea GmbH, available under https://nymea.io/license * * GNU Lesser General Public License Usage * Alternatively, this project may be redistributed and/or modified under the terms of the GNU * Lesser General Public License as published by the Free Software Foundation; version 3. * this project is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with this project. * If not, see . * * For any further details and any questions please contact us under contact@nymea.io * or see our FAQ/Licensing Information on https://nymea.io/license/faq * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ #include "zigbeedeviceprofile.h" #include "zigbeeutils.h" #include ZigbeeDeviceProfile::NodeDescriptor ZigbeeDeviceProfile::parseNodeDescriptor(const QByteArray &payload) { NodeDescriptor nodeDescriptor; nodeDescriptor.descriptorRawData = payload; // Parse and set the node descriptor QDataStream stream(payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 typeAndDescriptorFlags = 0, frequencyAndApsFlags = 0, macCapabilitiesFlags = 0, descriptorCapabilities = 0; quint16 serverMask = 0; stream >> typeAndDescriptorFlags >> frequencyAndApsFlags >> macCapabilitiesFlags >> nodeDescriptor.manufacturerCode >> nodeDescriptor.maximumBufferSize; stream >> nodeDescriptor.maximumRxSize >> serverMask >> nodeDescriptor.maximumTxSize >> descriptorCapabilities; nodeDescriptor.nodeType = static_cast(typeAndDescriptorFlags & 0x07); nodeDescriptor.complexDescriptorAvailable = typeAndDescriptorFlags & 0x08; nodeDescriptor.userDescriptorAvailable = typeAndDescriptorFlags & 0x10; // Frequency band, 5 bits if (ZigbeeUtils::checkBitUint8(frequencyAndApsFlags, 3)) { nodeDescriptor.frequencyBand = FrequencyBand868Mhz; } else if (ZigbeeUtils::checkBitUint8(frequencyAndApsFlags, 5)) { nodeDescriptor.frequencyBand = FrequencyBand902Mhz; } else if (ZigbeeUtils::checkBitUint8(frequencyAndApsFlags, 6)) { nodeDescriptor.frequencyBand = FrequencyBand2400Mhz; } nodeDescriptor.macCapabilities = parseMacCapabilities(macCapabilitiesFlags); nodeDescriptor.serverMask = parseServerMask(serverMask); nodeDescriptor.descriptorCapabilities = parseDescriptorCapabilities(descriptorCapabilities); return nodeDescriptor; } ZigbeeDeviceProfile::MacCapabilities ZigbeeDeviceProfile::parseMacCapabilities(quint8 macCapabilitiesFlag) { MacCapabilities capabilities; capabilities.flag = macCapabilitiesFlag; capabilities.alternatePanCoordinator = ((macCapabilitiesFlag >> 0) & 0x01); if (((macCapabilitiesFlag >> 1) & 0x01)) { capabilities.deviceType = DeviceTypeFullFunction; } else { capabilities.deviceType = DeviceTypeReducedFunction; } capabilities.powerSourceFlagMainPower = ((macCapabilitiesFlag >> 2) & 0x01); capabilities.receiverOnWhenIdle = ((macCapabilitiesFlag >> 3) & 0x01); capabilities.securityCapability = ((macCapabilitiesFlag >> 6) & 0x01); capabilities.allocateAddress = ((macCapabilitiesFlag >> 7) & 0x01); return capabilities; } ZigbeeDeviceProfile::ServerMask ZigbeeDeviceProfile::parseServerMask(quint16 serverMaskFlag) { ServerMask serverMask; serverMask.serverMaskFlag = serverMaskFlag; serverMask.primaryTrustCenter = ((serverMaskFlag >> 0) & 0x0001); serverMask.backupTrustCenter = ((serverMaskFlag >> 1) & 0x0001); serverMask.primaryBindingCache = ((serverMaskFlag >> 2) & 0x0001); serverMask.backupBindingCache = ((serverMaskFlag >> 3) & 0x0001); serverMask.primaryDiscoveryCache = ((serverMaskFlag >> 4) & 0x0001); serverMask.backupDiscoveryCache = ((serverMaskFlag >> 5) & 0x0001); serverMask.networkManager = ((serverMaskFlag >> 6) & 0x0001); return serverMask; } ZigbeeDeviceProfile::DescriptorCapabilities ZigbeeDeviceProfile::parseDescriptorCapabilities(quint8 descriptorCapabilitiesFlag) { DescriptorCapabilities capabilities; capabilities.descriptorCapabilitiesFlag = descriptorCapabilitiesFlag; capabilities.extendedActiveEndpointListAvailable = ((descriptorCapabilitiesFlag >> 0) & 0x01); capabilities.extendedSimpleDescriptorListAvailable = ((descriptorCapabilitiesFlag >> 1) & 0x01); return capabilities; } ZigbeeDeviceProfile::PowerDescriptor ZigbeeDeviceProfile::parsePowerDescriptor(quint16 powerDescriptorFlag) { PowerDescriptor powerDescriptor; powerDescriptor.powerDescriptoFlag = powerDescriptorFlag; // Bit 0 - 3 Power mode // 0000: Receiver configured according to “Receiver on when idle” MAC flag in the Node Descriptor // 0001: Receiver switched on periodically // 0010: Receiver switched on when stimulated, e.g. by pressing a button powerDescriptor.powerMode = ZigbeeDeviceProfile::PowerModeAlwaysOn; if (!ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 1)) { powerDescriptor.powerMode = ZigbeeDeviceProfile::PowerModeAlwaysOn; } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 0) && !ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 1)) { powerDescriptor.powerMode = ZigbeeDeviceProfile::PowerModeOnPeriodically; } else if (!ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 0) && ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 1)) { powerDescriptor.powerMode = ZigbeeDeviceProfile::PowerModeOnWhenStimulated; } // Bit 4 - 7 Available power sources // Bit 0: Permanent mains supply // Bit 1: Rechargeable battery // Bit 2: Disposable battery // Bit 4: Reserved if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 4)) { powerDescriptor.availablePowerSources.append(ZigbeeDeviceProfile::PowerSourcePermanentMainSupply); } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 5)) { powerDescriptor.availablePowerSources.append(ZigbeeDeviceProfile::PowerSourceRecharchableBattery); } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 6)) { powerDescriptor.availablePowerSources.append(ZigbeeDeviceProfile::PowerSourceDisposableBattery); } // Bit 8 - 11 Active source: according to the same schema as available power sources powerDescriptor.powerSource = ZigbeeDeviceProfile::PowerSourcePermanentMainSupply; if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 8)) { powerDescriptor.powerSource = ZigbeeDeviceProfile::PowerSourcePermanentMainSupply; } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 9)) { powerDescriptor.powerSource = ZigbeeDeviceProfile::PowerSourceRecharchableBattery; } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 10)) { powerDescriptor.powerSource = ZigbeeDeviceProfile::PowerSourceDisposableBattery; } // Bit 12 - 15: Battery level if available // 0000: Critically low // 0100: Approximately 33% // 1000: Approximately 66% // 1100: Approximately 100% (near fully charged) if (!ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 14) && !ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 15)) { powerDescriptor.powerLevel = ZigbeeDeviceProfile::PowerLevelCriticalLow; } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 14) && !ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 15)) { powerDescriptor.powerLevel = ZigbeeDeviceProfile::PowerLevelLow; } else if (!ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 14) && ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 15)) { powerDescriptor.powerLevel = ZigbeeDeviceProfile::PowerLevelOk; } else if (ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 14) && ZigbeeUtils::checkBitUint16(powerDescriptorFlag, 15)) { powerDescriptor.powerLevel = ZigbeeDeviceProfile::PowerLevelFull; } // qCDebug(dcZigbeeNode()) << "Node power descriptor (" << ZigbeeUtils::convertUint16ToHexString(m_powerDescriptorFlag) << "):"; // qCDebug(dcZigbeeNode()) << " Power mode:" << m_powerMode; // qCDebug(dcZigbeeNode()) << " Available power sources:"; // foreach (const PowerSource &source, m_availablePowerSources) { // qCDebug(dcZigbeeNode()) << " " << source; // } // qCDebug(dcZigbeeNode()) << " Power source:" << m_powerSource; // qCDebug(dcZigbeeNode()) << " Power level:" << m_powerLevel; return powerDescriptor; } ZigbeeDeviceProfile::BindingTable ZigbeeDeviceProfile::parseBindingTable(const QByteArray &payload) { ZigbeeDeviceProfile::BindingTable table; QDataStream stream(payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 sqn, status, listRecordCount; stream >> sqn >> status >> table.tableSize >> table.startIndex >> listRecordCount; table.status = static_cast(status); for (int i = 0; i < listRecordCount; i++) { ZigbeeDeviceProfile::BindingTableListRecord record; quint64 sourceAddress; stream >> sourceAddress; record.sourceAddress = ZigbeeAddress(sourceAddress); stream >> record.sourceEndpoint; stream >> record.clusterId; quint8 addressMode; stream >> addressMode; record.destinationAddressMode = static_cast(addressMode); if (addressMode == Zigbee::DestinationAddressModeGroup) { stream >> record.destinationShortAddress; } else if (addressMode == Zigbee::DestinationAddressModeIeeeAddress) { quint64 destinationAddressIeee; stream >> destinationAddressIeee >> record.destinationEndpoint; record.destinationIeeeAddress = ZigbeeAddress(destinationAddressIeee); } table.records.append(record); } return table; } ZigbeeDeviceProfile::NeighborTable ZigbeeDeviceProfile::parseNeighborTable(const QByteArray &payload) { ZigbeeDeviceProfile::NeighborTable table; QDataStream stream(payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 messageId, status, listRecordCount; stream >> messageId >> status >> table.tableSize >> table.startIndex >> listRecordCount; table.status = static_cast(status); for (int i = 0; i < listRecordCount; i++) { ZigbeeDeviceProfile::NeighborTableListRecord record; stream >> record.extendedPanId; quint64 ieeeAddress; stream >> ieeeAddress; record.ieeeAddress = ZigbeeAddress(ieeeAddress); stream >> record.shortAddress; quint8 nodeFlags; stream >> nodeFlags; record.nodeType = static_cast(nodeFlags & 0x03); record.receiverOnWhenIdle = static_cast((nodeFlags & 0x0c) >> 2); record.relationship = static_cast((nodeFlags & 0x70) >> 4); stream >> nodeFlags; record.permitJoining = (nodeFlags & 0x03) == 1; stream >> record.depth; stream >> record.lqi; table.records.append(record); } return table; } ZigbeeDeviceProfile::RoutingTable ZigbeeDeviceProfile::parseRoutingTable(const QByteArray &payload) { ZigbeeDeviceProfile::RoutingTable table; QDataStream stream(payload); stream.setByteOrder(QDataStream::LittleEndian); quint8 messageId, status, listRecordCount; stream >> messageId >> status >> table.tableSize >> table.startIndex >> listRecordCount; table.status = static_cast(status); for (int i = 0; i < listRecordCount; i++) { ZigbeeDeviceProfile::RoutingTableListRecord record; stream >> record.destinationAddress; qint8 flags; stream >> flags; record.status = static_cast(flags & 0x07); record.memoryConstrained = (flags & 0x08) > 0; record.manyToOne = (flags & 0x10) > 0; record.routeRecordRequired = (flags & 0x20) > 0; stream >> record.nextHopAddress; table.records.append(record); } return table; } ZigbeeDeviceProfile::Adpu ZigbeeDeviceProfile::parseAdpu(const QByteArray &adpu) { QDataStream stream(adpu); stream.setByteOrder(QDataStream::LittleEndian); ZigbeeDeviceProfile::Adpu deviceAdpu; quint8 statusFlag = 0; stream >> deviceAdpu.transactionSequenceNumber >> statusFlag >> deviceAdpu.addressOfInterest; deviceAdpu.status = static_cast(statusFlag); deviceAdpu.payload = adpu.right(adpu.length() - 4); return deviceAdpu; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::Adpu &adpu) { QDebugStateSaver saver(debug); debug.nospace() << "DeviceProfileAdpu(SQN: " << adpu.transactionSequenceNumber << ", "; debug.nospace() << adpu.status << ", "; debug.nospace() << ZigbeeUtils::convertUint16ToHexString(adpu.addressOfInterest) << ", "; debug.nospace() << "Payload: " << ZigbeeUtils::convertByteArrayToHexString(adpu.payload) << ")"; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::NodeDescriptor &nodeDescriptor) { QDebugStateSaver saver(debug); debug.nospace() << "NodeDescriptor(" << nodeDescriptor.nodeType << ")" << "\n"; debug.nospace() << " Complex descriptor available: " << nodeDescriptor.complexDescriptorAvailable << "\n"; debug.nospace() << " User descriptor available: " << nodeDescriptor.userDescriptorAvailable << "\n"; debug.nospace() << " " << nodeDescriptor.frequencyBand << "\n"; debug.nospace() << " " << nodeDescriptor.macCapabilities << "\n"; debug.nospace() << " Manufacturer code: " << ZigbeeUtils::convertUint16ToHexString(nodeDescriptor.manufacturerCode) << "(" << nodeDescriptor.manufacturerCode << ")" << "\n"; debug.nospace() << " Maximum buffer size: " << nodeDescriptor.maximumBufferSize << "\n"; debug.nospace() << " Maximum RX size: " << nodeDescriptor.maximumRxSize << "\n"; debug.nospace() << " Maximum TX size: " << nodeDescriptor.maximumTxSize << "\n"; debug.nospace() << " " << nodeDescriptor.serverMask << "\n"; debug.nospace() << " " << nodeDescriptor.descriptorCapabilities; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::MacCapabilities &macCapabilities) { QDebugStateSaver saver(debug); debug.nospace() << "MacCapabilities(" << ZigbeeUtils::convertByteToHexString(macCapabilities.flag) << ")" << "\n"; debug.nospace() << " Alternate PAN Coordinator: " << macCapabilities.alternatePanCoordinator << "\n"; debug.nospace() << " " << macCapabilities.deviceType << "\n"; debug.nospace() << " Power source main power: " << macCapabilities.powerSourceFlagMainPower << "\n"; debug.nospace() << " Receiver on when idle: " << macCapabilities.receiverOnWhenIdle << "\n"; debug.nospace() << " Security capability: " << macCapabilities.securityCapability << "\n"; debug.nospace() << " Allocate address: " << macCapabilities.allocateAddress; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::ServerMask &serverMask) { QDebugStateSaver saver(debug); debug.nospace() << "ServerMask(" << ZigbeeUtils::convertUint16ToHexString(serverMask.serverMaskFlag) << ")" << "\n"; debug.nospace() << " Primary trust center: " << serverMask.primaryTrustCenter << "\n"; debug.nospace() << " Backup trust center: " << serverMask.backupTrustCenter << "\n"; debug.nospace() << " Primary binding cache: " << serverMask.primaryBindingCache << "\n"; debug.nospace() << " Backup binding cache: " << serverMask.backupBindingCache << "\n"; debug.nospace() << " Primary discovery cache: " << serverMask.primaryDiscoveryCache << "\n"; debug.nospace() << " Backup discovery cache: " << serverMask.backupDiscoveryCache << "\n"; debug.nospace() << " Network manager: " << serverMask.networkManager; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::DescriptorCapabilities &descriptorCapabilities) { QDebugStateSaver saver(debug); debug.nospace() << "DescriptorCapabilities(" << ZigbeeUtils::convertByteToHexString(descriptorCapabilities.descriptorCapabilitiesFlag) << ")" << "\n"; debug.nospace() << " Extended active endpoint list available: " << descriptorCapabilities.extendedActiveEndpointListAvailable << "\n"; debug.nospace() << " Extended simple descriptor list available: " << descriptorCapabilities.extendedSimpleDescriptorListAvailable; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::PowerDescriptor &powerDescriptor) { QDebugStateSaver saver(debug); debug.nospace() << "PowerDescriptor(" << ZigbeeUtils::convertByteToHexString(powerDescriptor.powerDescriptoFlag) << ")" << "\n"; debug.nospace() << " Power mode: " << powerDescriptor.powerMode << "\n"; debug.nospace() << " Available power sources: " << powerDescriptor.availablePowerSources << "\n"; debug.nospace() << " Power source: " << powerDescriptor.powerSource << "\n"; debug.nospace() << " Power level: " << powerDescriptor.powerLevel; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::BindingTableListRecord &bindingTableListRecord) { QDebugStateSaver saver(debug); debug.nospace() << "BindingTableListRecord(" << bindingTableListRecord.sourceAddress.toString() << ", "; debug.nospace() << "source endpoint: " << bindingTableListRecord.sourceEndpoint << ", "; debug.nospace() << "cluster: " << static_cast(bindingTableListRecord.clusterId) << " --> "; switch (bindingTableListRecord.destinationAddressMode) { case Zigbee::DestinationAddressModeGroup: debug.nospace() << "destination address (group): " << ZigbeeUtils::convertUint16ToHexString(bindingTableListRecord.destinationShortAddress) << ") "; break; case Zigbee::DestinationAddressModeIeeeAddress: debug.nospace() << "destination address (unicast): " << bindingTableListRecord.destinationIeeeAddress.toString() << ", "; debug.nospace() << "destination endpoint: " << bindingTableListRecord.destinationEndpoint << ") "; break; default: break; } return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::NeighborTableListRecord &neighborTableListRecord) { QDebugStateSaver saver(debug); debug.nospace() << "NeighborTableListRecord(" << neighborTableListRecord.ieeeAddress.toString() << ", "; debug.nospace() << "NWK address: " << ZigbeeUtils::convertUint16ToHexString(neighborTableListRecord.shortAddress) << ", "; debug.nospace() << "Extended PAN ID: " << neighborTableListRecord.extendedPanId << ", "; debug.nospace() << "Node type: " << neighborTableListRecord.nodeType << ", "; debug.nospace() << "RxOn: " << neighborTableListRecord.receiverOnWhenIdle << ", "; debug.nospace() << "Relationship: " << neighborTableListRecord.relationship << ", "; debug.nospace() << "Permit join: " << neighborTableListRecord.permitJoining << ", "; debug.nospace() << "Depth: " << neighborTableListRecord.depth << ", "; debug.nospace() << "LQI: " << neighborTableListRecord.lqi << ")"; return debug; } QDebug operator<<(QDebug debug, const ZigbeeDeviceProfile::RoutingTableListRecord &routingTableListRecord) { QDebugStateSaver saver(debug); debug.nospace() << "RoutingTableListRecord("; debug.nospace() << "Destination address: " << ZigbeeUtils::convertUint16ToHexString(routingTableListRecord.destinationAddress) << ", "; debug.nospace() << "Next hop: " << routingTableListRecord.nextHopAddress << ", "; debug.nospace() << "Status: " << routingTableListRecord.status << ", "; debug.nospace() << "Memory constrained: " << routingTableListRecord.memoryConstrained << ", "; debug.nospace() << "Many-to-one: " << routingTableListRecord.manyToOne << ", "; debug.nospace() << "RRR: " << routingTableListRecord.routeRecordRequired << ")"; return debug; }