/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright 2013 - 2020, nymea GmbH * Contact: contact@nymea.io * * This file is part of nymea. * 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 "bluetoothgattcharacteristic.h" #include QString BluetoothGattCharacteristic::chararcteristicName() const { bool ok = false; quint16 typeId = m_uuid.toUInt16(&ok); if (ok) { QBluetoothUuid::CharacteristicType uuid = static_cast(typeId); const QString name = QBluetoothUuid::characteristicToString(uuid); if (!name.isEmpty()) return name; } return QString("Unknown Characteristic"); } QBluetoothUuid BluetoothGattCharacteristic::uuid() const { return m_uuid; } bool BluetoothGattCharacteristic::notifying() const { return m_notifying; } BluetoothGattCharacteristic::Properties BluetoothGattCharacteristic::properties() const { return m_properties; } QByteArray BluetoothGattCharacteristic::value() const { return m_value; } QList BluetoothGattCharacteristic::descriptors() const { return m_descriptors; } BluetoothGattDescriptor *BluetoothGattCharacteristic::getDescriptor(const QBluetoothUuid &desciptorUuid) const { foreach (BluetoothGattDescriptor *descriptor, m_descriptors) { if (descriptor->uuid() == desciptorUuid) { return descriptor; } } return nullptr; } BluetoothGattCharacteristic::BluetoothGattCharacteristic(const QDBusObjectPath &path, const QVariantMap &properties, QObject *parent) : QObject(parent), m_path(path), m_notifying(false) { m_characteristicInterface = new QDBusInterface(orgBluez, m_path.path(), orgBluezGattCharacteristic1, QDBusConnection::systemBus(), this); if (!m_characteristicInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path(); return; } QDBusConnection::systemBus().connect(orgBluez, m_path.path(), "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(onPropertiesChanged(QString,QVariantMap,QStringList))); processProperties(properties); } void BluetoothGattCharacteristic::processProperties(const QVariantMap &properties) { foreach (const QString &propertyName, properties.keys()) { if (propertyName == "UUID") { m_uuid = QBluetoothUuid(properties.value(propertyName).toString()); } else if (propertyName == "Notifying") { m_notifying = properties.value(propertyName).toBool(); emit notifyingChanged(m_notifying); } else if (propertyName == "Flags") { m_properties = parsePropertyFlags(properties.value(propertyName).toStringList()); } else if (propertyName == "Value") { m_value = properties.value(propertyName).toByteArray(); emit valueChanged(m_value); } } } void BluetoothGattCharacteristic::addDescriptorInternally(const QDBusObjectPath &path, const QVariantMap &properties) { if (hasDescriptor(path)) return; BluetoothGattDescriptor *descriptor = new BluetoothGattDescriptor(path, properties, this); m_descriptors.append(descriptor); qCDebug(dcBluez()) << "[+]" << descriptor; } bool BluetoothGattCharacteristic::hasDescriptor(const QDBusObjectPath &path) { foreach (BluetoothGattDescriptor *descriptor, m_descriptors) { if (descriptor->m_path == path) { return true; } } return false; } BluetoothGattDescriptor *BluetoothGattCharacteristic::getDescriptor(const QDBusObjectPath &path) { foreach (BluetoothGattDescriptor *descriptor, m_descriptors) { if (descriptor->m_path == path) { return descriptor; } } return nullptr; } void BluetoothGattCharacteristic::setValueInternally(const QByteArray &value) { if (m_value != value) { m_value = value; emit valueChanged(m_value); } } void BluetoothGattCharacteristic::setNotifyingInternally(const bool ¬ifying) { if (m_notifying != notifying) { m_notifying = notifying; emit notifyingChanged(m_notifying); } } BluetoothGattCharacteristic::Properties BluetoothGattCharacteristic::parsePropertyFlags(const QStringList &characteristicProperties) { Properties properties; foreach (const QString &propertyString, characteristicProperties) { if (propertyString == "broadcast") { properties |= Broadcasting; } else if (propertyString == "read") { properties |= Read; } else if (propertyString == "write-without-response") { properties |= WriteNoResponse; } else if (propertyString == "write") { properties |= Write; } else if (propertyString == "notify") { properties |= Notify; } else if (propertyString == "indicate") { properties |= Indicate; } else if (propertyString == "authenticated-signed-writes") { properties |= WriteAuthenticatedSigned; } else if (propertyString == "reliable-write") { properties |= ReliableWrite; } else if (propertyString == "writable-auxiliaries") { properties |= WritableAuxiliaries; } else if (propertyString == "encrypt-read") { properties |= EncryptRead; } else if (propertyString == "encrypt-write") { properties |= EncryptWrite; } else if (propertyString == "encrypt-authenticated-read") { properties |= EncryptAuthenticatedRead; } else if (propertyString == "encrypt-authenticated-write") { properties |= EncryptAuthenticatedWrite; } else if (propertyString == "secure-read") { properties |= SecureRead; } else if (propertyString == "secure-write") { properties |= Unknown; } } return properties; } void BluetoothGattCharacteristic::onPropertiesChanged(const QString &interface, const QVariantMap &changedProperties, const QStringList &invalidatedProperties) { if (interface != orgBluezGattCharacteristic1) return; qCDebug(dcBluez()) << "BluetoothCharacteristic:" << m_uuid.toString() << "properties changed" << interface << changedProperties << invalidatedProperties; processProperties(changedProperties); } void BluetoothGattCharacteristic::onReadingFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qCWarning(dcBluez()) << "Could not read characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message(); } else { QByteArray value = reply.argumentAt<0>(); qCDebug(dcBluez()) << "Async reading finished for" << m_uuid.toString() << value; setValueInternally(value); emit readingFinished(value); } call->deleteLater(); } void BluetoothGattCharacteristic::onWritingFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qCWarning(dcBluez()) << "Could not write characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message(); } else { QByteArray value = m_asyncWrites.take(call); qCDebug(dcBluez()) << "Async characteristic writing finished for" << m_uuid.toString() << value; emit writingFinished(value); } call->deleteLater(); } void BluetoothGattCharacteristic::onStartNotificationFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) qCWarning(dcBluez()) << "Could not start notifications on characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message(); call->deleteLater(); } void BluetoothGattCharacteristic::onStopNotificationFinished(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) qCWarning(dcBluez()) << "Could not stop notifications on characteristic" << m_uuid.toString() << reply.error().name() << reply.error().message(); call->deleteLater(); } bool BluetoothGattCharacteristic::readCharacteristic() { if (!m_characteristicInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path(); return false; } QDBusPendingCall readingCall = m_characteristicInterface->asyncCall("ReadValue", QVariantMap()); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(readingCall, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onReadingFinished); return true; } bool BluetoothGattCharacteristic::writeCharacteristic(const QByteArray &value) { if (!m_characteristicInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path(); return false; } QDBusPendingCall writingCall = m_characteristicInterface->asyncCall("WriteValue", value, QVariantMap()); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(writingCall, this); m_asyncWrites.insert(watcher, value); connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onWritingFinished); return true; } bool BluetoothGattCharacteristic::startNotifications() { if (!m_characteristicInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path(); return false; } // If already notifying if (notifying()) return true; QDBusPendingCall startNotifyCall = m_characteristicInterface->asyncCall("StartNotify"); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(startNotifyCall, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onStartNotificationFinished); return true; } bool BluetoothGattCharacteristic::stopNotifications() { if (!m_characteristicInterface->isValid()) { qCWarning(dcBluez()) << "Invalid DBus characteristic interface for" << m_path.path(); return false; } // If already stopped if (!notifying()) return true; QDBusPendingCall stopNotifyCall = m_characteristicInterface->asyncCall("StopNotify"); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(stopNotifyCall, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &BluetoothGattCharacteristic::onStopNotificationFinished); return true; } QDebug operator<<(QDebug debug, BluetoothGattCharacteristic *characteristic) { debug.noquote().nospace() << "GattCharacteristic(" << characteristic->chararcteristicName(); debug.noquote().nospace() << ", " << characteristic->uuid().toString(); debug.noquote().nospace() << ", Properties: " << characteristic->properties(); if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Unknown)) debug.noquote().nospace() << " Unknown"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Broadcasting)) debug.noquote().nospace() << " B"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Read)) debug.noquote().nospace() << " R"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WriteNoResponse)) debug.noquote().nospace() << " WNR"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Write)) debug.noquote().nospace() << " W"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Notify)) debug.noquote().nospace() << " N"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Indicate)) debug.noquote().nospace() << " I"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WriteAuthenticatedSigned)) debug.noquote().nospace() << " WAS"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::ReliableWrite)) debug.noquote().nospace() << " RW"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::WritableAuxiliaries)) debug.noquote().nospace() << " WA"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptWrite)) debug.noquote().nospace() << " EW"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptRead)) debug.noquote().nospace() << " ER"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptAuthenticatedRead)) debug.noquote().nospace() << " EAR"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::EncryptAuthenticatedWrite)) debug.noquote().nospace() << " EAW"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::SecureRead)) debug.noquote().nospace() << " SR"; if (characteristic->properties().testFlag(BluetoothGattCharacteristic::SecureWrite)) debug.noquote().nospace() << " SW"; // If this is a notifying characteristic, inform if notifications are enabled if (characteristic->properties().testFlag(BluetoothGattCharacteristic::Notify)) debug.noquote().nospace() << ", Notify: " << (characteristic->notifying() ? "ON" : "OFF"); debug.noquote().nospace() << ", value:" << characteristic->value(); debug.noquote().nospace() << ") "; return debug; }