| 1 | // Copyright (C) 2022 The Qt Company Ltd. | 
| 2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only | 
| 3 |  | 
| 4 | #include "bluezperipheralobjects_p.h" | 
| 5 |  | 
| 6 | #include "propertiesadaptor_p.h" | 
| 7 | #include "gattservice1adaptor_p.h" | 
| 8 | #include "gattcharacteristic1adaptor_p.h" | 
| 9 | #include "gattdescriptor1adaptor_p.h" | 
| 10 |  | 
| 11 | #include <QtCore/QLoggingCategory> | 
| 12 | #include <QtDBus/QDBusConnection> | 
| 13 |  | 
| 14 | QT_BEGIN_NAMESPACE | 
| 15 |  | 
| 16 | Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) | 
| 17 |  | 
| 18 | using namespace Qt::StringLiterals; | 
| 19 |  | 
| 20 | static constexpr auto characteristicPathTemplate{"%1/char%2"_L1 }; | 
| 21 | static constexpr auto descriptorPathTemplate{"%1/desc%2"_L1 }; | 
| 22 | static constexpr auto servicePathTemplate{"%1/service%2"_L1 }; | 
| 23 |  | 
| 24 | // The interface names and error values are from BlueZ "gatt-api" documentation | 
| 25 | static constexpr auto bluezServiceInterface{"org.bluez.GattService1"_L1 }; | 
| 26 | static constexpr auto bluezCharacteristicInterface{"org.bluez.GattCharacteristic1"_L1 }; | 
| 27 | static constexpr auto bluezDescriptorInterface{"org.bluez.GattDescriptor1"_L1 }; | 
| 28 |  | 
| 29 | static constexpr auto bluezErrorInvalidValueLength{"org.bluez.Error.InvalidValueLength"_L1 }; | 
| 30 | static constexpr auto bluezErrorInvalidOffset{"org.bluez.Error.InvalidOffset"_L1 }; | 
| 31 | static constexpr auto bluezErrorNotAuthorized{"org.bluez.Error.NotAuthorized"_L1 }; | 
| 32 | // Bluetooth Core v5.3, 3.2.9, Vol 3, Part F | 
| 33 | static constexpr int maximumAttributeLength{512}; | 
| 34 |  | 
| 35 |  | 
| 36 | QtBluezPeripheralGattObject::QtBluezPeripheralGattObject(const QString& objectPath, | 
| 37 |            const QString& uuid, QLowEnergyHandle handle, QObject* parent) | 
| 38 |     : QObject(parent), objectPath(objectPath), uuid(uuid), handle(handle), | 
| 39 |       propertiesAdaptor(new OrgFreedesktopDBusPropertiesAdaptor(this)) | 
| 40 | {} | 
| 41 |  | 
| 42 | QtBluezPeripheralGattObject::~QtBluezPeripheralGattObject() | 
| 43 | { | 
| 44 |     unregisterObject(); | 
| 45 | } | 
| 46 |  | 
| 47 | bool QtBluezPeripheralGattObject::registerObject() | 
| 48 | { | 
| 49 |     if (m_registered) | 
| 50 |         return true; | 
| 51 |  | 
| 52 |     if (QDBusConnection::systemBus().registerObject(path: objectPath, object: this)) { | 
| 53 |         qCDebug(QT_BT_BLUEZ) << "Registered object on DBus:"  << objectPath << uuid; | 
| 54 |         m_registered = true; | 
| 55 |         return true; | 
| 56 |     } else { | 
| 57 |         qCWarning(QT_BT_BLUEZ) << "Failed to register object on DBus:"  << objectPath << uuid; | 
| 58 |         return false; | 
| 59 |     } | 
| 60 | } | 
| 61 |  | 
| 62 | void QtBluezPeripheralGattObject::unregisterObject() | 
| 63 | { | 
| 64 |     if (!m_registered) | 
| 65 |         return; | 
| 66 |     QDBusConnection::systemBus().unregisterObject(path: objectPath); | 
| 67 |     qCDebug(QT_BT_BLUEZ) << "Unregistered object on DBus:"  << objectPath << uuid; | 
| 68 |     m_registered = false; | 
| 69 | } | 
| 70 |  | 
| 71 | void QtBluezPeripheralGattObject::accessEvent(const QVariantMap& options) | 
| 72 | { | 
| 73 |     // Report this event for connection management purposes | 
| 74 |     const auto remoteDevice = options.value(key: "device"_L1 ).value<QDBusObjectPath>().path(); | 
| 75 |     if (!remoteDevice.isEmpty()) | 
| 76 |         emit remoteDeviceAccessEvent(remoteDeviceObjectPath: remoteDevice, mtu: options.value(key: "mtu"_L1 ).toUInt()); | 
| 77 | } | 
| 78 |  | 
| 79 | QtBluezPeripheralDescriptor::QtBluezPeripheralDescriptor( | 
| 80 |                          const QLowEnergyDescriptorData& descriptorData, | 
| 81 |                          const QString& characteristicPath, quint16 ordinal, | 
| 82 |                          QLowEnergyHandle handle, QLowEnergyHandle characteristicHandle, | 
| 83 |                          QObject* parent) | 
| 84 |     : QtBluezPeripheralGattObject(descriptorPathTemplate.arg(args: characteristicPath).arg(a: ordinal), | 
| 85 |                  descriptorData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent), | 
| 86 |       m_adaptor(new OrgBluezGattDescriptor1Adaptor(this)), | 
| 87 |       m_characteristicPath(characteristicPath), | 
| 88 |       m_characteristicHandle(characteristicHandle) | 
| 89 | { | 
| 90 |     if (descriptorData.value().size() > maximumAttributeLength) { | 
| 91 |         qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large, cropping it to"  | 
| 92 |                                << maximumAttributeLength; | 
| 93 |         m_value = descriptorData.value().sliced(pos: 0, n: maximumAttributeLength); | 
| 94 |     } else { | 
| 95 |         m_value = descriptorData.value(); | 
| 96 |     } | 
| 97 |     initializeFlags(data: descriptorData); | 
| 98 | } | 
| 99 |  | 
| 100 | InterfaceList QtBluezPeripheralDescriptor::properties() const | 
| 101 | { | 
| 102 |     InterfaceList properties; | 
| 103 |     properties.insert(key: bluezDescriptorInterface, | 
| 104 |                       value: { | 
| 105 |                           {"UUID"_L1 , uuid}, | 
| 106 |                           {"Characteristic"_L1 , QDBusObjectPath(m_characteristicPath)}, | 
| 107 |                           {"Flags"_L1 , m_flags} | 
| 108 |                       }); | 
| 109 |     return  properties; | 
| 110 | } | 
| 111 |  | 
| 112 | // org.bluez.GattDescriptor1 | 
| 113 | // This function is invoked when remote device reads the value | 
| 114 | QByteArray QtBluezPeripheralDescriptor::ReadValue(const QVariantMap &options, QString& error) | 
| 115 | { | 
| 116 |     accessEvent(options); | 
| 117 |     // Offset is set by Bluez when the value size is more than MTU size. | 
| 118 |     // Bluez deduces the value size from the first ReadValue. If the | 
| 119 |     // received data size is larger than MTU, Bluez will take the first MTU bytes and | 
| 120 |     // issue more ReadValue calls with the 'offset' set | 
| 121 |     const quint16 offset = options.value(key: "offset"_L1 ).toUInt(); | 
| 122 |     const quint16 mtu = options.value(key: "mtu"_L1 ).toUInt(); | 
| 123 |  | 
| 124 |     if (offset > m_value.length() - 1) { | 
| 125 |         qCWarning(QT_BT_BLUEZ) << "Invalid offset"  << offset << ", value len:"  << m_value.length(); | 
| 126 |         error = bluezErrorInvalidOffset; | 
| 127 |         return {}; | 
| 128 |     } | 
| 129 |  | 
| 130 |     if (offset > 0) | 
| 131 |         return m_value.mid(index: offset, len: mtu); | 
| 132 |     else | 
| 133 |         return m_value; | 
| 134 | } | 
| 135 |  | 
| 136 | // org.bluez.GattDescriptor1 | 
| 137 | // This function is invoked when remote device writes a value | 
| 138 | QString QtBluezPeripheralDescriptor::WriteValue(const QByteArray &value, | 
| 139 |                                                 const QVariantMap &options) | 
| 140 | { | 
| 141 |     accessEvent(options); | 
| 142 |  | 
| 143 |     if (options.value(key: "prepare-authorize"_L1 ).toBool()) { | 
| 144 |         // Qt API doesn't provide the means for application to authorize | 
| 145 |         qCWarning(QT_BT_BLUEZ) << "Descriptor write requires authorization."  | 
| 146 |                                << "The client device needs to be trusted beforehand" ; | 
| 147 |         return bluezErrorNotAuthorized; | 
| 148 |     } | 
| 149 |  | 
| 150 |     if (value.size() > maximumAttributeLength) { | 
| 151 |         qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:"  << value.size(); | 
| 152 |         return bluezErrorInvalidValueLength; | 
| 153 |     } | 
| 154 |     m_value = value; | 
| 155 |     emit valueUpdatedByRemote(characteristicHandle: m_characteristicHandle, descriptorHandle: handle, value); | 
| 156 |     return {}; | 
| 157 | } | 
| 158 |  | 
| 159 | // This function is called when the value has been updated locally (server-side) | 
| 160 | bool QtBluezPeripheralDescriptor::localValueUpdate(const QByteArray& value) | 
| 161 | { | 
| 162 |     if (value.size() > maximumAttributeLength) { | 
| 163 |         qCWarning(QT_BT_BLUEZ) << "Descriptor value is too large:"  << value.size(); | 
| 164 |         return false; | 
| 165 |     } | 
| 166 |     m_value = value; | 
| 167 |     return true; | 
| 168 | } | 
| 169 |  | 
| 170 | void QtBluezPeripheralDescriptor::initializeFlags(const QLowEnergyDescriptorData& data) | 
| 171 | { | 
| 172 |     // Flag tokens are from org.bluez.GattDescriptor1 documentation | 
| 173 |     if (data.isReadable()) | 
| 174 |         m_flags.append(t: "read"_L1 ); | 
| 175 |     if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) | 
| 176 |         m_flags.append(t: "encrypt-read"_L1 ); | 
| 177 |     if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) | 
| 178 |         m_flags.append(t: "encrypt-authenticated-read"_L1 ); | 
| 179 |  | 
| 180 |     if (data.isWritable()) | 
| 181 |         m_flags.append(t: "write"_L1 ); | 
| 182 |     if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) | 
| 183 |         m_flags.append(t: "encrypt-write"_L1 ); | 
| 184 |     if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) | 
| 185 |         m_flags.append(t: "encrypt-authenticated-write"_L1 ); | 
| 186 |  | 
| 187 |     if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired | 
| 188 |             || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired) | 
| 189 |         m_flags.append(t: "authorize"_L1 ); | 
| 190 |  | 
| 191 |     if (m_flags.isEmpty()) { | 
| 192 |         qCWarning(QT_BT_BLUEZ) << "Descriptor property flags not set"  << uuid | 
| 193 |                                << "Peripheral may fail to register" ; | 
| 194 |     } | 
| 195 | } | 
| 196 |  | 
| 197 | QtBluezPeripheralCharacteristic::QtBluezPeripheralCharacteristic( | 
| 198 |         const QLowEnergyCharacteristicData& characteristicData, | 
| 199 |         const QString& servicePath, quint16 ordinal, | 
| 200 |         QLowEnergyHandle handle, QObject* parent) | 
| 201 |     : QtBluezPeripheralGattObject(characteristicPathTemplate.arg(args: servicePath).arg(a: ordinal), | 
| 202 |                  characteristicData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent), | 
| 203 |       m_adaptor(new OrgBluezGattCharacteristic1Adaptor(this)), | 
| 204 |       m_servicePath(servicePath), | 
| 205 |       m_minimumValueLength(std::min(a: characteristicData.minimumValueLength(), | 
| 206 |                                     b: maximumAttributeLength)), | 
| 207 |       m_maximumValueLength(std::min(a: characteristicData.maximumValueLength(), | 
| 208 |                                     b: maximumAttributeLength)) | 
| 209 | { | 
| 210 |     initializeFlags(data: characteristicData); | 
| 211 |     initializeValue(value: characteristicData.value()); | 
| 212 | } | 
| 213 |  | 
| 214 | InterfaceList QtBluezPeripheralCharacteristic::properties() const | 
| 215 | { | 
| 216 |     InterfaceList properties; | 
| 217 |     properties.insert(key: bluezCharacteristicInterface, | 
| 218 |                       value: { | 
| 219 |                           {"UUID"_L1 , uuid}, | 
| 220 |                           {"Service"_L1 , QDBusObjectPath(m_servicePath)}, | 
| 221 |                           {"Flags"_L1 , m_flags} | 
| 222 |                       }); | 
| 223 |     return properties; | 
| 224 | } | 
| 225 |  | 
| 226 | // org.bluez.GattCharacteristic1 | 
| 227 | // This function is invoked when remote device reads the value | 
| 228 | QByteArray QtBluezPeripheralCharacteristic::ReadValue(const QVariantMap &options, QString& error) | 
| 229 | { | 
| 230 |     accessEvent(options); | 
| 231 |     // Offset is set by Bluez when the value size is more than MTU size. | 
| 232 |     // Bluez deduces the value size from the first ReadValue. If the | 
| 233 |     // received data size is larger than MTU, Bluez will take the first MTU bytes and | 
| 234 |     // issue more ReadValue calls with the 'offset' set | 
| 235 |     const quint16 offset = options.value(key: "offset"_L1 ).toUInt(); | 
| 236 |     const quint16 mtu = options.value(key: "mtu"_L1 ).toUInt(); | 
| 237 |  | 
| 238 |     if (offset > m_value.length() - 1) { | 
| 239 |         qCWarning(QT_BT_BLUEZ) << "Invalid offset"  << offset << ", value len:"  << m_value.length(); | 
| 240 |         error = bluezErrorInvalidOffset; | 
| 241 |         return {}; | 
| 242 |     } | 
| 243 |  | 
| 244 |     if (offset > 0) | 
| 245 |         return m_value.mid(index: offset, len: mtu); | 
| 246 |     else | 
| 247 |         return m_value; | 
| 248 | } | 
| 249 |  | 
| 250 | // org.bluez.GattCharacteristic1 | 
| 251 | // This function is invoked when remote device writes a value | 
| 252 | QString QtBluezPeripheralCharacteristic::WriteValue(const QByteArray &value, | 
| 253 |                                                     const QVariantMap &options) | 
| 254 | { | 
| 255 |     accessEvent(options); | 
| 256 |  | 
| 257 |     if (options.value(key: "prepare-authorize"_L1 ).toBool()) { | 
| 258 |         // Qt API doesn't provide the means for application to authorize | 
| 259 |         qCWarning(QT_BT_BLUEZ) << "Characteristic write requires authorization."  | 
| 260 |                                << "The client device needs to be trusted beforehand" ; | 
| 261 |         return bluezErrorNotAuthorized; | 
| 262 |     } | 
| 263 |  | 
| 264 |     if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) { | 
| 265 |         qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length"  << value.size() | 
| 266 |                                << "min:"  << m_minimumValueLength | 
| 267 |                                << "max:"  << m_maximumValueLength; | 
| 268 |         return bluezErrorInvalidValueLength; | 
| 269 |     } | 
| 270 |     m_value = value; | 
| 271 |     emit valueUpdatedByRemote(handle, value); | 
| 272 |     return {}; | 
| 273 | } | 
| 274 |  | 
| 275 | // This function is called when the value has been updated locally (server-side) | 
| 276 | bool QtBluezPeripheralCharacteristic::localValueUpdate(const QByteArray& value) | 
| 277 | { | 
| 278 |     if (value.size() < m_minimumValueLength || value.size() > m_maximumValueLength) { | 
| 279 |         qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length"  << value.size() | 
| 280 |                                << "min:"  << m_minimumValueLength | 
| 281 |                                << "max:"  << m_maximumValueLength; | 
| 282 |         return false; | 
| 283 |     } | 
| 284 |     m_value = value; | 
| 285 |     if (m_notifying) { | 
| 286 |         emit propertiesAdaptor->PropertiesChanged( | 
| 287 |                     interface: bluezCharacteristicInterface, changed_properties: {{"Value"_L1 , m_value}}, invalidated_properties: {}); | 
| 288 |     } | 
| 289 |     return true; | 
| 290 | } | 
| 291 |  | 
| 292 | // org.bluez.GattCharacteristic1 | 
| 293 | // These are called when remote client enables or disables NTF/IND | 
| 294 | void QtBluezPeripheralCharacteristic::StartNotify() | 
| 295 | { | 
| 296 |     qCDebug(QT_BT_BLUEZ) << "NTF or IND enabled for characteristic"  << uuid; | 
| 297 |     m_notifying = true; | 
| 298 | } | 
| 299 |  | 
| 300 | void QtBluezPeripheralCharacteristic::StopNotify() | 
| 301 | { | 
| 302 |     qCDebug(QT_BT_BLUEZ) << "NTF or IND disabled for characteristic"  << uuid; | 
| 303 |     m_notifying = false; | 
| 304 | } | 
| 305 |  | 
| 306 |  | 
| 307 | void QtBluezPeripheralCharacteristic::initializeValue(const QByteArray& value) | 
| 308 | { | 
| 309 |     const auto valueSize = value.size(); | 
| 310 |     if (valueSize < m_minimumValueLength || valueSize > m_maximumValueLength) { | 
| 311 |         qCWarning(QT_BT_BLUEZ) << "Characteristic value has invalid length"  << valueSize | 
| 312 |                                << "min:"  << m_minimumValueLength | 
| 313 |                                << "max:"  << m_maximumValueLength; | 
| 314 |         m_value = QByteArray(m_minimumValueLength, 0); | 
| 315 |     } else { | 
| 316 |         m_value = value; | 
| 317 |     } | 
| 318 | } | 
| 319 |  | 
| 320 | void QtBluezPeripheralCharacteristic::initializeFlags(const QLowEnergyCharacteristicData& data) | 
| 321 | { | 
| 322 |     // Flag tokens are from org.bluez.GattCharacteristic1 documentation | 
| 323 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::Broadcasting) | 
| 324 |         m_flags.append(t: "broadcast"_L1 ); | 
| 325 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteNoResponse) | 
| 326 |         m_flags.append(t: "write-without-response"_L1 ); | 
| 327 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::Read) | 
| 328 |         m_flags.append(t: "read"_L1 ); | 
| 329 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::Write) | 
| 330 |         m_flags.append(t: "write"_L1 ); | 
| 331 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::Notify) | 
| 332 |         m_flags.append(t: "notify"_L1 ); | 
| 333 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::Indicate) | 
| 334 |         m_flags.append(t: "indicate"_L1 ); | 
| 335 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::WriteSigned) | 
| 336 |         m_flags.append(t: "authenticated-signed-writes"_L1 ); | 
| 337 |     if (data.properties() & QLowEnergyCharacteristic::PropertyType::ExtendedProperty) { | 
| 338 |         // If extended properties property is set, check if we have the descriptor | 
| 339 |         // describing them. Bluez will generate the actual descriptor based on these | 
| 340 |         // flags. For clarity: the 'extended-properties' token mentioned in the Bluez | 
| 341 |         // API is implied by these flags. | 
| 342 |         for (const auto& descriptor : data.descriptors()) { | 
| 343 |             // Core Bluetooth v5.3 Vol 3, Part G, 3.3.3.1 | 
| 344 |             if (descriptor.uuid() | 
| 345 |                     == QBluetoothUuid::DescriptorType::CharacteristicExtendedProperties | 
| 346 |                     && descriptor.value().size() == 2) { | 
| 347 |                 const auto properties = descriptor.value().at(i: 0); | 
| 348 |                 if (properties & 0x01) | 
| 349 |                     m_flags.append(t: "reliable-write"_L1 ); | 
| 350 |                 if (properties & 0x02) | 
| 351 |                     m_flags.append(t: "writable-auxiliaries"_L1 ); | 
| 352 |             } | 
| 353 |         } | 
| 354 |     } | 
| 355 |  | 
| 356 |     if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) | 
| 357 |         m_flags.append(t: "encrypt-read"_L1 ); | 
| 358 |     if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) | 
| 359 |         m_flags.append(t: "encrypt-authenticated-read"_L1 ); | 
| 360 |     if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttEncryptionRequired) | 
| 361 |         m_flags.append(t: "encrypt-write"_L1 ); | 
| 362 |     if (data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthenticationRequired) | 
| 363 |         m_flags.append(t: "encrypt-authenticated-write"_L1 ); | 
| 364 |  | 
| 365 |     if (data.readConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired | 
| 366 |             || data.writeConstraints() & QBluetooth::AttAccessConstraint::AttAuthorizationRequired) | 
| 367 |         m_flags.append(t: "authorize"_L1 ); | 
| 368 |  | 
| 369 |     if (m_flags.isEmpty()) { | 
| 370 |         qCWarning(QT_BT_BLUEZ) << "Characteristic property flags not set"  << uuid | 
| 371 |                                << "Peripheral may fail to register" ; | 
| 372 |     } | 
| 373 | } | 
| 374 |  | 
| 375 |  | 
| 376 | QtBluezPeripheralService::QtBluezPeripheralService(const QLowEnergyServiceData &serviceData, | 
| 377 |                       const QString& applicationPath, quint16 ordinal, | 
| 378 |                       QLowEnergyHandle handle, QObject* parent) | 
| 379 |     : QtBluezPeripheralGattObject(servicePathTemplate.arg(args: applicationPath).arg(a: ordinal), | 
| 380 |                  serviceData.uuid().toString(mode: QUuid::WithoutBraces), handle, parent), | 
| 381 |       m_isPrimary(serviceData.type() == QLowEnergyServiceData::ServiceTypePrimary), | 
| 382 |       m_adaptor(new OrgBluezGattService1Adaptor(this)) | 
| 383 | { | 
| 384 | } | 
| 385 |  | 
| 386 | void QtBluezPeripheralService::addIncludedService(const QString& objectPath) { | 
| 387 |     qCDebug(QT_BT_BLUEZ) << "Adding included service"  << objectPath << "for"  << uuid; | 
| 388 |     m_includedServices.append(t: QDBusObjectPath(objectPath)); | 
| 389 | } | 
| 390 |  | 
| 391 | InterfaceList QtBluezPeripheralService::properties() const { | 
| 392 |     InterfaceList interfaces; | 
| 393 |     interfaces.insert(key: bluezServiceInterface,value: { | 
| 394 |                           {"UUID"_L1 , uuid}, | 
| 395 |                           {"Primary"_L1 , m_isPrimary}, | 
| 396 |                           {"Includes"_L1 , QVariant::fromValue(value: m_includedServices)} | 
| 397 |                       }); | 
| 398 |     return interfaces; | 
| 399 | }; | 
| 400 |  | 
| 401 | QT_END_NAMESPACE | 
| 402 |  | 
| 403 | #include "moc_bluezperipheralobjects_p.cpp" | 
| 404 |  |