diff options
author | Alex Blasche <alexander.blasche@digia.com> | 2014-08-14 09:57:07 +0200 |
---|---|---|
committer | Alex Blasche <alexander.blasche@digia.com> | 2014-08-14 09:58:35 +0200 |
commit | 67d8044bcc330d9aa139ed3026b6b0603a7a990a (patch) | |
tree | eba72249447d6e2a3f28ed5b2cbfb351a0bc2053 | |
parent | 687c885b8625374433f4ccc8b6442ea72ea62d46 (diff) | |
parent | 05dc94529b1a8f7c65c8aebbf8081d1d1f3aa96a (diff) |
Merge remote-tracking branch 'gerrit/btle' into 5.4
Change-Id: I6786b50e3f302c6027255dc2393b3e7923705a1d
116 files changed, 13387 insertions, 168 deletions
@@ -24,10 +24,13 @@ tmp imports/* include config.tests/bluez/bluez +config.tests/bluez_le/bluez_le examples/bluetooth/btchat/btchat examples/bluetooth/btfiletransfer/btfiletransfer examples/bluetooth/btscanner/btscanner -examples/bluetooth/bttennis/bttennis +examples/bluetooth/heartlistener/heartlistener +examples/bluetooth/lowenergyscanner/lowenergyscanner +examples/bluetooth/pingpong/pingpong examples/bluetooth/picturetransfer/qml_picturetransfer examples/bluetooth/scanner/qml_scanner examples/nfc/annotatedurl/annotatedurl @@ -49,6 +52,10 @@ tests/auto/qbluetoothsocket/tst_qbluetoothsocket tests/auto/qbluetoothtransfermanager/tst_qbluetoothtransfermanager tests/auto/qbluetoothtransferrequest/tst_qbluetoothtransferrequest tests/auto/qbluetoothuuid/tst_qbluetoothuuid +tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic +tests/auto/qlowenergycontroller/tst_qlowenergycontroller +tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor +tests/auto/qlowenergyserviceinfo/tst_qlowenergyserviceinfo tests/auto/qndefmessage/tst_qndefmessage tests/auto/qndefnfcsmartposterrecord/tst_qndefnfcsmartposterrecord tests/auto/qndefrecord/tst_qndefrecord diff --git a/config.tests/bluez_le/bluez_le.pro b/config.tests/bluez_le/bluez_le.pro new file mode 100644 index 00000000..27b19a45 --- /dev/null +++ b/config.tests/bluez_le/bluez_le.pro @@ -0,0 +1,8 @@ +TEMPLATE = app + +CONFIG += link_pkgconfig +PKGCONFIG += bluez + +TARGET = bluez_le + +SOURCES += main.cpp diff --git a/config.tests/bluez_le/main.cpp b/config.tests/bluez_le/main.cpp new file mode 100644 index 00000000..ae23f6a6 --- /dev/null +++ b/config.tests/bluez_le/main.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtConnectivity module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> + +int main() +{ + sockaddr_l2 addr; + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = 4; + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; +} diff --git a/examples/bluetooth/bluetooth.pro b/examples/bluetooth/bluetooth.pro index a0379f6c..4576956f 100644 --- a/examples/bluetooth/bluetooth.pro +++ b/examples/bluetooth/bluetooth.pro @@ -5,4 +5,8 @@ qtHaveModule(widgets) { btfiletransfer } -qtHaveModule(quick): SUBDIRS += scanner picturetransfer pingpong +qtHaveModule(quick): SUBDIRS += scanner \ + picturetransfer \ + pingpong \ + lowenergyscanner \ + heartlistener diff --git a/examples/bluetooth/btchat/chatserver.cpp b/examples/bluetooth/btchat/chatserver.cpp index 0b29b87b..6da4b471 100644 --- a/examples/bluetooth/btchat/chatserver.cpp +++ b/examples/bluetooth/btchat/chatserver.cpp @@ -83,7 +83,9 @@ void ChatServer::startServer(const QBluetoothAddress& localAdapter) classId); classId.prepend(QVariant::fromValue(QBluetoothUuid(serviceUuid))); + serviceInfo.setAttribute(QBluetoothServiceInfo::ServiceClassIds, classId); + serviceInfo.setAttribute(QBluetoothServiceInfo::BluetoothProfileDescriptorList,classId); //! [Class Uuuid must contain at least 1 entry] diff --git a/examples/bluetooth/btfiletransfer/btfiletransfer.pro b/examples/bluetooth/btfiletransfer/btfiletransfer.pro index 00b16415..e5d47302 100644 --- a/examples/bluetooth/btfiletransfer/btfiletransfer.pro +++ b/examples/bluetooth/btfiletransfer/btfiletransfer.pro @@ -26,5 +26,5 @@ OTHER_FILES += \ RESOURCES += \ btfiletransfer.qrc -target.path = $$[QT_INSTALL_EXAMPLES]/bluetooth/scanner +target.path = $$[QT_INSTALL_EXAMPLES]/bluetooth/btfiletransfer INSTALLS += target diff --git a/examples/bluetooth/btscanner/service.cpp b/examples/bluetooth/btscanner/service.cpp index d9172d69..d24837eb 100644 --- a/examples/bluetooth/btscanner/service.cpp +++ b/examples/bluetooth/btscanner/service.cpp @@ -1,6 +1,7 @@ /**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtBluetooth module of the Qt Toolkit. @@ -44,6 +45,8 @@ #include <qbluetoothservicediscoveryagent.h> #include <qbluetoothserviceinfo.h> #include <qbluetoothlocaldevice.h> +#include <qlowenergyserviceinfo.h> +#include <qbluetoothuuid.h> ServiceDiscoveryDialog::ServiceDiscoveryDialog(const QString &name, @@ -74,6 +77,8 @@ ServiceDiscoveryDialog::ServiceDiscoveryDialog(const QString &name, connect(discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)), this, SLOT(addService(QBluetoothServiceInfo))); connect(discoveryAgent, SIGNAL(finished()), ui->status, SLOT(hide())); + connect(discoveryAgent, SIGNAL(serviceDiscovered(const QLowEnergyServiceInfo&)), + this, SLOT(addLowEnergyService(const QLowEnergyServiceInfo&))); discoveryAgent->start(); } @@ -98,3 +103,10 @@ void ServiceDiscoveryDialog::addService(const QBluetoothServiceInfo &info) ui->list->addItem(line); } +void ServiceDiscoveryDialog::addLowEnergyService(const QLowEnergyServiceInfo &gatt) +{ + QString line = gatt.serviceName(); + + ui->list->addItem(line); +} + diff --git a/examples/bluetooth/btscanner/service.h b/examples/bluetooth/btscanner/service.h index 293bc7a9..2089eb87 100644 --- a/examples/bluetooth/btscanner/service.h +++ b/examples/bluetooth/btscanner/service.h @@ -49,6 +49,8 @@ QT_FORWARD_DECLARE_CLASS(QBluetoothAddress) QT_FORWARD_DECLARE_CLASS(QBluetoothServiceInfo) QT_FORWARD_DECLARE_CLASS(QBluetoothServiceDiscoveryAgent) +QT_FORWARD_DECLARE_CLASS (QLowEnergyServiceInfo) +QT_FORWARD_DECLARE_CLASS (QLowEnergyCharacteristicInfo) QT_USE_NAMESPACE @@ -62,6 +64,7 @@ public: public slots: void addService(const QBluetoothServiceInfo&); + void addLowEnergyService(const QLowEnergyServiceInfo&); private: QBluetoothServiceDiscoveryAgent *discoveryAgent; diff --git a/examples/bluetooth/heartlistener/assets/Button.qml b/examples/bluetooth/heartlistener/assets/Button.qml new file mode 100644 index 00000000..40e98875 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/Button.qml @@ -0,0 +1,77 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id:button + //color: "#3870BA" + + property real buttonWidth: 300 + property real buttonHeight: 80 + property string text: "Button" + + signal buttonClick() + width: click.pressed ? (buttonWidth - 15) : buttonWidth + height: click.pressed ? (buttonHeight - 15) :buttonHeight + + color: click.pressed ? "#3265A7" : "#3870BA" + + border.color: "#F0EBED" + border.width: 5 + radius: 10 + + Text { + id: label + font.pixelSize: 30; font.bold: true + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + elide: Text.ElideMiddle + color: "#F0EBED" + text: button.text + } + + MouseArea { + id: click + anchors.fill: parent + onClicked: buttonClick() + } +} diff --git a/examples/bluetooth/heartlistener/assets/Point.qml b/examples/bluetooth/heartlistener/assets/Point.qml new file mode 100644 index 00000000..2ed94a4d --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/Point.qml @@ -0,0 +1,51 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id: point + + Image { + width: 10; height: 7 + smooth: true + source: "blue_heart_small.png" + } +} diff --git a/examples/bluetooth/heartlistener/assets/blue_heart.png b/examples/bluetooth/heartlistener/assets/blue_heart.png Binary files differnew file mode 100644 index 00000000..997ee699 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/blue_heart.png diff --git a/examples/bluetooth/heartlistener/assets/blue_heart_small.png b/examples/bluetooth/heartlistener/assets/blue_heart_small.png Binary files differnew file mode 100644 index 00000000..7bd1f981 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/blue_heart_small.png diff --git a/examples/bluetooth/heartlistener/assets/busy_dark.png b/examples/bluetooth/heartlistener/assets/busy_dark.png Binary files differnew file mode 100644 index 00000000..3a105953 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/busy_dark.png diff --git a/examples/bluetooth/heartlistener/assets/dialog.qml b/examples/bluetooth/heartlistener/assets/dialog.qml new file mode 100644 index 00000000..45b64cf3 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/dialog.qml @@ -0,0 +1,61 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id: root + opacity: 0.8 + color: "#E0DEDF" + anchors.horizontalCenter: parent.horizontalCenter + + property int hr: heartRate.hr + Text { + text: heartRate.message + color: "#3870BA" + + } + + onHrChanged: { + if (heartRate.hr > 0) { + root.destroy() + } + } +} diff --git a/examples/bluetooth/heartlistener/assets/draw.js b/examples/bluetooth/heartlistener/assets/draw.js new file mode 100644 index 00000000..a5de5474 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/draw.js @@ -0,0 +1,74 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +var component; +var size = 0; +var counter = 0; +var difference = 0; + +function start() { + size = heartRate.measurementsSize(); + difference = (plot.width-topbar.width)/size; + console.log(size +" "+ plot.width); + for (var i = 0; i< size; i++) { + var value = heartRate.measurements(i); + + drawIt(value); + counter++; + } +} + +function drawIt(value) { + if (component == null) + component = Qt.createComponent("Point.qml"); + if (component.status == Component.Ready) { + var dynamicObject = component.createObject(plot); + if (dynamicObject == null) { + console.log("error creating block"); + console.log(component.errorString()); + return false; + } + dynamicObject.x = 10+(counter*difference); + console.log(plot.height) + dynamicObject.y = plot.height -value; + + } +} + diff --git a/examples/bluetooth/heartlistener/assets/home.qml b/examples/bluetooth/heartlistener/assets/home.qml new file mode 100644 index 00000000..2c5e9485 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/home.qml @@ -0,0 +1,186 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id: screen + color: "#F0EBED" + property string message: heartRate.message + onMessageChanged: { + if (heartRate.message != "Scanning for devices..." && heartRate.message != "Low Energy device found. Scanning for more...") { + background.visible = false; + demoMode.visible = true; + } + else { + demoMode.visible = false; + background.visible = true; + } + } + + Rectangle { + id:select + width: parent.width + anchors.top: parent.top + height: 80 + color: "#F0EBED" + border.color: "#3870BA" + border.width: 2 + radius: 10 + + Text { + id: selectText + color: "#3870BA" + font.pixelSize: 34 + anchors.centerIn: parent + text: "Select Device" + } + } + + Rectangle { + id: spinner + width: parent.width + anchors.top: select.bottom + anchors.bottom: demoMode.top + visible: false + color: "#F0EBED" + z: 100 + + Rectangle { + id: inside + anchors.centerIn: parent + Image { + id: background + + width:100 + height:100 + anchors.horizontalCenter: parent.horizontalCenter + + source: "busy_dark.png" + fillMode: Image.PreserveAspectFit + NumberAnimation on rotation { duration: 3000; from:0; to: 360; loops: Animation.Infinite} + } + + Text { + id: infotext + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: background.bottom + text: heartRate.message + color: "#8F8F8F" + } + } + } + + Component.onCompleted: { + heartRate.deviceSearch(); + spinner.visible=true; + } + + ListView { + id: theListView + width: parent.width + onModelChanged: spinner.visible=false + anchors.top: select.bottom + anchors.bottom: demoMode.top + model: heartRate.name + + delegate: Rectangle { + id: box + height:140 + width: parent.width + color: "#3870BA" + border.color: "#F0EBED" + border.width: 5 + radius: 15 + + MouseArea { + anchors.fill: parent + onPressed: { box.color= "#3265A7"; box.height=110} + onClicked: { + heartRate.connectToService(modelData.deviceAddress); + pageLoader.source="monitor.qml"; + } + } + + Text { + id: device + font.pixelSize: 30 + text: modelData.deviceName + anchors.top: parent.top + anchors.topMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + color: "#F0EBED" + } + + Text { + id: deviceAddress + font.pixelSize: 30 + text: modelData.deviceAddress + anchors.bottom: parent.bottom + anchors.bottomMargin: 5 + anchors.horizontalCenter: parent.horizontalCenter + color: "#F0EBED" + } + } + } + + Button { + id:demoMode + buttonWidth: parent.width + buttonHeight: 0.1*parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: scanAgain.top + text: "Run Demo" + onButtonClick: { + heartRate.startDemo(); + pageLoader.source="monitor.qml"; + } + } + + Button { + id:scanAgain + buttonWidth: parent.width + buttonHeight: 0.1*parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + text: "Menu" + onButtonClick: pageLoader.source="main.qml" + } +} diff --git a/examples/bluetooth/heartlistener/assets/main.qml b/examples/bluetooth/heartlistener/assets/main.qml new file mode 100644 index 00000000..e892eb47 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/main.qml @@ -0,0 +1,84 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Item { + width: 400 + height: 600 + id: begin + + Rectangle { + color: "#F0EBED" + anchors.fill: parent + Rectangle { + id: about + width: 0.75*parent.width + height: 0.1*parent.height + anchors.top: parent.top + anchors.topMargin: 20 + anchors.horizontalCenter: parent.horizontalCenter + color: "#F0EBED" + border.color: "#3870BA" + border.width: 2 + radius: 10 + Text { + id: aboutinfo + anchors.centerIn: parent + color: "#3870BA" + text: "Welcome to the Heart Listener Application" + } + } + + Button { + id:call + buttonWidth: 0.75*parent.width + buttonHeight: 0.15*parent.height + anchors.centerIn: parent + text: "Scan for Devices" + onButtonClick: pageLoader.source="home.qml" + } + } + + Loader { + id: pageLoader + anchors.fill: parent + } +} diff --git a/examples/bluetooth/heartlistener/assets/monitor.qml b/examples/bluetooth/heartlistener/assets/monitor.qml new file mode 100644 index 00000000..2fcbef3d --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/monitor.qml @@ -0,0 +1,154 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import QtQuick.Particles 2.0 + +Rectangle { + id: screenMonitor + color: "#F0EBED" + + Button { + id:menu + buttonWidth: parent.width + buttonHeight: 0.1 * parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + text: "Menu" + onButtonClick: { + heartRate.disconnectService(); + pageLoader.source="home.qml"; + } + } + + Text { + id: hrValue + font.pointSize: 24; font.bold: true + anchors.top:menu.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 25 + + color: "#3870BA" + text: heartRate.hr + onTextChanged: { + if (heartRate.hr > 0 && updatei != null && heartRate.numDevices() > 0) { + updatei.destroy() + } + } + } + + Rectangle { + id: updatei + width: parent.width + height: 80 + anchors.bottom: stop.top + + color: "#F0EBED" + border.color: "#3870BA" + border.width: 2 + + Text { + id: logi + text: heartRate.message + anchors.centerIn: updatei + color: "#3870BA" + } + } + + Image { + id: background + width: 300 + height: width + anchors.centerIn: parent + source: "blue_heart.png" + fillMode: Image.PreserveAspectFit + NumberAnimation on width { + running: heartRate.hr > 0; + duration: heartRate.hr/60*250; + from:300; to: 350; + loops: Animation.Infinite; + } + + ParticleSystem { + id: systwo + anchors.fill: parent + + ImageParticle { + system: systwo + id: cptwo + source: "star.png" + colorVariation: 0.4 + color: "#000000FF" + } + + Emitter { + //burst on click + id: burstytwo + system: systwo + enabled: true + x: 160 + y: 150 + emitRate: heartRate.hr*100 + maximumEmitted: 4000 + acceleration: AngleDirection {angleVariation: 360; magnitude: 360; } + size: 4 + endSize: 8 + sizeVariation: 4 + } + + + } + + } + + Button { + id:stop + buttonWidth: parent.width + buttonHeight: 0.1*parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + text: "Stop Monitoring" + onButtonClick: { + burstytwo.enabled = false; + heartRate.disconnectService(); + pageLoader.source = "results.qml"; + } + } +} diff --git a/examples/bluetooth/heartlistener/assets/results.qml b/examples/bluetooth/heartlistener/assets/results.qml new file mode 100644 index 00000000..bdc7147d --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/results.qml @@ -0,0 +1,291 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 +import "draw.js" as DrawGraph + +Rectangle { + id: results + color: "#F0EBED" + + Component.onCompleted: heartRate.obtainResults() + + function getTime() { + var t = heartRate.time; + var min = Math.floor(t/60); + var sec = t%60; + var r = min + " min " + sec + " sec "; + return r; + } + + function drawGraph() { + var b = plot.height/200; + var ctx = canvas1.getContext('2d'); + ctx.beginPath() + ctx.moveTo(10, plot.height- (b*60)) + var size = heartRate.measurementsSize(); + var difference = (plot.width-topbar.width)/size; + + for (var i = 0; i< size; i++) { + var value = heartRate.measurements(i); + if (i == 0) { + ctx.moveTo(10+2, (plot.height- (value*b) + 2)); + ctx.rect((10 + i*difference), (plot.height- (value*b)), 4, 4); + + } + else { + ctx.lineTo((10+2 + i*difference), (plot.height- (value*b) + 2)); + ctx.rect((10 + i*difference), (plot.height- (value*b)), 4, 4); + } + + } + ctx.fillStyle = "#3870BA" + ctx.fill() + ctx.strokeStyle = "#3870BA" + ctx.stroke() + ctx.closePath() + } + + Rectangle { + id: res + width: parent.width + anchors.top: parent.top + height: 80 + color: "#F0EBED" + border.color: "#3870BA" + border.width: 2 + radius: 10 + Text { + id: restText + color: "#3870BA" + font.pixelSize: 34 + anchors.centerIn: parent + text: "Results" + } + } + + Text { + id: topbar + text: "200" + anchors.left: parent.left + anchors.top: res.bottom + anchors.rightMargin: 4 + color: "#3870BA" + z: 50 + } + + Rectangle { + id: level + anchors.left: topbar.right + + anchors.top: res.bottom + height: ((results.height -(res.height + menuLast.height + start.height ))/2) + width: 3 + color: "#3870BA" + } + + Text { + id: middlebar + anchors.verticalCenter: level.verticalCenter + anchors.left: parent.left + text: "100" + color: "#3870BA" + z: 50 + } + + Rectangle{ + id: downlevel + anchors.bottom: cover.top + width: parent.width + anchors.left: level.right + height: 3 + color: "#3870BA" + z: 50 + } + + Rectangle { + id: plot + anchors.left: level.right + anchors.leftMargin: 15 + width: results.width + height: ((parent.height-(res.height+menuLast.height+start.height))/2) + + anchors.top: res.bottom + color: "#F0EBED" + Canvas { + id: canvas1 + anchors.fill: parent + z: 150 + onPaint: drawGraph() + } + } + + Rectangle { + id: cover + anchors.top: plot.bottom + anchors.bottom: menuLast.top + width: parent.width + height: ((parent.height-(res.height+menuLast.height+start.height))/2) + color: "#F0EBED" + radius: 10 + border.color: "#3870BA" + border.width: 2 + + Flickable { + id: scroll + anchors.fill: parent + anchors.margins: 5 + clip: true + contentWidth: parent.width + contentHeight: stresult.height + + Rectangle { + id: stresult + width: parent.width + height: (results.height - (res.height + menuLast.height + start.height - 100)) + color: "#F0EBED" + radius: 10 + + Text { + id: averageHR + font.pixelSize: 30; + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + + color: "#3870BA" + text: "Average Heart Rate" + } + + Text { + id: averageHRt + font.pixelSize: 40; font.bold: true + anchors.top: averageHR.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + color: "#3870BA" + text: heartRate.average + } + + Text { + id: time + font.pixelSize: 30; + anchors.top: averageHRt.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + color: "#3870BA" + text: "Seconds measured:" + } + + Text { + id: timet + font.pixelSize: 40; font.bold: true + anchors.top: time.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + color: "#3870BA" + text: getTime() + } + Text { + id: maxi + font.pixelSize: 30; + anchors.top: timet.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 20 + color: "#3870BA" + text: " Max || Min " + } + + Text { + id: mini + font.pixelSize: 40; font.bold: true + anchors.top:maxi.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + color: "#3870BA" + text: " " + heartRate.maxHR + " || " + heartRate.minHR + } + + Text { + id: calories + font.pixelSize: 30; + anchors.top: mini.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + color: "#3870BA" + text: " Calories " + } + + Text { + id: caloriestext + font.pixelSize: 40; font.bold: true + anchors.top:calories.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 10 + color: "#3870BA" + text: heartRate.calories.toFixed(3) + } + } + } + } + + Button { + id:menuLast + buttonWidth: parent.width + buttonHeight: 0.1*parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: start.top + text: "Menu" + onButtonClick: { pageLoader.source="main.qml"} + } + + Button { + id:start + buttonWidth: parent.width + buttonHeight: 0.1*parent.height + anchors.horizontalCenter: parent.horizontalCenter + anchors.bottom: parent.bottom + text: "Start Monitoring" + onButtonClick: { + heartRate.connectToService(heartRate.deviceAddress()); + pageLoader.source="monitor.qml"; + } + } +} diff --git a/examples/bluetooth/heartlistener/assets/star.png b/examples/bluetooth/heartlistener/assets/star.png Binary files differnew file mode 100644 index 00000000..defbde53 --- /dev/null +++ b/examples/bluetooth/heartlistener/assets/star.png diff --git a/examples/bluetooth/heartlistener/deviceinfo.cpp b/examples/bluetooth/heartlistener/deviceinfo.cpp new file mode 100644 index 00000000..5be9fcfe --- /dev/null +++ b/examples/bluetooth/heartlistener/deviceinfo.cpp @@ -0,0 +1,58 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "deviceinfo.h" + +DeviceInfo::DeviceInfo(const QBluetoothDeviceInfo &info): + QObject(), m_device(info) +{ +} + +QBluetoothDeviceInfo DeviceInfo::getDevice() const +{ + return m_device; +} + +void DeviceInfo::setDevice(const QBluetoothDeviceInfo &device) +{ + m_device = device; + emit deviceChanged(); +} diff --git a/examples/bluetooth/heartlistener/deviceinfo.h b/examples/bluetooth/heartlistener/deviceinfo.h new file mode 100644 index 00000000..a80f723b --- /dev/null +++ b/examples/bluetooth/heartlistener/deviceinfo.h @@ -0,0 +1,69 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DEVICEINFO_H +#define DEVICEINFO_H + +#include <QString> +#include <QObject> +#include <qbluetoothdeviceinfo.h> +#include <qbluetoothaddress.h> + +class DeviceInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString deviceName READ getName NOTIFY deviceChanged) + Q_PROPERTY(QString deviceAddress READ getAddress NOTIFY deviceChanged) +public: + DeviceInfo(const QBluetoothDeviceInfo &device); + void setDevice(const QBluetoothDeviceInfo &device); + QString getName() const { return m_device.name(); } + QString getAddress() const { return m_device.address().toString(); } + QBluetoothDeviceInfo getDevice() const; + +signals: + void deviceChanged(); + +private: + QBluetoothDeviceInfo m_device; +}; + +#endif // DEVICEINFO_H diff --git a/examples/bluetooth/heartlistener/doc/images/hearratemonitor.png b/examples/bluetooth/heartlistener/doc/images/hearratemonitor.png Binary files differnew file mode 100644 index 00000000..ed51ba86 --- /dev/null +++ b/examples/bluetooth/heartlistener/doc/images/hearratemonitor.png diff --git a/examples/bluetooth/heartlistener/doc/images/hearrateresults.png b/examples/bluetooth/heartlistener/doc/images/hearrateresults.png Binary files differnew file mode 100644 index 00000000..2c961517 --- /dev/null +++ b/examples/bluetooth/heartlistener/doc/images/hearrateresults.png diff --git a/examples/bluetooth/heartlistener/doc/images/hearrateresults1.png b/examples/bluetooth/heartlistener/doc/images/hearrateresults1.png Binary files differnew file mode 100644 index 00000000..7af63b57 --- /dev/null +++ b/examples/bluetooth/heartlistener/doc/images/hearrateresults1.png diff --git a/examples/bluetooth/heartlistener/doc/images/heartrateintro.png b/examples/bluetooth/heartlistener/doc/images/heartrateintro.png Binary files differnew file mode 100644 index 00000000..fb0a7b34 --- /dev/null +++ b/examples/bluetooth/heartlistener/doc/images/heartrateintro.png diff --git a/examples/bluetooth/heartlistener/doc/src/heartlistener.qdoc b/examples/bluetooth/heartlistener/doc/src/heartlistener.qdoc new file mode 100644 index 00000000..fa63714a --- /dev/null +++ b/examples/bluetooth/heartlistener/doc/src/heartlistener.qdoc @@ -0,0 +1,82 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example heartlistener + \title Bluetooth Low Energy Heart Listener example + + The Bluetooth Low Energy (BLE) Heart Listener Example shows how to develop a Bluetooth + Low Energy applications using the Qt Bluetooth API. The application covers + the scanning for BLE devices, connecting to the BLE Heart Rate service and + receiving updates from the BLE heart belt. The BLE heart belt is required + for this application to work. + + \image heartrateintro.png + + The best and the safest approach is to do a service discovery for BLE device first and + then pick the heart rate service. Before that it is necessary to connect signals from + the QLowEnergyController class, which is responsible for the communication with the + BLE device. + + \snippet heartlistener/heartrate.cpp Connect signals + + After service scan is done and heart rate service found, the heart rate measurement + characteristic needs to be found and enabled for the notifications (advertisements). + + \snippet heartlistener/heartrate.cpp Connecting to service + + The enableNotifications(m_heartRateCharacteristic) method is the one that + will enable advertisement from the BLE device. Every time, when new update gets from + the BLE device (in our case heart rate belt) QLowEnergyController will emit + valueChanged(QLowEnergyCharacteristicInfo) method, which will invoke + receiveMeasurement(const QLowEnergyCharacteristicInfo &) slot. + + \snippet heartlistener/heartrate.cpp Reading value + + Every BLE device has its own structure of data that is advertising. In the code above, + the approach for checking the value structure is presented. The heart rate service contains + that information in the first 8 bits of the advertised value. For instance, first bit tells + the format of the value (is it 8 or 16 bit value), fourth tells does the heart belt provide + energy spenditure information, etc. + + \snippet heartlistener/heartrate.cpp Error handling + + In case an error occurs, QLowEnergyController will emit error, which can be read with + errorString() method. An error for BLE service can occur in the process of connecting to + the service. For BLE characteristics, an error can occur when trying to subscribe for the + notifications. + + In the end, it is required to disconnect from the service. + + \snippet heartlistener/heartrate.cpp Disconnecting from service + + \image hearratemonitor.png + \image hearrateresults.png + \image hearrateresults1.png + +*/ + diff --git a/examples/bluetooth/heartlistener/heartlistener.pro b/examples/bluetooth/heartlistener/heartlistener.pro new file mode 100644 index 00000000..7856b64e --- /dev/null +++ b/examples/bluetooth/heartlistener/heartlistener.pro @@ -0,0 +1,20 @@ +TEMPLATE = app +TARGET = heartlistener + +QT += quick bluetooth + +# Input +HEADERS += deviceinfo.h \ + heartrate.h +SOURCES += deviceinfo.cpp \ + heartrate.cpp \ + main.cpp + +OTHER_FILES += assets/*.qml \ + assets/*.js + +RESOURCES += \ + resources.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/bluetooth/heartlistener +INSTALLS += target diff --git a/examples/bluetooth/heartlistener/heartrate.cpp b/examples/bluetooth/heartlistener/heartrate.cpp new file mode 100644 index 00000000..ac61a076 --- /dev/null +++ b/examples/bluetooth/heartlistener/heartrate.cpp @@ -0,0 +1,424 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "heartrate.h" + +HeartRate::HeartRate(): + m_currentDevice(QBluetoothDeviceInfo()), foundHeartRateService(false), + m_max(0), m_min(0), calories(0), m_control(0), timer(0), + m_service(0) +{ + m_deviceDiscoveryAgent = new QBluetoothDeviceDiscoveryAgent(this); + + connect(m_deviceDiscoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)), + this, SLOT(addDevice(const QBluetoothDeviceInfo&))); + connect(m_deviceDiscoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), + this, SLOT(deviceScanError(QBluetoothDeviceDiscoveryAgent::Error))); + connect(m_deviceDiscoveryAgent, SIGNAL(finished()), this, SLOT(scanFinished())); + + // initialize random seed for demo mode + qsrand(QTime::currentTime().msec()); +} + +HeartRate::~HeartRate() +{ + qDeleteAll(m_devices); + m_devices.clear(); +} + +void HeartRate::deviceSearch() +{ + qDeleteAll(m_devices); + m_devices.clear(); + m_deviceDiscoveryAgent->start(); + setMessage("Scanning for devices..."); +} + +void HeartRate::addDevice(const QBluetoothDeviceInfo &device) +{ + if (device.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { + qWarning() << "Discovered LE Device name: " << device.name() << " Address: " + << device.address().toString(); + + DeviceInfo *dev = new DeviceInfo(device); + m_devices.append(dev); + setMessage("Low Energy device found. Scanning for more..."); + } +} + +void HeartRate::scanFinished() +{ + if (m_devices.size() == 0) + setMessage("No Low Energy devices found"); + Q_EMIT nameChanged(); +} + +void HeartRate::deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error) +{ + if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError) + setMessage("The Bluetooth adaptor is powered off, power it on before doing discovery."); + else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError) + setMessage("Writing or reading from the device resulted in an error."); + else + setMessage("An unknown error has occurred."); +} + + +void HeartRate::setMessage(QString message) +{ + m_info = message; + Q_EMIT messageChanged(); +} + +QString HeartRate::message() const +{ + return m_info; +} + +QVariant HeartRate::name() +{ + return QVariant::fromValue(m_devices); +} + +void HeartRate::connectToService(const QString &address) +{ + m_measurements.clear(); + + bool deviceFound = false; + for (int i = 0; i < m_devices.size(); i++) { + if (((DeviceInfo*)m_devices.at(i))->getAddress() == address ) { + m_currentDevice.setDevice(((DeviceInfo*)m_devices.at(i))->getDevice()); + setMessage("Connecting to device..."); + deviceFound = true; + break; + } + } + // we are running demo mode + if (!deviceFound) { + startDemo(); + return; + } + + if (m_control) { + m_control->disconnectFromDevice(); + delete m_control; + m_control = 0; + + } + //! [Connect signals] + m_control = new QLowEnergyController(m_currentDevice.getDevice().address(), + this); + connect(m_control, SIGNAL(serviceDiscovered(QBluetoothUuid)), + this, SLOT(serviceDiscovered(QBluetoothUuid))); + connect(m_control, SIGNAL(discoveryFinished()), + this, SLOT(serviceScanDone())); + connect(m_control, SIGNAL(error(QLowEnergyController::Error)), + this, SLOT(controllerError(QLowEnergyController::Error))); + connect(m_control, SIGNAL(connected()), + this, SLOT(serviceConnected())); + + m_control->connectToDevice(); + //! [Connect signals] +} + +// TODO fix the qdoc tag below +//! [Connecting to service] + +void HeartRate::serviceConnected() +{ + m_control->discoverServices(); +} + +void HeartRate::deviceDisconnected() +{ + setMessage("Heart Rate service disconnected"); + qWarning() << "Remote device disconnected"; +} + + +void HeartRate::serviceDiscovered(const QBluetoothUuid &gatt) +{ + if (gatt == QBluetoothUuid(QBluetoothUuid::HeartRate)) { + setMessage("Heart Rate service discovered. Waiting for service scan to be done..."); + foundHeartRateService = true; + } +} + +void HeartRate::serviceScanDone() +{ + delete m_service; + m_service = 0; + + if (foundHeartRateService) { + setMessage("Connecting to service..."); + m_service = m_control->createServiceObject( + QBluetoothUuid(QBluetoothUuid::HeartRate), this); + } + + if (!m_service) { + setMessage("Heart Rate Service not found."); + return; + } + + connect(m_service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), + this, SLOT(serviceStateChanged(QLowEnergyService::ServiceState))); + connect(m_service, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), + this, SLOT(updateHeartRateValue(QLowEnergyCharacteristic,QByteArray))); + connect(m_service, SIGNAL(descriptorChanged(QLowEnergyDescriptor,QByteArray)), + this, SLOT(confirmedDescriptorWrite(QLowEnergyDescriptor,QByteArray))); + + m_service->discoverDetails(); +} + +//! [Connecting to service] + +void HeartRate::disconnectService() +{ + foundHeartRateService = false; + m_stop = QDateTime::currentDateTime(); + + if (m_devices.isEmpty()) { + if (timer) + timer->stop(); + return; + } + + //disable notifications + if (m_notificationDesc.isValid() && m_service) { + m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0000")); + } else { + m_control->disconnectFromDevice(); + delete m_service; + m_service = 0; + } +} + +//! [Error handling] +void HeartRate::controllerError(QLowEnergyController::Error error) +{ + setMessage("Cannot connect to remote device."); + qWarning() << "Controller Error:" << error; +} +//! [Error handling] + + +void HeartRate::serviceStateChanged(QLowEnergyService::ServiceState s) +{ + switch (s) { + case QLowEnergyService::ServiceDiscovered: + { + const QLowEnergyCharacteristic hrChar = m_service->characteristic( + QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement)); + if (!hrChar.isValid()) { + setMessage("HR Data not found."); + break; + } + + const QLowEnergyDescriptor m_notificationDesc = hrChar.descriptor( + QBluetoothUuid::ClientCharacteristicConfiguration); + if (m_notificationDesc.isValid()) { + m_service->writeDescriptor(m_notificationDesc, QByteArray::fromHex("0100")); + setMessage("Measuring"); + m_start = QDateTime::currentDateTime(); + } + + break; + } + default: + //nothing for now + break; + } +} + +void HeartRate::serviceError(QLowEnergyService::ServiceError e) +{ + switch (e) { + case QLowEnergyService::DescriptorWriteError: + setMessage("Cannot obtain HR notifications"); + break; + default: + qWarning() << "HR service error:" << e; + } +} + +void HeartRate::updateHeartRateValue(const QLowEnergyCharacteristic &c, + const QByteArray &value) +{ + // ignore any other characteristic change -> shouldn't really happen though + if (c.uuid() != QBluetoothUuid(QBluetoothUuid::HeartRateMeasurement)) + return; + + //! [Reading value] + const char *data = value.constData(); + quint8 flags = data[0]; + + //Heart Rate + if (flags & 0x1) { // HR 16 bit? otherwise 8 bit + quint16 *heartRate = (quint16 *) &data[1]; + //qDebug() << "16 bit HR value:" << *heartRate; + m_measurements.append(*heartRate); + } else { + quint8 *heartRate = (quint8 *) &data[1]; + m_measurements.append(*heartRate); + //qDebug() << "8 bit HR value:" << *heartRate; + } + + //Energy Expended + if (flags & 0x8) { + int index = (flags & 0x1) ? 5 : 3; + quint16 *energy = (quint16 *) &data[index]; + qDebug() << "Used Energy:" << *energy; + } + //! [Reading value] + + Q_EMIT hrChanged(); +} + +void HeartRate::confirmedDescriptorWrite(const QLowEnergyDescriptor &d, + const QByteArray &value) +{ + if (d.isValid() && d == m_notificationDesc && value == QByteArray("0000")) { + //disabled notifications -> assume disconnect intent + m_control->disconnectFromDevice(); + delete m_service; + m_service = 0; + } +} + +int HeartRate::hR() const +{ + if (m_measurements.isEmpty()) + return 0; + return m_measurements.last(); +} + +void HeartRate::obtainResults() +{ + Q_EMIT timeChanged(); + Q_EMIT averageChanged(); + Q_EMIT caloriesChanged(); +} + +int HeartRate::time() +{ + return m_start.secsTo(m_stop); +} + +int HeartRate::maxHR() const +{ + return m_max; +} + +int HeartRate::minHR() const +{ + return m_min; +} + +float HeartRate::average() +{ + if (m_measurements.size() == 0) { + return 0; + } else { + m_max = 0; + m_min = 1000; + int sum = 0; + for (int i = 0; i < m_measurements.size(); i++) { + sum += (int) m_measurements.value(i); + if (((int)m_measurements.value(i)) > m_max) + m_max = (int)m_measurements.value(i); + if (((int)m_measurements.value(i)) < m_min) + m_min = (int)m_measurements.value(i); + } + return sum/m_measurements.size(); + } +} + +int HeartRate::measurements(int index) const +{ + if (index > m_measurements.size()) + return 0; + else + return (int)m_measurements.value(index); +} + +int HeartRate::measurementsSize() const +{ + return m_measurements.size(); +} + +QString HeartRate::deviceAddress() const +{ + return m_currentDevice.getAddress(); +} + +float HeartRate::caloriesCalculation() +{ + calories = ((-55.0969 + (0.6309 * average()) + (0.1988 * 94) + (0.2017 * 24)) / 4.184) * 60 * time()/3600 ; + return calories; +} + +int HeartRate::numDevices() const +{ + return m_devices.size(); +} + +void HeartRate::startDemo() +{ + m_start = QDateTime::currentDateTime(); + if (!timer) { + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(receiveDemo())); + } + timer->start(1000); + setMessage("This is Demo mode"); +} + +void HeartRate::receiveDemo() +{ + m_measurements.append(randomPulse()); + Q_EMIT hrChanged(); +} + +int HeartRate::randomPulse() const +{ + // random number between 50 and 70 + return qrand() % (70 - 50) + 50; +} diff --git a/examples/bluetooth/heartlistener/heartrate.h b/examples/bluetooth/heartlistener/heartrate.h new file mode 100644 index 00000000..65e92af8 --- /dev/null +++ b/examples/bluetooth/heartlistener/heartrate.h @@ -0,0 +1,149 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef HEARTRATE_H +#define HEARTRATE_H + +#include "deviceinfo.h" + +#include <QString> +#include <QDebug> +#include <QDateTime> +#include <QVector> +#include <QTimer> +#include <QBluetoothDeviceDiscoveryAgent> +#include <QBluetoothDeviceInfo> +#include <QLowEnergyController> +#include <QLowEnergyService> + + +QT_USE_NAMESPACE +class HeartRate: public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariant name READ name NOTIFY nameChanged) + Q_PROPERTY(QString message READ message NOTIFY messageChanged) + Q_PROPERTY(int hr READ hR NOTIFY hrChanged) + Q_PROPERTY(int maxHR READ maxHR NOTIFY averageChanged) + Q_PROPERTY(int minHR READ minHR NOTIFY averageChanged) + Q_PROPERTY(float average READ average NOTIFY averageChanged) + Q_PROPERTY(int time READ time NOTIFY timeChanged) + Q_PROPERTY(float calories READ caloriesCalculation NOTIFY caloriesChanged) + +public: + HeartRate(); + ~HeartRate(); + void setMessage(QString message); + QString message() const; + QVariant name(); + int hR() const; + int time(); + float average(); + int maxHR() const; + int minHR() const; + float caloriesCalculation(); + +public slots: + void deviceSearch(); + void connectToService(const QString &address); + void disconnectService(); + void startDemo(); + + void obtainResults(); + int measurements(int index) const; + int measurementsSize() const; + QString deviceAddress() const; + int numDevices() const; + +private slots: + //QBluetothDeviceDiscoveryAgent + void addDevice(const QBluetoothDeviceInfo&); + void scanFinished(); + void deviceScanError(QBluetoothDeviceDiscoveryAgent::Error); + + //QLowEnergyController + void serviceDiscovered(const QBluetoothUuid &); + void serviceScanDone(); + void controllerError(QLowEnergyController::Error); + void serviceConnected(); + void deviceDisconnected(); + + + //QLowEnergyService + void serviceStateChanged(QLowEnergyService::ServiceState s); + void updateHeartRateValue(const QLowEnergyCharacteristic &c, + const QByteArray &value); + void confirmedDescriptorWrite(const QLowEnergyDescriptor &d, + const QByteArray &value); + void serviceError(QLowEnergyService::ServiceError e); + + //DemoMode + void receiveDemo(); + +Q_SIGNALS: + void messageChanged(); + void nameChanged(); + void hrChanged(); + void averageChanged(); + void timeChanged(); + void caloriesChanged(); + +private: + int randomPulse() const; + + DeviceInfo m_currentDevice; + QBluetoothDeviceDiscoveryAgent *m_deviceDiscoveryAgent; + QLowEnergyDescriptor m_notificationDesc; + QList<QObject*> m_devices; + QString m_info; + bool foundHeartRateService; + QVector<quint16> m_measurements; + QDateTime m_start; + QDateTime m_stop; + int m_max; + int m_min; + float calories; + QLowEnergyController *m_control; + QTimer *timer; // for demo application + QLowEnergyService *m_service; +}; + +#endif // HEARTRATE_H diff --git a/examples/bluetooth/heartlistener/main.cpp b/examples/bluetooth/heartlistener/main.cpp new file mode 100644 index 00000000..e50b87f1 --- /dev/null +++ b/examples/bluetooth/heartlistener/main.cpp @@ -0,0 +1,61 @@ +/*************************************************************************** +** +** Copyright (C) 2014 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QLoggingCategory> +#include <QQmlContext> +#include <QGuiApplication> +#include <QQuickView> +#include "heartrate.h" + +int main(int argc, char *argv[]) +{ + //QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); + QGuiApplication app(argc, argv); + + HeartRate heartRate; + QQuickView *view = new QQuickView; + view->rootContext()->setContextProperty("heartRate", &heartRate); + view->setSource(QUrl("qrc:/assets/main.qml")); + view->setResizeMode(QQuickView::SizeRootObjectToView); + view->show(); + return app.exec(); + +} diff --git a/examples/bluetooth/heartlistener/resources.qrc b/examples/bluetooth/heartlistener/resources.qrc new file mode 100644 index 00000000..ac6f9c83 --- /dev/null +++ b/examples/bluetooth/heartlistener/resources.qrc @@ -0,0 +1,16 @@ +<RCC> + <qresource prefix="/"> + <file>assets/blue_heart.png</file> + <file>assets/busy_dark.png</file> + <file>assets/Button.qml</file> + <file>assets/dialog.qml</file> + <file>assets/draw.js</file> + <file>assets/home.qml</file> + <file>assets/main.qml</file> + <file>assets/monitor.qml</file> + <file>assets/Point.qml</file> + <file>assets/results.qml</file> + <file>assets/star.png</file> + <file>assets/blue_heart_small.png</file> + </qresource> +</RCC> diff --git a/examples/bluetooth/lowenergyscanner/assets/Characteristics.qml b/examples/bluetooth/lowenergyscanner/assets/Characteristics.qml new file mode 100644 index 00000000..4a7db471 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/Characteristics.qml @@ -0,0 +1,147 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + width: 300 + height: 600 + + Header { + id: header + anchors.top: parent.top + headerText: "Characteristics list" + } + + Dialog { + id: info + anchors.centerIn: parent + visible: true + dialogText: "Scanning for characteristics..."; + } + + Connections { + target: device + onCharacteristicsUpdated: { + menu.menuText = "Back" + if (characteristicview.count === 0) + info.dialogText = "No characteristic found" + else + info.visible = false; + } + + onDisconnected: { + pageLoader.source = "main.qml" + } + } + + ListView { + id: characteristicview + width: parent.width + clip: true + + anchors.top: header.bottom + anchors.bottom: menu.top + model: device.characteristicList + + delegate: Rectangle { + id: characteristicbox + height:300 + width: parent.width + color: "lightsteelblue" + border.width: 2 + border.color: "black" + radius: 5 + + Label { + id: characteristicName + textContent: modelData.characteristicName + anchors.top: parent.top + anchors.topMargin: 5 + } + + Label { + id: characteristicUuid + font.pointSize: characteristicName.font.pointSize*0.7 + textContent: modelData.characteristicUuid + anchors.top: characteristicName.bottom + anchors.topMargin: 5 + } + + Label { + id: characteristicValue + font.pointSize: characteristicName.font.pointSize*0.7 + textContent: ("Value: " + modelData.characteristicValue) + anchors.bottom: characteristicHandle.top + horizontalAlignment: Text.AlignHCenter + anchors.topMargin: 5 + } + + Label { + id: characteristicHandle + font.pointSize: characteristicName.font.pointSize*0.7 + textContent: ("Handlers: " + modelData.characteristicHandle) + anchors.bottom: characteristicPermission.top + anchors.topMargin: 5 + } + + Label { + id: characteristicPermission + font.pointSize: characteristicName.font.pointSize*0.7 + textContent: modelData.characteristicPermission + anchors.bottom: parent.bottom + anchors.topMargin: 5 + anchors.bottomMargin: 5 + } + } + } + + Menu { + id: menu + anchors.bottom: parent.bottom + menuWidth: parent.width + menuText: "Scanning" + menuHeight: (parent.height/6) + onButtonClick: { + pageLoader.source = "Services.qml" + } + } +} diff --git a/examples/bluetooth/lowenergyscanner/assets/Dialog.qml b/examples/bluetooth/lowenergyscanner/assets/Dialog.qml new file mode 100644 index 00000000..be5388b5 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/Dialog.qml @@ -0,0 +1,61 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + width: parent.width/2 + height: 62 + z: 50 + property string dialogText: "" + border.width: 1 + border.color: "#363636" + radius: 10 + + Text { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + elide: Text.ElideMiddle + text: dialogText + color: "#363636" + wrapMode: Text.Wrap + } +} diff --git a/examples/bluetooth/lowenergyscanner/assets/Header.qml b/examples/bluetooth/lowenergyscanner/assets/Header.qml new file mode 100644 index 00000000..654fda34 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/Header.qml @@ -0,0 +1,61 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + width: parent.width + height: 70 + border.width: 1 + border.color: "#363636" + radius: 5 + property string headerText: "" + + Text { + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + text: headerText + font.bold: true + font.pixelSize: 30 + elide: Text.ElideMiddle + color: "#363636" + } +} diff --git a/examples/bluetooth/lowenergyscanner/assets/Label.qml b/examples/bluetooth/lowenergyscanner/assets/Label.qml new file mode 100644 index 00000000..1dced831 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/Label.qml @@ -0,0 +1,53 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Text { + property string textContent: "" + font.pixelSize: 30 + anchors.horizontalCenter: parent.horizontalCenter + color: "#363636" + horizontalAlignment: Text.AlignHCenter + elide: Text.ElideMiddle + width: parent.width + wrapMode: Text.Wrap + text: textContent +} diff --git a/examples/bluetooth/lowenergyscanner/assets/Menu.qml b/examples/bluetooth/lowenergyscanner/assets/Menu.qml new file mode 100644 index 00000000..d1eaebb2 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/Menu.qml @@ -0,0 +1,90 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + + property real menuWidth: 100 + property real menuHeight: 50 + property string menuText: "Search" + signal buttonClick() + + height: menuHeight + width: menuWidth + + Rectangle { + id: search + width: parent.width + height: parent.height + anchors.centerIn: parent + color: "#363636" + border.width: 1 + border.color: "#E3E3E3" + radius: 5 + Text { + id: searchText + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + anchors.fill: parent + text: menuText + elide: Text.ElideMiddle + color: "#E3E3E3" + } + + MouseArea { + anchors.fill: parent + onPressed: { + search.width = search.width - 7 + search.height = search.height - 5 + } + + onReleased: { + search.width = search.width + 7 + search.height = search.height + 5 + } + + onClicked: { + buttonClick() + } + } + } +} diff --git a/examples/bluetooth/lowenergyscanner/assets/Services.qml b/examples/bluetooth/lowenergyscanner/assets/Services.qml new file mode 100644 index 00000000..e4e802d2 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/Services.qml @@ -0,0 +1,136 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + width: 300 + height: 600 + + Header { + id: header + anchors.top: parent.top + headerText: "Services list" + } + + Dialog { + id: info + anchors.centerIn: parent + visible: true + dialogText: "Scanning for services..."; + } + + Connections { + target: device + onServicesUpdated: { + if (servicesview.count === 0) + info.dialogText = "No services found" + else + info.visible = false; + } + + onDisconnected: { + pageLoader.source = "main.qml" + } + } + + ListView { + id: servicesview + width: parent.width + anchors.top: header.bottom + anchors.bottom: menu.top + model: device.servicesList + clip: true + + delegate: Rectangle { + id: servicebox + height:100 + color: "lightsteelblue" + border.width: 2 + border.color: "black" + radius: 5 + width: parent.width + Component.onCompleted: { + info.visible = false + } + + MouseArea { + anchors.fill: parent + onClicked: { + device.connectToService(modelData.serviceUuid); + pageLoader.source = "Characteristics.qml"; + } + } + + Label { + id: serviceName + textContent: modelData.serviceName + anchors.top: parent.top + anchors.topMargin: 5 + } + + Label { + textContent: modelData.serviceType + font.pointSize: serviceName.font.pointSize * 0.5 + anchors.top: serviceName.bottom + } + + Label { + id: serviceUuid + font.pointSize: serviceName.font.pointSize * 0.5 + textContent: modelData.serviceUuid + anchors.bottom: servicebox.bottom + anchors.bottomMargin: 5 + } + } + } + + Menu { + id: menu + anchors.bottom: parent.bottom + menuWidth: parent.width + menuText: "Back" + menuHeight: (parent.height/6) + onButtonClick: { + device.disconnectFromDevice() + } + } +} diff --git a/examples/bluetooth/lowenergyscanner/assets/main.qml b/examples/bluetooth/lowenergyscanner/assets/main.qml new file mode 100644 index 00000000..9f0395d5 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/assets/main.qml @@ -0,0 +1,130 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +import QtQuick 2.0 + +Rectangle { + id: back + width: 300 + height: 600 + property bool deviceState: device.state + onDeviceStateChanged: { + if (!device.state) + info.visible = false; + } + + Header { + id: header + anchors.top: parent.top + headerText: "Start Discovery" + } + + Dialog { + id: info + anchors.centerIn: parent + visible: false + } + + ListView { + id: theListView + width: parent.width + clip: true + + anchors.top: header.bottom + anchors.bottom: menu.top + model: device.devicesList + + delegate: Rectangle { + id: box + height:100 + width: parent.width + color: "lightsteelblue" + border.width: 2 + border.color: "black" + radius: 5 + + Component.onCompleted: { + info.visible = false; + header.headerText = "Select a device"; + } + + MouseArea { + anchors.fill: parent + onClicked: { + device.scanServices(modelData.deviceAddress); + pageLoader.source = "Services.qml" + } + } + + Label { + id: deviceName + textContent: modelData.deviceName + anchors.top: parent.top + anchors.topMargin: 5 + } + + Label { + id: deviceAddress + textContent: modelData.deviceAddress + font.pointSize: deviceName.font.pointSize*0.7 + anchors.bottom: box.bottom + anchors.bottomMargin: 5 + } + } + } + + Menu { + id: menu + anchors.bottom: parent.bottom + menuWidth: parent.width + menuHeight: (parent.height/6) + menuText: device.update + onButtonClick: { + device.startDeviceDiscovery(); + info.dialogText = "Searching..."; + info.visible = true;} + } + + Loader { + id: pageLoader + anchors.fill: parent + } +} diff --git a/examples/bluetooth/lowenergyscanner/characteristicinfo.cpp b/examples/bluetooth/lowenergyscanner/characteristicinfo.cpp new file mode 100644 index 00000000..d7ae8438 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/characteristicinfo.cpp @@ -0,0 +1,145 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "characteristicinfo.h" +#include "qbluetoothuuid.h" +#include <QByteArray> + +CharacteristicInfo::CharacteristicInfo() +{ +} + +CharacteristicInfo::CharacteristicInfo(const QLowEnergyCharacteristic &characteristic): + m_characteristic(characteristic) +{ +} + +void CharacteristicInfo::setCharacteristic(const QLowEnergyCharacteristic &characteristic) +{ + m_characteristic = characteristic; + emit characteristicChanged(); +} + +QString CharacteristicInfo::getName() const +{ + QString name = m_characteristic.name(); + if (!name.isEmpty()) + return name; + + // find descriptor with CharacteristicUserDescription + foreach (const QLowEnergyDescriptor &descriptor, m_characteristic.descriptors()) { + if (descriptor.type() == QBluetoothUuid::CharacteristicUserDescription) { + name = descriptor.value(); + break; + } + } + + if (name.isEmpty()) + name = "Unknown"; + + return name; +} + +QString CharacteristicInfo::getUuid() const +{ + const QBluetoothUuid uuid = m_characteristic.uuid(); + bool success = false; + quint16 result16 = uuid.toUInt16(&success); + if (success) + return QStringLiteral("0x") + QString::number(result16, 16); + + quint32 result32 = uuid.toUInt32(&success); + if (success) + return QStringLiteral("0x") + QString::number(result32, 16); + + return uuid.toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')); +} + +QString CharacteristicInfo::getValue() const +{ + // Show raw string first and hex value below + QByteArray a = m_characteristic.value(); + QString result; + if (a.isEmpty()) { + result = QStringLiteral("<none>"); + return result; + } + + result = a; + result += QLatin1Char('\n'); + result += a.toHex(); + + return result; +} + +QString CharacteristicInfo::getHandle() const +{ + return QStringLiteral("0x") + QString::number(m_characteristic.handle(), 16); +} + +QString CharacteristicInfo::getPermission() const +{ + QString properties = "( "; + int permission = m_characteristic.properties(); + if (permission & QLowEnergyCharacteristic::Read) + properties += QStringLiteral(" Read"); + if (permission & QLowEnergyCharacteristic::Write) + properties += QStringLiteral(" Write"); + if (permission & QLowEnergyCharacteristic::Notify) + properties += QStringLiteral(" Notify"); + if (permission & QLowEnergyCharacteristic::Indicate) + properties += QStringLiteral(" Indicate"); + if (permission & QLowEnergyCharacteristic::ExtendedProperty) + properties += QStringLiteral(" ExtendedProperty"); + if (permission & QLowEnergyCharacteristic::Broadcasting) + properties += QStringLiteral(" Broadcast"); + if (permission & QLowEnergyCharacteristic::WriteNoResponse) + properties += QStringLiteral(" WriteNoResp"); + if (permission & QLowEnergyCharacteristic::WriteSigned) + properties += QStringLiteral(" WriteSigned"); + properties += " )"; + return properties; +} + +QLowEnergyCharacteristic CharacteristicInfo::getCharacteristic() const +{ + return m_characteristic; +} diff --git a/examples/bluetooth/lowenergyscanner/characteristicinfo.h b/examples/bluetooth/lowenergyscanner/characteristicinfo.h new file mode 100644 index 00000000..d06c962e --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/characteristicinfo.h @@ -0,0 +1,75 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef CHARACTERISTICINFO_H +#define CHARACTERISTICINFO_H +#include <QObject> +#include <QString> +#include <QtBluetooth/QLowEnergyCharacteristic> + +class CharacteristicInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString characteristicName READ getName NOTIFY characteristicChanged) + Q_PROPERTY(QString characteristicUuid READ getUuid NOTIFY characteristicChanged) + Q_PROPERTY(QString characteristicValue READ getValue NOTIFY characteristicChanged) + Q_PROPERTY(QString characteristicHandle READ getHandle NOTIFY characteristicChanged) + Q_PROPERTY(QString characteristicPermission READ getPermission NOTIFY characteristicChanged) + +public: + CharacteristicInfo(); + CharacteristicInfo(const QLowEnergyCharacteristic &characteristic); + void setCharacteristic(const QLowEnergyCharacteristic &characteristic); + QString getName() const; + QString getUuid() const; + QString getValue() const; + QString getHandle() const; + QString getPermission() const; + QLowEnergyCharacteristic getCharacteristic() const; + +Q_SIGNALS: + void characteristicChanged(); + +private: + QLowEnergyCharacteristic m_characteristic; +}; + +#endif // CHARACTERISTICINFO_H diff --git a/examples/bluetooth/lowenergyscanner/device.cpp b/examples/bluetooth/lowenergyscanner/device.cpp new file mode 100644 index 00000000..3d82800f --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/device.cpp @@ -0,0 +1,290 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "device.h" +#include <qbluetoothaddress.h> +#include <qbluetoothdevicediscoveryagent.h> +#include <qbluetoothlocaldevice.h> +#include <qbluetoothdeviceinfo.h> +#include <qbluetoothservicediscoveryagent.h> +#include <QDebug> +#include <QList> +#include <QTimer> + +Device::Device(): + connected(false), controller(0), m_deviceScanState(false) +{ + discoveryAgent = new QBluetoothDeviceDiscoveryAgent(); + connect(discoveryAgent, SIGNAL(deviceDiscovered(const QBluetoothDeviceInfo&)), + this, SLOT(addDevice(const QBluetoothDeviceInfo&))); + connect(discoveryAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error)), + this, SLOT(deviceScanError(QBluetoothDeviceDiscoveryAgent::Error))); + connect(discoveryAgent, SIGNAL(finished()), this, SLOT(deviceScanFinished())); + + setUpdate("Search"); +} + +Device::~Device() +{ + delete discoveryAgent; + delete controller; + qDeleteAll(devices); + qDeleteAll(m_services); + qDeleteAll(m_characteristics); + devices.clear(); + m_services.clear(); + m_characteristics.clear(); +} + +void Device::startDeviceDiscovery() +{ + qDeleteAll(devices); + devices.clear(); + emit devicesUpdated(); + + setUpdate("Scanning for devices ..."); + discoveryAgent->start(); + m_deviceScanState = true; + Q_EMIT stateChanged(); +} + +void Device::addDevice(const QBluetoothDeviceInfo &info) +{ + if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) { + DeviceInfo *d = new DeviceInfo(info); + devices.append(d); + setUpdate("Last device added: " + d->getName()); + } +} + +void Device::deviceScanFinished() +{ + emit devicesUpdated(); + m_deviceScanState = false; + emit stateChanged(); + if (devices.isEmpty()) + setUpdate("No Low Energy devices found..."); + else + setUpdate("Done! Scan Again!"); +} + +QVariant Device::getDevices() +{ + return QVariant::fromValue(devices); +} + +QVariant Device::getServices() +{ + return QVariant::fromValue(m_services); +} + +QVariant Device::getCharacteristics() +{ + return QVariant::fromValue(m_characteristics); +} + +QString Device::getUpdate() +{ + return m_message; +} + +void Device::scanServices(const QString &address) +{ + // We need the current device for service discovery. + for (int i = 0; i < devices.size(); i++) { + if (((DeviceInfo*)devices.at(i))->getAddress() == address ) + currentDevice.setDevice(((DeviceInfo*)devices.at(i))->getDevice()); + } + + if (!currentDevice.getDevice().isValid()) { + qWarning() << "Not a valid device"; + return; + } + + qDeleteAll(m_characteristics); + m_characteristics.clear(); + emit characteristicsUpdated(); + qDeleteAll(m_services); + m_services.clear(); + emit servicesUpdated(); + + setUpdate("Connecting to device..."); + + if (controller && controller->remoteAddress() != currentDevice.getDevice().address()) { + controller->disconnectFromDevice(); + delete controller; + controller = 0; + } + + if (!controller) { + // Connecting signals and slots for connecting to LE services. + controller = new QLowEnergyController(currentDevice.getDevice().address()); + connect(controller, SIGNAL(connected()), + this, SLOT(deviceConnected())); + connect(controller, SIGNAL(error(QLowEnergyController::Error)), + this, SLOT(errorReceived(QLowEnergyController::Error))); + connect(controller, SIGNAL(disconnected()), + this, SLOT(deviceDisconnected())); + connect(controller, SIGNAL(serviceDiscovered(QBluetoothUuid)), + this, SLOT(addLowEnergyService(QBluetoothUuid))); + connect(controller, SIGNAL(discoveryFinished()), + this, SLOT(serviceScanDone())); + } + + controller->connectToDevice(); +} + +void Device::addLowEnergyService(const QBluetoothUuid &serviceUuid) +{ + QLowEnergyService *service = controller->createServiceObject(serviceUuid); + if (!service) { + qWarning() << "Cannot create service for uuid"; + return; + } + ServiceInfo *serv = new ServiceInfo(service); + m_services.append(serv); + + emit servicesUpdated(); +} + +void Device::serviceScanDone() +{ + setUpdate("Service scan done!"); +} + +void Device::connectToService(const QString &uuid) +{ + QLowEnergyService *service = 0; + for (int i = 0; i < m_services.size(); i++) { + ServiceInfo *serviceInfo = (ServiceInfo*)m_services.at(i); + if (serviceInfo->getUuid() == uuid) { + service = serviceInfo->service(); + break; + } + } + + if (!service) + return; + + qDeleteAll(m_characteristics); + m_characteristics.clear(); + emit characteristicsUpdated(); + + if (service->state() == QLowEnergyService::DiscoveryRequired) { + connect(service, SIGNAL(stateChanged(QLowEnergyService::ServiceState)), + this, SLOT(serviceDetailsDiscovered(QLowEnergyService::ServiceState))); + service->discoverDetails(); + return; + } + + //discovery already done + const QList<QLowEnergyCharacteristic> chars = service->characteristics(); + foreach (const QLowEnergyCharacteristic &ch, chars) { + CharacteristicInfo *cInfo = new CharacteristicInfo(ch); + m_characteristics.append(cInfo); + } + + QTimer::singleShot(0, this, SIGNAL(characteristicsUpdated())); +} + +void Device::deviceConnected() +{ + setUpdate("Discovering services!"); + connected = true; + controller->discoverServices(); +} + +void Device::errorReceived(QLowEnergyController::Error /*error*/) +{ + qWarning() << "Error: " << controller->errorString(); + setUpdate(controller->errorString()); +} + +void Device::setUpdate(QString message) +{ + m_message = message; + emit updateChanged(); +} + +void Device::disconnectFromDevice() +{ + if (connected) + controller->disconnectFromDevice(); +} + +void Device::deviceDisconnected() +{ + qWarning() << "Disconnect from device"; + emit disconnected(); +} + +void Device::serviceDetailsDiscovered(QLowEnergyService::ServiceState newState) +{ + if (newState != QLowEnergyService::ServiceDiscovered) + return; + + QLowEnergyService *service = qobject_cast<QLowEnergyService *>(sender()); + if (!service) + return; + + const QList<QLowEnergyCharacteristic> chars = service->characteristics(); + foreach (const QLowEnergyCharacteristic &ch, chars) { + CharacteristicInfo *cInfo = new CharacteristicInfo(ch); + m_characteristics.append(cInfo); + } + + emit characteristicsUpdated(); +} + +void Device::deviceScanError(QBluetoothDeviceDiscoveryAgent::Error error) +{ + if (error == QBluetoothDeviceDiscoveryAgent::PoweredOffError) + setUpdate("The Bluetooth adaptor is powered off, power it on before doing discovery."); + else if (error == QBluetoothDeviceDiscoveryAgent::InputOutputError) + setUpdate("Writing or reading from the device resulted in an error."); + else + setUpdate("An unknown error has occurred."); +} + +bool Device::state() +{ + return m_deviceScanState; +} diff --git a/examples/bluetooth/lowenergyscanner/device.h b/examples/bluetooth/lowenergyscanner/device.h new file mode 100644 index 00000000..f15c4c95 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/device.h @@ -0,0 +1,122 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the demonstration applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DEVICE_H +#define DEVICE_H + +#include <qbluetoothglobal.h> +#include <qbluetoothlocaldevice.h> +#include <QObject> +#include <QVariant> +#include <QList> +#include <QBluetoothServiceDiscoveryAgent> +#include <QBluetoothDeviceDiscoveryAgent> +#include <QLowEnergyController> +#include <QBluetoothServiceInfo> +#include "deviceinfo.h" +#include "serviceinfo.h" +#include "characteristicinfo.h" + +QT_FORWARD_DECLARE_CLASS (QBluetoothDeviceInfo) +QT_FORWARD_DECLARE_CLASS (QLowEnergyServiceInfo) +QT_FORWARD_DECLARE_CLASS (QBluetoothServiceInfo) + +class Device: public QObject +{ + Q_OBJECT + Q_PROPERTY(QVariant devicesList READ getDevices NOTIFY devicesUpdated) + Q_PROPERTY(QVariant servicesList READ getServices NOTIFY servicesUpdated) + Q_PROPERTY(QVariant characteristicList READ getCharacteristics NOTIFY characteristicsUpdated) + Q_PROPERTY(QString update READ getUpdate NOTIFY updateChanged) + Q_PROPERTY(bool state READ state NOTIFY stateChanged) +public: + Device(); + ~Device(); + QVariant getDevices(); + QVariant getServices(); + QVariant getCharacteristics(); + QString getUpdate(); + bool state(); + +public slots: + void startDeviceDiscovery(); + void scanServices(const QString &address); + + void connectToService(const QString &uuid); + void disconnectFromDevice(); + +private slots: + // QBluetoothDeviceDiscoveryAgent related + void addDevice(const QBluetoothDeviceInfo&); + void deviceScanFinished(); + void deviceScanError(QBluetoothDeviceDiscoveryAgent::Error); + + // QLowEnergyController realted + void addLowEnergyService(const QBluetoothUuid &uuid); + void deviceConnected(); + void errorReceived(QLowEnergyController::Error); + void serviceScanDone(); + void deviceDisconnected(); + + // QLowEnergyService related + void serviceDetailsDiscovered(QLowEnergyService::ServiceState newState); + +Q_SIGNALS: + void devicesUpdated(); + void servicesUpdated(); + void characteristicsUpdated(); + void updateChanged(); + void stateChanged(); + void disconnected(); + +private: + void setUpdate(QString message); + QBluetoothDeviceDiscoveryAgent *discoveryAgent; + DeviceInfo currentDevice; + QList<QObject*> devices; + QList<QObject*> m_services; + QList<QObject*> m_characteristics; + QString m_message; + bool connected; + QLowEnergyController *controller; + bool m_deviceScanState; +}; + +#endif // DEVICE_H diff --git a/examples/bluetooth/lowenergyscanner/deviceinfo.cpp b/examples/bluetooth/lowenergyscanner/deviceinfo.cpp new file mode 100644 index 00000000..d6b7a734 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/deviceinfo.cpp @@ -0,0 +1,72 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "deviceinfo.h" + +DeviceInfo::DeviceInfo() +{ +} + +DeviceInfo::DeviceInfo(const QBluetoothDeviceInfo &d) +{ + device = d; +} + +QString DeviceInfo::getAddress() const +{ + return device.address().toString(); +} + +QString DeviceInfo::getName() const +{ + return device.name(); +} + +QBluetoothDeviceInfo DeviceInfo::getDevice() +{ + return device; +} + +void DeviceInfo::setDevice(const QBluetoothDeviceInfo &dev) +{ + device = QBluetoothDeviceInfo(dev); + Q_EMIT deviceChanged(); +} diff --git a/examples/bluetooth/lowenergyscanner/deviceinfo.h b/examples/bluetooth/lowenergyscanner/deviceinfo.h new file mode 100644 index 00000000..48f11cfe --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/deviceinfo.h @@ -0,0 +1,71 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DEVICEINFO_H +#define DEVICEINFO_H + +#include <QObject> +#include <qbluetoothdeviceinfo.h> +#include <qbluetoothaddress.h> +#include <QList> +#include "deviceinfo.h" + +class DeviceInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString deviceName READ getName NOTIFY deviceChanged) + Q_PROPERTY(QString deviceAddress READ getAddress NOTIFY deviceChanged) +public: + DeviceInfo(); + DeviceInfo(const QBluetoothDeviceInfo &d); + QString getAddress() const; + QString getName() const; + QBluetoothDeviceInfo getDevice(); + void setDevice(const QBluetoothDeviceInfo &dev); + +Q_SIGNALS: + void deviceChanged(); + +private: + QBluetoothDeviceInfo device; +}; + +#endif // DEVICEINFO_H diff --git a/examples/bluetooth/lowenergyscanner/doc/images/lowenergyscanner-example.png b/examples/bluetooth/lowenergyscanner/doc/images/lowenergyscanner-example.png Binary files differnew file mode 100644 index 00000000..bdc3a22e --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/doc/images/lowenergyscanner-example.png diff --git a/examples/bluetooth/lowenergyscanner/doc/images/lowenergyscanner-example1.png b/examples/bluetooth/lowenergyscanner/doc/images/lowenergyscanner-example1.png Binary files differnew file mode 100644 index 00000000..4854abf7 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/doc/images/lowenergyscanner-example1.png diff --git a/examples/bluetooth/lowenergyscanner/doc/src/lowenergyscanner.qdoc b/examples/bluetooth/lowenergyscanner/doc/src/lowenergyscanner.qdoc new file mode 100644 index 00000000..fe7e007f --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/doc/src/lowenergyscanner.qdoc @@ -0,0 +1,40 @@ +/**************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Contact: http://www.qt-project.org/legal +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:FDL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Free Documentation License Usage +** Alternatively, this file may be used under the terms of the GNU Free +** Documentation License version 1.3 as published by the Free Software +** Foundation and appearing in the file included in the packaging of +** this file. Please review the following information to ensure +** the GNU Free Documentation License version 1.3 requirements +** will be met: http://www.gnu.org/copyleft/fdl.html. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/*! + \example lowenergyscanner + \title Bluetooth Low Energy Scanner Example + + The Bluetooth Low Energy Scanner Example shows how to develop Bluetooth + Low Energy applications using the Qt Bluetooth API. The application covers + scanning for Low Energy devices, scanning their services and reading + the service characteristics. + + \image lowenergyscanner-example.png + \image lowenergyscanner-example1.png + +*/ diff --git a/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro b/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro new file mode 100644 index 00000000..f3378b4c --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/lowenergyscanner.pro @@ -0,0 +1,25 @@ +TARGET = lowenergyscanner +INCLUDEPATH += . + +QT += quick bluetooth + +# Input +SOURCES += main.cpp \ + device.cpp \ + deviceinfo.cpp \ + serviceinfo.cpp \ + characteristicinfo.cpp + +OTHER_FILES += assets/*.qml + +HEADERS += \ + device.h \ + deviceinfo.h \ + serviceinfo.h \ + characteristicinfo.h + +RESOURCES += \ + resources.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/bluetooth/lowenergyscanner +INSTALLS += target diff --git a/examples/bluetooth/lowenergyscanner/main.cpp b/examples/bluetooth/lowenergyscanner/main.cpp new file mode 100644 index 00000000..ce359545 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/main.cpp @@ -0,0 +1,62 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QLoggingCategory> +#include <QQmlContext> +#include <QGuiApplication> +#include <QQuickView> +#include "device.h" + + +int main(int argc, char *argv[]) +{ + //QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); + QGuiApplication app(argc, argv); + + Device d; + QQuickView *view = new QQuickView; + view->rootContext()->setContextProperty("device", &d); + + view->setSource(QUrl("qrc:/assets/main.qml")); + view->setResizeMode(QQuickView::SizeRootObjectToView); + view->show(); + return app.exec(); +} diff --git a/examples/bluetooth/lowenergyscanner/resources.qrc b/examples/bluetooth/lowenergyscanner/resources.qrc new file mode 100644 index 00000000..49a518e8 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/resources.qrc @@ -0,0 +1,11 @@ +<RCC> + <qresource prefix="/"> + <file>assets/Characteristics.qml</file> + <file>assets/main.qml</file> + <file>assets/Menu.qml</file> + <file>assets/Services.qml</file> + <file>assets/Header.qml</file> + <file>assets/Dialog.qml</file> + <file>assets/Label.qml</file> + </qresource> +</RCC> diff --git a/examples/bluetooth/lowenergyscanner/serviceinfo.cpp b/examples/bluetooth/lowenergyscanner/serviceinfo.cpp new file mode 100644 index 00000000..c05422ca --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/serviceinfo.cpp @@ -0,0 +1,102 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "serviceinfo.h" + +ServiceInfo::ServiceInfo() +{ +} + +ServiceInfo::ServiceInfo(QLowEnergyService *service): + m_service(service) +{ + m_service->setParent(this); +} + +QLowEnergyService *ServiceInfo::service() const +{ + return m_service; +} + +QString ServiceInfo::getName() const +{ + if (!m_service) + return QString(); + + return m_service->serviceName(); +} + +QString ServiceInfo::getType() const +{ + if (!m_service) + return QString(); + + QString result; + if (m_service->type() & QLowEnergyService::PrimaryService) + result += QStringLiteral("primary"); + else + result += QStringLiteral("secondary"); + + if (m_service->type() & QLowEnergyService::IncludedService) + result += QStringLiteral(" included"); + + result.prepend('<').append('>'); + + return result; +} + +QString ServiceInfo::getUuid() const +{ + if (!m_service) + return QString(); + + const QBluetoothUuid uuid = m_service->serviceUuid(); + bool success = false; + quint16 result16 = uuid.toUInt16(&success); + if (success) + return QStringLiteral("0x") + QString::number(result16, 16); + + quint32 result32 = uuid.toUInt32(&success); + if (success) + return QStringLiteral("0x") + QString::number(result32, 16); + + return uuid.toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')); +} diff --git a/examples/bluetooth/lowenergyscanner/serviceinfo.h b/examples/bluetooth/lowenergyscanner/serviceinfo.h new file mode 100644 index 00000000..1d35b1b7 --- /dev/null +++ b/examples/bluetooth/lowenergyscanner/serviceinfo.h @@ -0,0 +1,67 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef SERVICEINFO_H +#define SERVICEINFO_H +#include <QtBluetooth/QLowEnergyService> + +class ServiceInfo: public QObject +{ + Q_OBJECT + Q_PROPERTY(QString serviceName READ getName NOTIFY serviceChanged) + Q_PROPERTY(QString serviceUuid READ getUuid NOTIFY serviceChanged) + Q_PROPERTY(QString serviceType READ getType NOTIFY serviceChanged) +public: + ServiceInfo(); + ServiceInfo(QLowEnergyService *service); + QLowEnergyService *service() const; + QString getUuid() const; + QString getName() const; + QString getType() const; + +Q_SIGNALS: + void serviceChanged(); + +private: + QLowEnergyService *m_service; +}; + +#endif // SERVICEINFO_H diff --git a/qtconnectivity.pro b/qtconnectivity.pro index e2a73944..365c4104 100644 --- a/qtconnectivity.pro +++ b/qtconnectivity.pro @@ -1,5 +1,6 @@ load(configure) qtCompileTest(bluez) +qtCompileTest(bluez_le) qtCompileTest(btapi10_2_1) qtCompileTest(libbb2) load(qt_parts) diff --git a/src/bluetooth/bluetooth.pro b/src/bluetooth/bluetooth.pro index 9caaf794..719d7ed5 100644 --- a/src/bluetooth/bluetooth.pro +++ b/src/bluetooth/bluetooth.pro @@ -2,6 +2,7 @@ TARGET = QtBluetooth QT = core QT_PRIVATE = concurrent + QMAKE_DOCS = $$PWD/doc/qtbluetooth.qdocconf OTHER_FILES += doc/src/*.qdoc # show .qdoc files in Qt Creator @@ -22,7 +23,12 @@ PUBLIC_HEADERS += \ qbluetoothlocaldevice.h \ qbluetoothtransfermanager.h \ qbluetoothtransferrequest.h \ - qbluetoothtransferreply.h + qlowenergyserviceinfo.h \ + qlowenergyservice.h \ + qlowenergycharacteristic.h \ + qlowenergydescriptor.h \ + qbluetoothtransferreply.h \ + qlowenergycontroller.h PRIVATE_HEADERS += \ qbluetoothaddress_p.h\ @@ -36,7 +42,10 @@ PRIVATE_HEADERS += \ qbluetoothtransferreply_p.h \ qbluetoothtransferrequest_p.h \ qprivatelinearbuffer_p.h \ - qbluetoothlocaldevice_p.h + qbluetoothlocaldevice_p.h \ + qlowenergyserviceinfo_p.h \ + qlowenergycontroller_p.h \ + qlowenergyserviceprivate_p.h SOURCES += \ qbluetoothaddress.cpp\ @@ -52,7 +61,13 @@ SOURCES += \ qbluetooth.cpp \ qbluetoothtransfermanager.cpp \ qbluetoothtransferrequest.cpp \ - qbluetoothtransferreply.cpp + qbluetoothtransferreply.cpp \ + qlowenergyserviceinfo.cpp \ + qlowenergyservice.cpp \ + qlowenergycharacteristic.cpp \ + qlowenergydescriptor.cpp \ + qlowenergycontroller.cpp \ + qlowenergyserviceprivate.cpp config_bluez:qtHaveModule(dbus) { QT *= dbus @@ -70,16 +85,29 @@ config_bluez:qtHaveModule(dbus) { qbluetoothsocket_bluez.cpp \ qbluetoothserver_bluez.cpp \ qbluetoothlocaldevice_bluez.cpp \ - qbluetoothtransferreply_bluez.cpp + qbluetoothtransferreply_bluez.cpp \ + + + # old versions of Bluez do not have the required BTLE symbols + config_bluez_le { + SOURCES += \ + qlowenergycontroller_bluez.cpp + } else { + message("Bluez version is too old to support Bluetooth Low Energy.") + message("Only classic Bluetooth will be available.") + DEFINES += QT_BLUEZ_NO_BTLE + SOURCES += \ + qlowenergycontroller_p.cpp + } } else:CONFIG(blackberry) { DEFINES += QT_QNX_BLUETOOTH include(qnx/qnx.pri) + LIBS += -lbtapi config_btapi10_2_1 { DEFINES += QT_QNX_BT_BLUETOOTH - LIBS += -lbtapi } PRIVATE_HEADERS += \ @@ -92,7 +120,8 @@ config_bluez:qtHaveModule(dbus) { qbluetoothservicediscoveryagent_qnx.cpp \ qbluetoothsocket_qnx.cpp \ qbluetoothserver_qnx.cpp \ - qbluetoothtransferreply_qnx.cpp + qbluetoothtransferreply_qnx.cpp \ + qlowenergycontroller_p.cpp } else:android:!android-no-sdk { include(android/android.pri) @@ -113,7 +142,8 @@ config_bluez:qtHaveModule(dbus) { qbluetoothserviceinfo_android.cpp \ qbluetoothservicediscoveryagent_android.cpp \ qbluetoothsocket_android.cpp \ - qbluetoothserver_android.cpp + qbluetoothserver_android.cpp \ + qlowenergycontroller_p.cpp } else { message("Unsupported Bluetooth platform, will not build a working QtBluetooth library.") @@ -124,12 +154,11 @@ config_bluez:qtHaveModule(dbus) { qbluetoothserviceinfo_p.cpp \ qbluetoothservicediscoveryagent_p.cpp \ qbluetoothsocket_p.cpp \ - qbluetoothserver_p.cpp - + qbluetoothserver_p.cpp \ + qlowenergycontroller_p.cpp } OTHER_FILES += HEADERS += $$PUBLIC_HEADERS $$PRIVATE_HEADERS - diff --git a/src/bluetooth/bluez/bluez_data_p.h b/src/bluetooth/bluez/bluez_data_p.h index 822f53b9..70b80ec3 100644 --- a/src/bluetooth/bluez/bluez_data_p.h +++ b/src/bluetooth/bluez/bluez_data_p.h @@ -44,6 +44,7 @@ #include <QtCore/qglobal.h> #include <sys/socket.h> +#include <QtBluetooth/QBluetoothUuid> #define BTPROTO_L2CAP 0 #define BTPROTO_RFCOMM 3 @@ -64,6 +65,27 @@ #define L2CAP_LM_TRUSTED 0x0008 #define L2CAP_LM_SECURE 0x0020 +#define BDADDR_LE_PUBLIC 0x01 + +/* Byte order conversions */ +#if __BYTE_ORDER == __LITTLE_ENDIAN +#define htobs(d) (d) +#define htobl(d) (d) +#define htobll(d) (d) +#define btohs(d) (d) +#define btohl(d) (d) +#define btohll(d) (d) +#elif __BYTE_ORDER == __BIG_ENDIAN +#define htobs(d) bswap_16(d) +#define htobl(d) bswap_32(d) +#define htobll(d) bswap_64(d) +#define btohs(d) bswap_16(d) +#define btohl(d) bswap_32(d) +#define btohll(d) bswap_64(d) +#else +#error "Unknown byte order" +#endif + // Bluetooth address typedef struct { quint8 b[6]; @@ -75,6 +97,9 @@ struct sockaddr_l2 { unsigned short l2_psm; bdaddr_t l2_bdaddr; unsigned short l2_cid; +#if !defined(QT_BLUEZ_NO_BTLE) + quint8 l2_bdaddr_type; +#endif }; // RFCOMM socket @@ -84,4 +109,65 @@ struct sockaddr_rc { quint8 rc_channel; }; +// Bt Low Energy related + +#define bt_get_unaligned(ptr) \ +({ \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v; \ +}) + +#define bt_put_unaligned(val, ptr) \ +do { \ + struct __attribute__((packed)) { \ + __typeof__(*(ptr)) __v; \ + } *__p = (__typeof__(__p)) (ptr); \ + __p->__v = (val); \ +} while (0) + +#if __BYTE_ORDER == __LITTLE_ENDIAN + +static inline void btoh128(const quint128 *src, quint128 *dst) +{ + memcpy(dst, src, sizeof(quint128)); +} + +static inline void ntoh128(const quint128 *src, quint128 *dst) +{ + int i; + + for (i = 0; i < 16; i++) + dst->data[15 - i] = src->data[i]; +} + +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bt_get_unaligned((const uint16_t *) ptr); +} +#elif __BYTE_ORDER == __BIG_ENDIAN +static inline uint16_t bt_get_le16(const void *ptr) +{ + return bswap_16(bt_get_unaligned((const uint16_t *) ptr)); +} + +static inline void btoh128(const quint128 *src, quint128 *dst) +{ + int i; + + for (i = 0; i < 16; i++) + dst->data[15 - i] = src->data[i]; +} + +static inline void ntoh128(const quint128 *src, quint128 *dst) +{ + memcpy(dst, src, sizeof(quint128)); +} +#else +#error "Unknown byte order" +#endif + +#define hton128(x, y) ntoh128(x, y) + #endif // BLUEZ_DATA_P_H diff --git a/src/bluetooth/doc/src/bluetooth-index.qdoc b/src/bluetooth/doc/src/bluetooth-index.qdoc index fe9322e9..afdf2600 100644 --- a/src/bluetooth/doc/src/bluetooth-index.qdoc +++ b/src/bluetooth/doc/src/bluetooth-index.qdoc @@ -77,6 +77,8 @@ import statement in your \c .qml file: \li \l {scanner}{QML Bluetooth Scanner} \li \l {picturetransfer}{QML Bluetooth Picture Push} \li \l {pingpong}{QML Bluetooth PingPong} + \li \l {lowenergyscanner}{Bluetooth Low Energy Scanner} + \li \l {heartlistener}{Heart Listener} \endlist \li C++ \list diff --git a/src/bluetooth/doc/src/bluetooth-overview.qdoc b/src/bluetooth/doc/src/bluetooth-overview.qdoc index f8040fb8..e20a1d22 100644 --- a/src/bluetooth/doc/src/bluetooth-overview.qdoc +++ b/src/bluetooth/doc/src/bluetooth-overview.qdoc @@ -29,7 +29,8 @@ \ingroup technology-apis \title Qt Bluetooth Overview \page qtbluetooth-overview.html -\brief The Qt Bluetooth API enables connectivity with other Bluetooth enabled devices. +\brief The Qt Bluetooth API enables connectivity with other regular Bluetooth + and Bluetooth Low Energy enabled devices. \tableofcontents @@ -41,6 +42,9 @@ \li Push files to remote devices using the OBEX Object Push Profile (OPP) \li Connect to remote devices through a RFCOMM channel using the Serial Port Profile (SPP). \li Create a RFCOMM server that allows incoming connections using SPP. + \li Retrieve specification about Bluetooth Low Energy device. + \li Connect to Bluetooth Low Energy device. + \li Receive advertisement from Bluetooth Low Energy device. \endlist Note that the Object Push Profile is not supported on Android. @@ -103,4 +107,66 @@ \l QBluetoothSocket classes. A good example to start with SPP is the \l{btchat}{Bluetooth Chat} example. + \section1 Bluetooth Low Energy + + Bluetooth Low Energy (in later text BLE), also known as Bluetooth Smart is a wireless computer + network technology, which was officially introduced in 2011. It works at the same, + 2,4HGz frequency, as ”classic” Bluetooth. The main difference is, as stated by its technology name, + low energy consumption. It provides an opportunity for BLE devices to operate for months, + even years, on coin-cell batteries. This technology was introduced with Bluetooth v 4.0 + and devices which support this technology are called Bluetooth Smart Ready Devices. + The key features of technology are: + \list + \li Ultra-low peak, average and idle mode power consumption + \li Ability to run for years on standard, coin-cell batteries + \li Low cost + \li Multi-vendor interoperability + \li Enhanced range + \endlist + + BLE uses a client-server architecture. The server (BLE device) offers services (temperature, + heart rate or any other measurements) and advertises them. The client (PC, smartphone + or any other Bluetooth Smart Ready device) connects to the server and reads the values + advertised by the server. The BLE API is based on GATT (Generic Attribute Profile) concepts. + GATT commands are initiated by the client, and the server processes them. Each command is + usually answered by a reply. + + Each BLE service may consist of one or more characteristics. A characteristic + contains data and can be further described by descriptors, which provide additional + information or means of manipulating the characteristic. All services, characteristics and + descriptors are recognized by their 128bits UUIDs. Further details on known uuids can be found + in \l QBluetoothUuid. + + To be able to read and write characteristics, it is necessary to connect to the LE service. + + \snippet heartlistener/heartrate.cpp Connect signals + + We start a service discovery with a \l QBluetoothServiceDiscoveryAgent class and connect its + \l {QBluetoothServiceDiscoveryAgent::}{serviceDiscovered()} signal. Within the receiving slot we connect to the desired service. + \l QLowEnergyController is used to connect or disconnect to services, emits service-related value changes + and propagates errors in relation to the service management. + + Even though it is possible to connect to an LE service before the service scan is done, + it is advisable to delay until after the service search has finished. + + \snippet heartlistener/heartrate.cpp Connecting to service + + In the code example above, the desired characteristics is of type + \l {QBluetoothUuid::HeartRateMeasurement}{HeartRateMeasurement}. Since the application measures + the heart rate changes, it must enable change notifications for the characteristic. + Note that not all characteristics provide change notifications. Since the HeartRate characteristic + has been standardized it is possible to assume that notifications can be received. Ultimately + \l QLowEnergyCharacteristicInfo::isNotificationCharacteristic() must return \c true to + verify the availability of notifications. + + Finally, we process the value of the HeartRate characteristic, as per Bluetooth Low Energy standard: + + \snippet heartlistener/heartrate.cpp Reading value + + In general a characterisitic value is a series of hexadecimal numbers. The precise interpretation of + those hexadecimal numbers depends on the characteristic type and its value structure. + A significant number has been standardized by the + \l {https://developer.bluetooth.org/gatt/services/Pages/ServicesHome.aspx}{Bluetooth SIG} whereas others + may follow a custom protocol. The above code snippet demonstrates how to the read the standardized + HeartRate value. */ diff --git a/src/bluetooth/doc/src/examples.qdoc b/src/bluetooth/doc/src/examples.qdoc index f9965075..f53d7127 100644 --- a/src/bluetooth/doc/src/examples.qdoc +++ b/src/bluetooth/doc/src/examples.qdoc @@ -69,6 +69,13 @@ \row \li \l{picturetransfer}{QML Picture Push Example} \li A QML application that transfers pictures between Bluetooth devices. + \row + \li \l{lowenergyscanner}{QML Bluetooth Low Energy Scanner} + \li Scan for Bluetooth Low Energy devices, services and characteristics. + \row + \li \l{heartlistener}{QML Bluetooth Low Energy Heart Listener} + \li Connect to the Bluetooth Low Energy heart belt and receive + measurements. \endtable */ diff --git a/src/bluetooth/gatoattclient.cpp b/src/bluetooth/gatoattclient.cpp new file mode 100644 index 00000000..abf3a760 --- /dev/null +++ b/src/bluetooth/gatoattclient.cpp @@ -0,0 +1,617 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QDataStream> +#include <QtCore/QDebug> + +#include "gatoattclient.h" +#include "helpers.h" + +#define PROTOCOL_DEBUG 0 + +#define ATT_CID 4 +#define ATT_PSM 31 + +#define ATT_DEFAULT_LE_MTU 23 +#define ATT_MAX_LE_MTU 0x200 + +QT_BEGIN_NAMESPACE + +enum AttOpcode { + AttOpNone = 0, + AttOpErrorResponse = 0x1, + AttOpExchangeMTURequest = 0x2, + AttOpExchangeMTUResponse = 0x3, + AttOpFindInformationRequest = 0x4, + AttOpFindInformationResponse = 0x5, + AttOpFindByTypeValueRequest = 0x6, + AttOpFindByTypeValueResponse = 0x7, + AttOpReadByTypeRequest = 0x8, + AttOpReadByTypeResponse = 0x9, + AttOpReadRequest = 0xA, + AttOpReadResponse = 0xB, + AttOpReadBlobRequest = 0xC, + AttOpReadBlobResponse = 0xD, + AttOpReadMultipleRequest = 0xE, + AttOpReadMultipleResponse = 0xF, + AttOpReadByGroupTypeRequest = 0x10, + AttOpReadByGroupTypeResponse = 0x11, + AttOpWriteRequest = 0x12, + AttOpWriteResponse = 0x13, + AttOpWriteCommand = 0x52, + AttOpPrepareWriteRequest = 0x16, + AttOpPrepareWriteResponse = 0x17, + AttOpExecuteWriteRequest = 0x18, + AttOpExecuteWriteResponse = 0x19, + AttOpHandleValueNotification = 0x1B, + AttOpHandleValueIndication = 0x1D, + AttOpHandleValueConfirmation = 0x1E, + AttOpSignedWriteCommand = 0xD2 +}; + +static QByteArray remove_method_signature(const char *sig) +{ + const char* bracketPosition = strchr(sig, '('); + if (!bracketPosition || !(sig[0] >= '0' && sig[0] <= '3')) { + qWarning("Invalid slot specification"); + return QByteArray(); + } + return QByteArray(sig + 1, bracketPosition - 1 - sig); +} + +GatoAttClient::GatoAttClient(QObject *parent) : + QObject(parent), socket(new GatoSocket(this)), cur_mtu(ATT_DEFAULT_LE_MTU), next_id(1) +{ + connect(socket, SIGNAL(connected()), SLOT(handleSocketConnected())); + connect(socket, SIGNAL(disconnected()), SLOT(handleSocketDisconnected())); + connect(socket, SIGNAL(readyRead()), SLOT(handleSocketReadyRead())); +} + +GatoAttClient::~GatoAttClient() +{ +} + +GatoSocket::State GatoAttClient::state() const +{ + return socket->state(); +} + +bool GatoAttClient::connectTo(const GatoAddress &addr) +{ + return socket->connectTo(addr, ATT_CID); +} + +void GatoAttClient::close() +{ + socket->close(); +} + +int GatoAttClient::mtu() const +{ + return cur_mtu; +} + +uint GatoAttClient::request(int opcode, const QByteArray &data, QObject *receiver, const char *member) +{ + Request req; + req.id = next_id++; + req.opcode = opcode; + req.pkt = data; + req.pkt.prepend(static_cast<char>(opcode)); + req.receiver = receiver; + req.member = remove_method_signature(member); + + pending_requests.enqueue(req); + + if (pending_requests.size() == 1) { + // So we can just send this request instead of waiting for others to complete + sendARequest(); + } + + return req.id; +} + +void GatoAttClient::cancelRequest(uint id) +{ + QQueue<Request>::iterator it = pending_requests.begin(); + while (it != pending_requests.end()) { + if (it->id == id) { + it = pending_requests.erase(it); + } else { + ++it; + } + } +} + +uint GatoAttClient::requestExchangeMTU(quint16 client_mtu, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << client_mtu; + + return request(AttOpExchangeMTURequest, data, receiver, member); +} + +uint GatoAttClient::requestFindInformation(GatoHandle start, GatoHandle end, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << start << end; + + return request(AttOpFindInformationRequest, data, receiver, member); +} + +uint GatoAttClient::requestFindByTypeValue(GatoHandle start, GatoHandle end, const GatoUUID &uuid, const QByteArray &value, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << start << end; + + bool uuid16_ok; + quint16 uuid16 = uuid.toUInt16(&uuid16_ok); + if (uuid16_ok) { + s << uuid16; + } else { + qWarning() << "FindByTypeValue does not support UUIDs other than UUID16"; + return -1; + } + + s << value; + + return request(AttOpFindByTypeValueRequest, data, receiver, member); +} + +uint GatoAttClient::requestReadByType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << start << end; + write_gatouuid(s, uuid, true, false); + + return request(AttOpReadByTypeRequest, data, receiver, member); +} + +uint GatoAttClient::requestRead(GatoHandle handle, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << handle; + + return request(AttOpReadRequest, data, receiver, member); +} + +uint GatoAttClient::requestReadByGroupType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << start << end; + write_gatouuid(s, uuid, true, false); + + return request(AttOpReadByGroupTypeRequest, data, receiver, member); +} + +uint GatoAttClient::requestWrite(GatoHandle handle, const QByteArray &value, QObject *receiver, const char *member) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << handle; + s.writeRawData(value.constData(), value.length()); + + return request(AttOpWriteRequest, data, receiver, member); +} + +void GatoAttClient::command(int opcode, const QByteArray &data) +{ + QByteArray packet = data; + packet.prepend(static_cast<char>(opcode)); + + socket->send(packet); + +#if PROTOCOL_DEBUG + qDebug() << "Wrote" << packet.size() << "bytes (command)" << packet.toHex(); +#endif +} + +void GatoAttClient::commandWrite(GatoHandle handle, const QByteArray &value) +{ + QByteArray data; + QDataStream s(&data, QIODevice::WriteOnly); + s.setByteOrder(QDataStream::LittleEndian); + s << handle; + s.writeRawData(value.constData(), value.length()); + + command(AttOpWriteCommand, data); +} + +void GatoAttClient::sendARequest() +{ + if (pending_requests.isEmpty()) { + return; + } + + Request &req = pending_requests.head(); + socket->send(req.pkt); + +#if PROTOCOL_DEBUG + qDebug() << "Wrote" << req.pkt.size() << "bytes (request)" << req.pkt.toHex(); +#endif +} + +bool GatoAttClient::handleEvent(const QByteArray &event) +{ + const char *data = event.constData(); + quint8 opcode = event[0]; + GatoHandle handle; + + switch (opcode) { + case AttOpHandleValueNotification: + handle = read_le<GatoHandle>(&data[1]); + emit attributeUpdated(handle, event.mid(3), false); + return true; + case AttOpHandleValueIndication: + handle = read_le<GatoHandle>(&data[1]); + + // Send the confirmation back + command(AttOpHandleValueConfirmation, QByteArray()); + + emit attributeUpdated(handle, event.mid(3), true); + return true; + default: + return false; + } +} + +bool GatoAttClient::handleResponse(const Request &req, const QByteArray &response) +{ + // If we know the request, we can provide a decoded answer + switch (req.opcode) { + case AttOpExchangeMTURequest: + if (response[0] == AttOpExchangeMTUResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(quint16, read_le<quint16>(response.constData() + 1))); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpExchangeMTURequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(quint16, 0)); + } + return true; + } else { + return false; + } + break; + case AttOpFindInformationRequest: + if (response[0] == AttOpFindInformationResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::InformationData>, parseInformationData(response.mid(1)))); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpFindInformationRequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::InformationData>, QList<InformationData>())); + } + return true; + } else { + return false; + } + break; + case AttOpFindByTypeValueRequest: + if (response[0] == AttOpFindByTypeValueResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::HandleInformation>, parseHandleInformation(response.mid(1)))); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpFindByTypeValueRequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::HandleInformation>, QList<HandleInformation>())); + } + return true; + } else { + return false; + } + break; + case AttOpReadByTypeRequest: + if (response[0] == AttOpReadByTypeResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::AttributeData>, parseAttributeData(response.mid(1)))); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadByTypeRequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::AttributeData>, QList<AttributeData>())); + } + return true; + } else { + return false; + } + break; + case AttOpReadRequest: + if (response[0] == AttOpReadResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QByteArray, response.mid(1))); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadRequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QByteArray, QByteArray())); + } + return true; + } else { + return false; + } + break; + case AttOpReadByGroupTypeRequest: + if (response[0] == AttOpReadByGroupTypeResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::AttributeGroupData>, parseAttributeGroupData(response.mid(1)))); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpReadByGroupTypeRequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(QList<GatoAttClient::AttributeGroupData>, QList<AttributeGroupData>())); + } + return true; + } else { + return false; + } + break; + case AttOpWriteRequest: + if (response[0] == AttOpWriteResponse) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(bool, true)); + } + return true; + } else if (response[0] == AttOpErrorResponse && response[1] == AttOpWriteRequest) { + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(uint, req.id), + Q_ARG(bool, false)); + } + return true; + } else { + return false; + } + break; + default: // Otherwise just send a QByteArray. + if (req.receiver) { + QMetaObject::invokeMethod(req.receiver, req.member.constData(), + Q_ARG(const QByteArray&, response)); + } + return true; + } +} + +QList<GatoAttClient::InformationData> GatoAttClient::parseInformationData(const QByteArray &data) +{ + const int format = data[0]; + QList<InformationData> list; + int item_len; + + switch (format) { + case 1: + item_len = 2 + 2; + break; + case 2: + item_len = 2 + 16; + break; + default: + qWarning() << "Unknown InformationData format!"; + return list; + } + + int items = (data.size() - 1) / item_len; + list.reserve(items); + + int pos = 1; + const char *s = data.constData(); + for (int i = 0; i < items; i++) { + InformationData d; + QByteArray uuid; + d.handle = read_le<GatoHandle>(&s[pos]); + switch (format) { + case 1: + uuid = data.mid(pos + 2, 2); + break; + case 2: + uuid = data.mid(pos + 2, 16); + break; + } + d.uuid = bytearray_to_gatouuid(uuid); + + list.append(d); + + pos += item_len; + } + + return list; +} + +QList<GatoAttClient::HandleInformation> GatoAttClient::parseHandleInformation(const QByteArray &data) +{ + const int item_len = 2; + const int items = data.size() / item_len; + QList<HandleInformation> list; + list.reserve(items); + + int pos = 0; + const char *s = data.constData(); + for (int i = 0; i < items; i++) { + HandleInformation d; + d.start = read_le<GatoHandle>(&s[pos]); + d.end = read_le<GatoHandle>(&s[pos + 2]); + list.append(d); + + pos += item_len; + } + + return list; +} + +QList<GatoAttClient::AttributeData> GatoAttClient::parseAttributeData(const QByteArray &data) +{ + const int item_len = data[0]; + const int items = (data.size() - 1) / item_len; + QList<AttributeData> list; + list.reserve(items); + + int pos = 1; + const char *s = data.constData(); + for (int i = 0; i < items; i++) { + AttributeData d; + d.handle = read_le<GatoHandle>(&s[pos]); + d.value = data.mid(pos + 2, item_len - 2); + list.append(d); + + pos += item_len; + } + + return list; +} + +QList<GatoAttClient::AttributeGroupData> GatoAttClient::parseAttributeGroupData(const QByteArray &data) +{ + const int item_len = data[0]; + const int items = (data.size() - 1) / item_len; + QList<AttributeGroupData> list; + list.reserve(items); + + int pos = 1; + const char *s = data.constData(); + for (int i = 0; i < items; i++) { + AttributeGroupData d; + d.start = read_le<GatoHandle>(&s[pos]); + d.end = read_le<GatoHandle>(&s[pos + 2]); + d.value = data.mid(pos + 4, item_len - 4); + list.append(d); + + pos += item_len; + } + + return list; +} + +void GatoAttClient::handleSocketConnected() +{ + requestExchangeMTU(ATT_MAX_LE_MTU, this, SLOT(handleServerMTU(quint16))); + emit connected(); +} + +void GatoAttClient::handleSocketDisconnected() +{ + emit disconnected(); +} + +void GatoAttClient::handleSocketReadyRead() +{ + QByteArray pkt = socket->receive(); + if (!pkt.isEmpty()) { +#if PROTOCOL_DEBUG + qDebug() << "Received" << pkt.size() << "bytes" << pkt.toHex(); +#endif + + // Check if it is an event + if (handleEvent(pkt)) { + return; + } + + // Otherwise, if we have a request waiting, check if this answers it + if (!pending_requests.isEmpty()) { + if (handleResponse(pending_requests.head(), pkt)) { + pending_requests.dequeue(); + // Proceed to next request + if (!pending_requests.isEmpty()) { + sendARequest(); + } + return; + } + } + + qDebug() << "No idea what this packet (" + << QString("0x%1").arg(uint(pkt.at(0)), 2, 16, QLatin1Char('0')) + << ") is"; + } +} + +void GatoAttClient::handleServerMTU(uint req, quint16 server_mtu) +{ + Q_UNUSED(req); + if (server_mtu) { + cur_mtu = server_mtu; + if (cur_mtu < ATT_DEFAULT_LE_MTU) { + cur_mtu = ATT_DEFAULT_LE_MTU; + } + } +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/gatoattclient.h b/src/bluetooth/gatoattclient.h new file mode 100644 index 00000000..6222f863 --- /dev/null +++ b/src/bluetooth/gatoattclient.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GATOATTCLIENT_H +#define GATOATTCLIENT_H + +#include <QtCore/QObject> +#include <QtCore/QQueue> +#include "gatosocket.h" +#include "gatouuid.h" + +QT_BEGIN_NAMESPACE + +class GatoAttClient : public QObject +{ + Q_OBJECT + +public: + explicit GatoAttClient(QObject *parent = 0); + ~GatoAttClient(); + + GatoSocket::State state() const; + + bool connectTo(const GatoAddress& addr); + void close(); + + struct InformationData + { + GatoHandle handle; + GatoUUID uuid; + }; + struct HandleInformation + { + GatoHandle start; + GatoHandle end; + }; + struct AttributeData + { + GatoHandle handle; + QByteArray value; + }; + struct AttributeGroupData + { + GatoHandle start; + GatoHandle end; + QByteArray value; + }; + + int mtu() const; + + uint request(int opcode, const QByteArray &data, QObject *receiver, const char *member); + uint requestExchangeMTU(quint16 client_mtu, QObject *receiver, const char *member); + uint requestFindInformation(GatoHandle start, GatoHandle end, QObject *receiver, const char *member); + uint requestFindByTypeValue(GatoHandle start, GatoHandle end, const GatoUUID &uuid, const QByteArray& value, QObject *receiver, const char *member); + uint requestReadByType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member); + uint requestRead(GatoHandle handle, QObject *receiver, const char *member); + uint requestReadByGroupType(GatoHandle start, GatoHandle end, const GatoUUID &uuid, QObject *receiver, const char *member); + uint requestWrite(GatoHandle handle, const QByteArray &value, QObject *receiver, const char *member); + void cancelRequest(uint id); + + void command(int opcode, const QByteArray &data); + void commandWrite(GatoHandle handle, const QByteArray &value); + +Q_SIGNALS: + void connected(); + void disconnected(); + + void attributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed); + +private: + struct Request + { + uint id; + quint8 opcode; + QByteArray pkt; + QObject *receiver; + QByteArray member; + }; + + void sendARequest(); + bool handleEvent(const QByteArray &event); + bool handleResponse(const Request& req, const QByteArray &response); + + QList<InformationData> parseInformationData(const QByteArray &data); + QList<HandleInformation> parseHandleInformation(const QByteArray &data); + QList<AttributeData> parseAttributeData(const QByteArray &data); + QList<AttributeGroupData> parseAttributeGroupData(const QByteArray &data); + +private Q_SLOTS: + void handleSocketConnected(); + void handleSocketDisconnected(); + void handleSocketReadyRead(); + + void handleServerMTU(uint req, quint16 server_mtu); + +private: + GatoSocket *socket; + quint16 cur_mtu; + uint next_id; + QQueue<Request> pending_requests; +}; + +QT_END_NAMESPACE + +#endif // GATOATTCLIENT_H diff --git a/src/bluetooth/gatoperipheral.cpp b/src/bluetooth/gatoperipheral.cpp new file mode 100644 index 00000000..b44efc26 --- /dev/null +++ b/src/bluetooth/gatoperipheral.cpp @@ -0,0 +1,874 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QDebug> + +#include <assert.h> +#include <bluetooth/bluetooth.h> + +#include "gatoperipheral_p.h" +#include "gatoaddress.h" +#include "gatouuid.h" +#include "helpers.h" + +QT_BEGIN_NAMESPACE + +enum EIRDataFields { + EIRFlags = 0x01, + EIRIncompleteUUID16List = 0x02, + EIRCompleteUUID16List = 0x03, + EIRIncompleteUUID32List = 0x04, + EIRCompleteUUID32List = 0x05, + EIRIncompleteUUID128List = 0x06, + EIRCompleteUUID128List = 0x07, + EIRIncompleteLocalName = 0x08, + EIRCompleteLocalName = 0x09, + EIRTxPowerLevel = 0x0A, + EIRDeviceClass = 0x0D, + EIRSecurityManagerTKValue = 0x10, + EIRSecurityManagerOutOfBandFlags = 0x11, + EIRSolicitedUUID128List = 0x15 +}; + +GatoPeripheral::GatoPeripheral(const GatoAddress &addr, QObject *parent) : + QObject(parent), d_ptr(new GatoPeripheralPrivate(this)) +{ + Q_D(GatoPeripheral); + d->addr = addr; + d->att = new GatoAttClient(this); + + connect(d->att, SIGNAL(connected()), d, SLOT(handleAttConnected())); + connect(d->att, SIGNAL(disconnected()), d, SLOT(handleAttDisconnected())); + connect(d->att, SIGNAL(attributeUpdated(GatoHandle,QByteArray,bool)), d, SLOT(handleAttAttributeUpdated(GatoHandle,QByteArray,bool))); +} + +GatoPeripheral::~GatoPeripheral() +{ + if (state() != StateDisconnected) { + disconnect(); + } + delete d_ptr; +} + +GatoPeripheral::State GatoPeripheral::state() const +{ + Q_D(const GatoPeripheral); + return static_cast<State>(d->att->state()); +} + +GatoAddress GatoPeripheral::address() const +{ + Q_D(const GatoPeripheral); + return d->addr; +} + +QString GatoPeripheral::name() const +{ + Q_D(const GatoPeripheral); + return d->name; +} + +QList<GatoService> GatoPeripheral::services() const +{ + Q_D(const GatoPeripheral); + return d->services.values(); +} + +void GatoPeripheral::parseEIR(quint8 data[], int len) +{ + Q_D(GatoPeripheral); + + int pos = 0; + while (pos < len) { + int item_len = data[pos]; + pos++; + if (item_len == 0) break; + int type = data[pos]; + assert(pos + item_len <= len); + switch (type) { + case EIRFlags: + d->parseEIRFlags(&data[pos + 1], item_len - 1); + break; + case EIRIncompleteUUID16List: + d->parseEIRUUIDs(16/8, false, &data[pos + 1], item_len - 1); + break; + case EIRCompleteUUID16List: + d->parseEIRUUIDs(16/8, true, &data[pos + 1], item_len - 1); + break; + case EIRIncompleteUUID32List: + d->parseEIRUUIDs(32/8, false, &data[pos + 1], item_len - 1); + break; + case EIRCompleteUUID32List: + d->parseEIRUUIDs(32/8, true, &data[pos + 1], item_len - 1); + break; + case EIRIncompleteUUID128List: + d->parseEIRUUIDs(128/8, false, &data[pos + 1], item_len - 1); + break; + case EIRCompleteUUID128List: + d->parseEIRUUIDs(128/8, true, &data[pos + 1], item_len - 1); + break; + case EIRIncompleteLocalName: + d->parseName(false, &data[pos + 1], item_len - 1); + break; + case EIRCompleteLocalName: + d->parseName(true, &data[pos + 1], item_len - 1); + break; + case EIRTxPowerLevel: + case EIRSolicitedUUID128List: + qDebug() << "Unhandled EIR data type" << type; + break; + default: + qWarning() << "Unknown EIR data type" << type; + break; + } + + pos += item_len; + } + + assert(pos == len); +} + +bool GatoPeripheral::advertisesService(const GatoUUID &uuid) const +{ + Q_D(const GatoPeripheral); + return d->service_uuids.contains(uuid); +} + +void GatoPeripheral::connectPeripheral() +{ + Q_D(GatoPeripheral); + if (d->att->state() != GatoSocket::StateDisconnected) { + qDebug() << "Already connecting"; + return; + } + + d->att->connectTo(d->addr); +} + +void GatoPeripheral::disconnectPeripheral() +{ + Q_D(GatoPeripheral); + + d->att->close(); +} + +void GatoPeripheral::discoverServices() +{ + Q_D(GatoPeripheral); + if (!d->complete_services && state() == StateConnected) { + d->clearServices(); + d->att->requestReadByGroupType(0x0001, 0xFFFF, GatoUUID::GattPrimaryService, + d, SLOT(handlePrimary(QList<GatoAttClient::AttributeGroupData>))); + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::discoverServices(const QList<GatoUUID> &serviceUUIDs) +{ + Q_D(GatoPeripheral); + if (serviceUUIDs.isEmpty()) return; + if (state() == StateConnected) { + foreach (const GatoUUID& uuid, serviceUUIDs) { + QByteArray value = gatouuid_to_bytearray(uuid, true, false); + uint req = d->att->requestFindByTypeValue(0x0001, 0xFFFF, GatoUUID::GattPrimaryService, value, + d, SLOT(handlePrimaryForService(uint,QList<GatoAttClient::HandleInformation>))); + d->pending_primary_reqs.insert(req, uuid); + } + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::discoverCharacteristics(const GatoService &service) +{ + Q_D(GatoPeripheral); + + if (!d->services.contains(service.startHandle())) { + qWarning() << "Unknown service for this peripheral"; + return; + } + + GatoService &our_service = d->services[service.startHandle()]; + + if (our_service.startHandle() != service.startHandle() || + our_service.endHandle() != service.endHandle() || + our_service.uuid() != service.uuid()) { + qWarning() << "Unknown service for this peripheral"; + return; + } + + if (state() == StateConnected) { + GatoHandle start = our_service.startHandle(); + GatoHandle end = our_service.endHandle(); + + d->clearServiceCharacteristics(&our_service); + + uint req = d->att->requestReadByType(start, end, GatoUUID::GattCharacteristic, + d, SLOT(handleCharacteristic(QList<GatoAttClient::AttributeData>))); + d->pending_characteristic_reqs.insert(req, start); + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::discoverCharacteristics(const GatoService &service, const QList<GatoUUID> &characteristicUUIDs) +{ + // TODO There seems to be no way to ask for the peripheral to filter by uuid + Q_UNUSED(characteristicUUIDs); + discoverCharacteristics(service); +} + +void GatoPeripheral::discoverDescriptors(const GatoCharacteristic &characteristic) +{ + Q_D(GatoPeripheral); + + GatoHandle char_handle = characteristic.startHandle(); + GatoHandle service_handle = d->characteristic_to_service.value(char_handle); + + if (!service_handle) { + qWarning() << "Unknown characteristic for this peripheral"; + return; + } + + GatoService &our_service = d->services[service_handle]; + Q_ASSERT(our_service.containsCharacteristic(char_handle)); + GatoCharacteristic our_char = our_service.getCharacteristic(char_handle); + Q_ASSERT(our_char.startHandle() == char_handle); + + if (state() == StateConnected) { + d->clearCharacteristicDescriptors(&our_char); + our_service.addCharacteristic(our_char); // Update service with empty descriptors list + uint req = d->att->requestFindInformation(our_char.startHandle() + 1, our_char.endHandle(), + d, SLOT(handleDescriptors(uint,QList<GatoAttClient::InformationData>))); + d->pending_descriptor_reqs.insert(req, char_handle); + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::readValue(const GatoCharacteristic &characteristic) +{ + Q_D(GatoPeripheral); + + GatoHandle char_handle = characteristic.startHandle(); + GatoHandle service_handle = d->characteristic_to_service.value(char_handle); + + if (!service_handle) { + qWarning() << "Unknown characteristic for this peripheral"; + return; + } + + GatoService &our_service = d->services[service_handle]; + Q_ASSERT(our_service.containsCharacteristic(char_handle)); + + if (state() == StateConnected) { + uint req = d->att->requestRead(characteristic.valueHandle(), + d, SLOT(handleCharacteristicRead(uint,QByteArray))); + d->pending_characteristic_read_reqs.insert(req, char_handle); + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::readValue(const GatoDescriptor &descriptor) +{ + Q_D(GatoPeripheral); + + GatoHandle desc_handle = descriptor.handle(); + GatoHandle char_handle = d->descriptor_to_characteristic.value(desc_handle); + + if (!char_handle) { + qWarning() << "Unknown descriptor for this peripheral"; + return; + } + + GatoHandle service_handle = d->characteristic_to_service.value(char_handle); + Q_ASSERT(service_handle); + + GatoService &our_service = d->services[service_handle]; + Q_ASSERT(our_service.containsCharacteristic(char_handle)); + + if (state() == StateConnected) { + uint req = d->att->requestRead(descriptor.handle(), + d, SLOT(handleDescriptorRead(uint,QByteArray))); + d->pending_descriptor_read_reqs.insert(req, char_handle); + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::writeValue(const GatoCharacteristic &characteristic, const QByteArray &data, WriteType type) +{ + Q_D(GatoPeripheral); + + GatoHandle char_handle = characteristic.startHandle(); + GatoHandle service_handle = d->characteristic_to_service.value(char_handle); + + if (!service_handle) { + qWarning() << "Unknown characteristic for this peripheral"; + return; + } + + GatoService &our_service = d->services[service_handle]; + Q_ASSERT(our_service.containsCharacteristic(char_handle)); + + if (state() == StateConnected) { + switch (type) { + case WriteWithResponse: + d->att->requestWrite(characteristic.valueHandle(), data, + d, SLOT(handleCharacteristicWrite(uint,bool))); + break; + case WriteWithoutResponse: + d->att->commandWrite(characteristic.valueHandle(), data); + break; + } + + + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::writeValue(const GatoDescriptor &descriptor, const QByteArray &data) +{ + Q_D(GatoPeripheral); + + GatoHandle desc_handle = descriptor.handle(); + GatoHandle char_handle = d->descriptor_to_characteristic.value(desc_handle); + + if (!char_handle) { + qWarning() << "Unknown descriptor for this peripheral"; + return; + } + + GatoHandle service_handle = d->characteristic_to_service.value(char_handle); + Q_ASSERT(service_handle); + + GatoService &our_service = d->services[service_handle]; + Q_ASSERT(our_service.containsCharacteristic(char_handle)); + + if (state() == StateConnected) { + d->att->requestWrite(descriptor.handle(), data, + d, SLOT(handleDescriptorWrite(uint,bool))); + } else { + qWarning() << "Not connected"; + } +} + +void GatoPeripheral::setNotification(const GatoCharacteristic &characteristic, bool enabled) +{ + Q_D(GatoPeripheral); + + GatoHandle char_handle = characteristic.startHandle(); + GatoHandle service_handle = d->characteristic_to_service.value(char_handle); + + if (!service_handle) { + qWarning() << "Unknown characteristic for this peripheral"; + return; + } + + GatoService &our_service = d->services[service_handle]; + Q_ASSERT(our_service.containsCharacteristic(char_handle)); + GatoCharacteristic our_char = our_service.getCharacteristic(char_handle); + + if (!(our_char.properties() & GatoCharacteristic::PropertyNotify)) { + qWarning() << "Characteristic does not support notifications"; + return; + } + + if (state() != StateConnected) { + qWarning() << "Not connected"; + return; + } + + const GatoUUID uuid(GatoUUID::GattClientCharacteristicConfiguration); + if (our_char.containsDescriptor(uuid)) { + GatoDescriptor desc = our_char.getDescriptor(uuid); + d->pending_set_notify.remove(char_handle); + writeValue(characteristic, d->genClientCharConfiguration(true, false)); + } else { + d->pending_set_notify[char_handle] = enabled; + discoverDescriptors(our_char); // May need to find appropiate descriptor + } +} + +GatoPeripheralPrivate::GatoPeripheralPrivate(GatoPeripheral *parent) + : QObject(parent), q_ptr(parent), + complete_name(false), complete_services(false) +{ +} + +GatoPeripheralPrivate::~GatoPeripheralPrivate() +{ + delete att; +} + +void GatoPeripheralPrivate::parseEIRFlags(quint8 data[], int len) +{ + Q_UNUSED(data); + Q_UNUSED(len); + // Nothing to do for now. +} + +void GatoPeripheralPrivate::parseEIRUUIDs(int size, bool complete, quint8 data[], int len) +{ + Q_UNUSED(complete); + + if (size != 16/8 && size != 32/8 && size != 128/8) { + qWarning() << "Unhandled UUID size: " << size; + return; + } + + for (int pos = 0; pos < len; pos += size) { + char *ptr = reinterpret_cast<char*>(&data[pos]); + QByteArray ba = QByteArray::fromRawData(ptr, size/8); + + service_uuids.insert(bytearray_to_gatouuid(ba)); + } +} + +void GatoPeripheralPrivate::parseName(bool complete, quint8 data[], int len) +{ + Q_Q(GatoPeripheral); + if (complete || !complete_name) { + name = QString::fromUtf8(reinterpret_cast<char*>(data), len); + complete_name = complete; + emit q->nameChanged(); + } +} + +GatoCharacteristic GatoPeripheralPrivate::parseCharacteristicValue(const QByteArray &ba) +{ + GatoCharacteristic characteristic; + const char *data = ba.constData(); + + quint8 properties = data[0]; + characteristic.setProperties(GatoCharacteristic::Properties(properties)); + + GatoHandle handle = read_le<quint16>(&data[1]); + characteristic.setValueHandle(handle); + + GatoUUID uuid = bytearray_to_gatouuid(ba.mid(3)); + characteristic.setUuid(uuid); + + return characteristic; +} + +QByteArray GatoPeripheralPrivate::genClientCharConfiguration(bool notification, bool indication) +{ + QByteArray ba; + ba.resize(sizeof(quint16)); + + quint16 val = 0; + if (notification) + val |= 0x1; + if (indication) + val |= 0x2; + + write_le<quint16>(val, ba.data()); + + return ba; +} + +void GatoPeripheralPrivate::clearServices() +{ + characteristic_to_service.clear(); + value_to_characteristic.clear(); + descriptor_to_characteristic.clear(); + services.clear(); +} + +void GatoPeripheralPrivate::clearServiceCharacteristics(GatoService *service) +{ + QList<GatoCharacteristic> chars = service->characteristics(); + QList<GatoCharacteristic>::iterator it; + for (it = chars.begin(); it != chars.end(); ++it) { + clearCharacteristicDescriptors(&*it); + characteristic_to_service.remove(it->startHandle()); + value_to_characteristic.remove(it->valueHandle()); + } + service->clearCharacteristics(); +} + +void GatoPeripheralPrivate::clearCharacteristicDescriptors(GatoCharacteristic *characteristic) +{ + QList<GatoDescriptor> descs = characteristic->descriptors(); + foreach (const GatoDescriptor& d, descs) { + descriptor_to_characteristic.remove(d.handle()); + } + characteristic->clearDescriptors(); +} + +void GatoPeripheralPrivate::finishSetNotifyOperations(const GatoCharacteristic &characteristic) +{ + Q_Q(GatoPeripheral); + + GatoHandle handle = characteristic.startHandle(); + + if (pending_set_notify.contains(handle)) { + const GatoUUID uuid(GatoUUID::GattClientCharacteristicConfiguration); + bool notify = pending_set_notify.value(handle); + + foreach (const GatoDescriptor &descriptor, characteristic.descriptors()) { + if (descriptor.uuid() == uuid) { + q->writeValue(descriptor, genClientCharConfiguration(notify, false)); + } + } + + pending_set_notify.remove(handle); + } +} + +void GatoPeripheralPrivate::handleAttConnected() +{ + Q_Q(GatoPeripheral); + + emit q->connected(); +} + +void GatoPeripheralPrivate::handleAttDisconnected() +{ + Q_Q(GatoPeripheral); + + // Forget about all pending requests + pending_primary_reqs.clear(); + pending_characteristic_reqs.clear(); + pending_characteristic_read_reqs.clear(); + pending_descriptor_reqs.clear(); + pending_descriptor_read_reqs.clear(); + + emit q->disconnected(); +} + +void GatoPeripheralPrivate::handleAttAttributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed) +{ + Q_Q(GatoPeripheral); + Q_UNUSED(confirmed); + + // Let's see if this is a handle we know about. + if (value_to_characteristic.contains(handle)) { + // Ok, it's a characteristic value. + GatoHandle char_handle = value_to_characteristic.value(handle); + GatoHandle service_handle = characteristic_to_service.value(char_handle); + if (!service_handle) { + qWarning() << "Got a notification for a characteristic I don't know about"; + return; + } + + GatoService &service = services[service_handle]; + GatoCharacteristic characteristic = service.getCharacteristic(char_handle); + + emit q->valueUpdated(characteristic, value); + } +} + +void GatoPeripheralPrivate::handlePrimary(uint req, const QList<GatoAttClient::AttributeGroupData> &list) +{ + Q_Q(GatoPeripheral); + Q_UNUSED(req); + + if (list.isEmpty()) { + complete_services = true; + emit q->servicesDiscovered(); + } else { + GatoHandle last_handle = 0; + + foreach (const GatoAttClient::AttributeGroupData &data, list) { + GatoUUID uuid = bytearray_to_gatouuid(data.value); + GatoService service; + + service.setUuid(uuid); + service.setStartHandle(data.start); + service.setEndHandle(data.end); + + services.insert(data.start, service); + service_uuids.insert(uuid); + + last_handle = data.end; + } + + // Fetch following attributes + att->requestReadByGroupType(last_handle + 1, 0xFFFF, GatoUUID::GattPrimaryService, + this, SLOT(handlePrimary(uint,QList<GatoAttClient::AttributeGroupData>))); + } +} + +void GatoPeripheralPrivate::handlePrimaryForService(uint req, const QList<GatoAttClient::HandleInformation> &list) +{ + Q_Q(GatoPeripheral); + + GatoUUID uuid = pending_primary_reqs.value(req, GatoUUID()); + if (uuid.isNull()) { + qDebug() << "Got primary for service response for a request I did not make"; + return; + } + pending_primary_reqs.remove(req); + + if (list.isEmpty()) { + if (pending_primary_reqs.isEmpty()) { + emit q->servicesDiscovered(); + } + } else { + GatoHandle last_handle = 0; + + foreach (const GatoAttClient::HandleInformation &data, list) { + GatoService service; + + service.setUuid(uuid); + service.setStartHandle(data.start); + service.setEndHandle(data.end); + + services.insert(data.start, service); + service_uuids.insert(uuid); + + last_handle = data.end; + } + + // Fetch following attributes + QByteArray value = gatouuid_to_bytearray(uuid, true, false); + uint req = att->requestFindByTypeValue(last_handle + 1, 0xFFFF, GatoUUID::GattPrimaryService, value, + this, SLOT(handlePrimaryForService(uint,QList<GatoAttClient::HandleInformation>))); + pending_primary_reqs.insert(req, uuid); + } +} + +void GatoPeripheralPrivate::handleCharacteristic(uint req, const QList<GatoAttClient::AttributeData> &list) +{ + Q_Q(GatoPeripheral); + + GatoHandle service_start = pending_characteristic_reqs.value(req, 0); + if (!service_start) { + qDebug() << "Got characteristics for a request I did not make"; + return; + } + pending_characteristic_reqs.remove(req); + + Q_ASSERT(services.contains(service_start)); + GatoService &service = services[service_start]; + Q_ASSERT(service.startHandle() == service_start); + + if (list.isEmpty()) { + emit q->characteristicsDiscovered(service); + } else { + GatoHandle last_handle = 0; + + // If we are continuing a characteristic list, this means the + // last service we discovered in the previous iteration was not + // the last one, so we have to reduce its endHandle! + QList<GatoCharacteristic> cur_chars = service.characteristics(); + if (!cur_chars.isEmpty()) { + GatoCharacteristic &last = cur_chars.back(); + last.setEndHandle(list.front().handle - 1); + service.addCharacteristic(last); + } + + for (int i = 0; i < list.size(); i++) { + const GatoAttClient::AttributeData &data = list.at(i); + GatoCharacteristic characteristic = parseCharacteristicValue(data.value); + + characteristic.setStartHandle(data.handle); + if (i + 1 < list.size()) { + characteristic.setEndHandle(list.at(i + 1).handle - 1); + } else { + characteristic.setEndHandle(service.endHandle()); + } + + service.addCharacteristic(characteristic); + characteristic_to_service.insert(data.handle, service_start); + value_to_characteristic.insert(characteristic.valueHandle(), data.handle); + + last_handle = data.handle; + } + + if (last_handle >= service.endHandle()) { + // Already finished, no need to send another request + emit q->characteristicsDiscovered(service); + return; + } + + // Fetch following attributes + uint req = att->requestReadByType(last_handle + 1, service.endHandle(), GatoUUID::GattCharacteristic, + this, SLOT(handleCharacteristic(uint,QList<GatoAttClient::AttributeData>))); + pending_characteristic_reqs.insert(req, service.startHandle()); + } +} + +void GatoPeripheralPrivate::handleDescriptors(uint req, const QList<GatoAttClient::InformationData> &list) +{ + Q_Q(GatoPeripheral); + + GatoHandle char_handle = pending_descriptor_reqs.value(req); + if (!char_handle) { + qDebug() << "Got descriptor for a request I did not make"; + return; + } + pending_descriptor_reqs.remove(req); + GatoHandle service_handle = characteristic_to_service.value(char_handle); + if (!service_handle) { + qWarning() << "Unknown characteristic during descriptor discovery: " << char_handle; + return; + } + + Q_ASSERT(services.contains(service_handle)); + GatoService &service = services[service_handle]; + Q_ASSERT(service.startHandle() == service_handle); + + Q_ASSERT(service.containsCharacteristic(char_handle)); + GatoCharacteristic characteristic = service.getCharacteristic(char_handle); + + if (list.isEmpty()) { + finishSetNotifyOperations(characteristic); + emit q->descriptorsDiscovered(characteristic); + } else { + GatoHandle last_handle = 0; + + foreach (const GatoAttClient::InformationData &data, list) { + // Skip the value attribute itself. + if (data.handle == characteristic.valueHandle()) continue; + + GatoDescriptor descriptor; + + descriptor.setHandle(data.handle); + descriptor.setUuid(data.uuid); + + characteristic.addDescriptor(descriptor); + + service.addCharacteristic(characteristic); + descriptor_to_characteristic.insert(data.handle, char_handle); + + last_handle = data.handle; + } + + service.addCharacteristic(characteristic); + + if (last_handle >= characteristic.endHandle()) { + // Already finished, no need to send another request + finishSetNotifyOperations(characteristic); + emit q->descriptorsDiscovered(characteristic); + return; + } + + // Fetch following attributes + uint req = att->requestFindInformation(last_handle + 1, characteristic.endHandle(), + this, SLOT(handleDescriptors(uint,QList<GatoAttClient::InformationData>))); + pending_descriptor_reqs.insert(req, char_handle); + + } +} + +void GatoPeripheralPrivate::handleCharacteristicRead(uint req, const QByteArray &value) +{ + Q_Q(GatoPeripheral); + + GatoHandle char_handle = pending_characteristic_read_reqs.value(req); + if (!char_handle) { + qDebug() << "Got characteristics for a request I did not make"; + return; + } + pending_characteristic_read_reqs.remove(req); + GatoHandle service_handle = characteristic_to_service.value(char_handle); + if (!service_handle) { + qWarning() << "Unknown characteristic during read: " << char_handle; + return; + } + + Q_ASSERT(services.contains(service_handle)); + GatoService &service = services[service_handle]; + Q_ASSERT(service.startHandle() == service_handle); + + Q_ASSERT(service.containsCharacteristic(char_handle)); + GatoCharacteristic characteristic = service.getCharacteristic(char_handle); + + emit q->valueUpdated(characteristic, value); +} + +void GatoPeripheralPrivate::handleDescriptorRead(uint req, const QByteArray &value) +{ + Q_Q(GatoPeripheral); + + GatoHandle desc_handle = pending_descriptor_read_reqs.value(req); + if (!desc_handle) { + qDebug() << "Got characteristics for a request I did not make"; + return; + } + pending_descriptor_read_reqs.remove(req); + GatoHandle char_handle = descriptor_to_characteristic.value(desc_handle); + if (!char_handle) { + qWarning() << "Unknown characteristic during read: " << char_handle; + return; + } + GatoHandle service_handle = characteristic_to_service.value(char_handle); + if (!service_handle) { + qWarning() << "Unknown characteristic during read: " << char_handle; + return; + } + + Q_ASSERT(services.contains(service_handle)); + GatoService &service = services[service_handle]; + Q_ASSERT(service.startHandle() == service_handle); + + Q_ASSERT(service.containsCharacteristic(char_handle)); + GatoCharacteristic characteristic = service.getCharacteristic(char_handle); + + Q_ASSERT(characteristic.containsDescriptor(desc_handle)); + GatoDescriptor descriptor = characteristic.getDescriptor(desc_handle); + + emit q->descriptorValueUpdated(descriptor, value); +} + +void GatoPeripheralPrivate::handleCharacteristicWrite(uint req, bool ok) +{ + Q_UNUSED(req); + if (!ok) { + qWarning() << "Failed to write some characteristic"; + } +} + +void GatoPeripheralPrivate::handleDescriptorWrite(uint req, bool ok) +{ + Q_UNUSED(req); + if (!ok) { + qWarning() << "Failed to write some characteristic"; + } +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/gatoperipheral.h b/src/bluetooth/gatoperipheral.h new file mode 100644 index 00000000..0186d9a7 --- /dev/null +++ b/src/bluetooth/gatoperipheral.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GATOPERIPHERAL_H +#define GATOPERIPHERAL_H + +#include <QtCore/QObject> +#include "libgato_global.h" +#include "gatouuid.h" +#include "gatoaddress.h" + +QT_BEGIN_NAMESPACE + +class GatoService; +class GatoCharacteristic; +class GatoDescriptor; +class GatoPeripheralPrivate; + +class LIBGATO_EXPORT GatoPeripheral : public QObject +{ + Q_OBJECT + Q_DECLARE_PRIVATE(GatoPeripheral) + Q_ENUMS(State) + Q_ENUMS(WriteType) + Q_PROPERTY(GatoAddress address READ address) + Q_PROPERTY(QString name READ name NOTIFY nameChanged) + +public: + GatoPeripheral(const GatoAddress& addr, QObject *parent = 0); + ~GatoPeripheral(); + + enum State { + StateDisconnected, + StateConnecting, + StateConnected + }; + + enum WriteType { + WriteWithResponse = 0, + WriteWithoutResponse + }; + + State state() const; + GatoAddress address() const; + QString name() const; + QList<GatoService> services() const; + + void parseEIR(quint8 data[], int len); + bool advertisesService(const GatoUUID &uuid) const; + +public Q_SLOTS: + void connectPeripheral(); + void disconnectPeripheral(); + void discoverServices(); + void discoverServices(const QList<GatoUUID>& serviceUUIDs); + void discoverCharacteristics(const GatoService &service); + void discoverCharacteristics(const GatoService &service, const QList<GatoUUID>& characteristicUUIDs); + void discoverDescriptors(const GatoCharacteristic &characteristic); + void readValue(const GatoCharacteristic &characteristic); + void readValue(const GatoDescriptor &descriptor); + void writeValue(const GatoCharacteristic &characteristic, const QByteArray &data, WriteType type = WriteWithResponse); + void writeValue(const GatoDescriptor &descriptor, const QByteArray &data); + void setNotification(const GatoCharacteristic &characteristic, bool enabled); + +Q_SIGNALS: + void connected(); + void disconnected(); + void nameChanged(); + void servicesDiscovered(); + void characteristicsDiscovered(const GatoService &service); + void descriptorsDiscovered(const GatoCharacteristic &characteristic); + void valueUpdated(const GatoCharacteristic &characteristic, const QByteArray &value); + void descriptorValueUpdated(const GatoDescriptor &descriptor, const QByteArray &value); + +private: + GatoPeripheralPrivate *const d_ptr; +}; + +QT_END_NAMESPACE + +#endif // GATOPERIPHERAL_H diff --git a/src/bluetooth/gatoperipheral_p.h b/src/bluetooth/gatoperipheral_p.h new file mode 100644 index 00000000..9873c7bd --- /dev/null +++ b/src/bluetooth/gatoperipheral_p.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Javier de San Pedro <dev.git@javispedro.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GATOPERIPHERAL_P_H +#define GATOPERIPHERAL_P_H + +#include "gatoperipheral.h" +#include "gatoservice.h" +#include "gatocharacteristic.h" +#include "gatodescriptor.h" +#include "gatoattclient.h" + +QT_BEGIN_NAMESPACE + +class GatoPeripheralPrivate : public QObject +{ + Q_OBJECT + + Q_DECLARE_PUBLIC(GatoPeripheral) + +public: + GatoPeripheralPrivate(GatoPeripheral *parent); + ~GatoPeripheralPrivate(); + + GatoPeripheral *q_ptr; + GatoAddress addr; + QString name; + QSet<GatoUUID> service_uuids; + QMap<GatoHandle, GatoService> services; + + bool complete_name : 1; + bool complete_services : 1; + + /** Maps attribute handles to service handles. */ + QMap<GatoHandle, GatoHandle> characteristic_to_service; + QMap<GatoHandle, GatoHandle> value_to_characteristic; + QMap<GatoHandle, GatoHandle> descriptor_to_characteristic; + + GatoAttClient *att; + QMap<uint, GatoUUID> pending_primary_reqs; + QMap<uint, GatoHandle> pending_characteristic_reqs; + QMap<uint, GatoHandle> pending_characteristic_read_reqs; + QMap<uint, GatoHandle> pending_descriptor_reqs; + QMap<uint, GatoHandle> pending_descriptor_read_reqs; + + QMap<GatoHandle, bool> pending_set_notify; + + void parseEIRFlags(quint8 data[], int len); + void parseEIRUUIDs(int size, bool complete, quint8 data[], int len); + void parseName(bool complete, quint8 data[], int len); + + static GatoCharacteristic parseCharacteristicValue(const QByteArray &ba); + + static QByteArray genClientCharConfiguration(bool notification, bool indication); + + void clearServices(); + void clearServiceCharacteristics(GatoService *service); + void clearCharacteristicDescriptors(GatoCharacteristic *characteristic); + + void finishSetNotifyOperations(const GatoCharacteristic &characteristic); + +public slots: + void handleAttConnected(); + void handleAttDisconnected(); + void handleAttAttributeUpdated(GatoHandle handle, const QByteArray &value, bool confirmed); + void handlePrimary(uint req, const QList<GatoAttClient::AttributeGroupData>& list); + void handlePrimaryForService(uint req, const QList<GatoAttClient::HandleInformation>& list); + void handleCharacteristic(uint req, const QList<GatoAttClient::AttributeData> &list); + void handleDescriptors(uint req, const QList<GatoAttClient::InformationData> &list); + void handleCharacteristicRead(uint req, const QByteArray &value); + void handleDescriptorRead(uint req, const QByteArray &value); + void handleCharacteristicWrite(uint req, bool ok); + void handleDescriptorWrite(uint req, bool ok); +}; + +QT_END_NAMESPACE + +#endif // GATOPERIPHERAL_P_H diff --git a/src/bluetooth/qbluetooth.cpp b/src/bluetooth/qbluetooth.cpp index de1e5629..d9f77fac 100644 --- a/src/bluetooth/qbluetooth.cpp +++ b/src/bluetooth/qbluetooth.cpp @@ -76,6 +76,13 @@ namespace QBluetooth { */ } +/*! + \typedef QLowEnergyHandle + \relates QBluetooth + + Typedef for Bluetooth Low Energy ATT attribute handles. +*/ + Q_LOGGING_CATEGORY(QT_BT, "qt.bluetooth") Q_LOGGING_CATEGORY(QT_BT_ANDROID, "qt.bluetooth.android") Q_LOGGING_CATEGORY(QT_BT_BLUEZ, "qt.bluetooth.bluez") diff --git a/src/bluetooth/qbluetooth.h b/src/bluetooth/qbluetooth.h index 3d92e1f8..f9650385 100644 --- a/src/bluetooth/qbluetooth.h +++ b/src/bluetooth/qbluetooth.h @@ -59,6 +59,8 @@ Q_DECLARE_FLAGS(SecurityFlags, Security) Q_DECLARE_OPERATORS_FOR_FLAGS(SecurityFlags) } +typedef quint16 QLowEnergyHandle; + QT_END_NAMESPACE #endif // QBLUETOOTH_H diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent.h b/src/bluetooth/qbluetoothdevicediscoveryagent.h index 89158c53..ba675d90 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent.h @@ -111,6 +111,7 @@ private: Q_PRIVATE_SLOT(d_func(), void _q_discoveryFinished()) Q_PRIVATE_SLOT(d_func(), void _q_discoveryInterrupted(const QString &path)) Q_PRIVATE_SLOT(d_func(), void _q_PropertiesChanged(const QString &interface, const QVariantMap &changed_properties, const QStringList &invalidated_properties)) + Q_PRIVATE_SLOT(d_func(), void _q_extendedDeviceDiscoveryTimeout()) #endif }; diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp index b1d8eeeb..c7fdde05 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_bluez.cpp @@ -69,10 +69,11 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( managerBluez5(0), adapterBluez5(0), discoveryTimer(0), + useExtendedDiscovery(false), q_ptr(parent) { + Q_Q(QBluetoothDeviceDiscoveryAgent); if (isBluez5()) { - Q_Q(QBluetoothDeviceDiscoveryAgent); managerBluez5 = new OrgFreedesktopDBusObjectManagerInterface( QStringLiteral("org.bluez"), QStringLiteral("/"), @@ -84,6 +85,11 @@ QBluetoothDeviceDiscoveryAgentPrivate::QBluetoothDeviceDiscoveryAgentPrivate( } else { manager = new OrgBluezManagerInterface(QStringLiteral("org.bluez"), QStringLiteral("/"), QDBusConnection::systemBus(), parent); + QObject::connect(&extendedDiscoveryTimer, + SIGNAL(timeout()), + q, SLOT(_q_extendedDeviceDiscoveryTimeout())); + extendedDiscoveryTimer.setInterval(10000); + extendedDiscoveryTimer.setSingleShot(true); } inquiryType = QBluetoothDeviceDiscoveryAgent::GeneralUnlimitedInquiry; } @@ -169,6 +175,26 @@ void QBluetoothDeviceDiscoveryAgentPrivate::start() return; } + if (propertiesReply.value().value(QStringLiteral("Discovering")).toBool()) { + /* The discovery session is already ongoing. BTLE devices are advertised + immediately after the start of the device discovery session. Hence if the + session is already ongoing, we have just missed the BTLE device + advertisement. + + This always happens during the second device discovery run in + the current process. The first discovery doesn't have this issue. + As to why the discovery session remains active despite the previous one + being terminated is not known. This may be a bug in Bluez4. + + To workaround this issue we have to wait for two discovery + sessions cycles. + */ + qCDebug(QT_BT_BLUEZ) << "Using BTLE device discovery workaround."; + useExtendedDiscovery = true; + } else { + useExtendedDiscovery = false; + } + QDBusPendingReply<> discoveryReply = adapter->StartDiscovery(); if (discoveryReply.isError()) { delete adapter; @@ -283,6 +309,17 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_deviceFound(const QString &addres uuids.append(QBluetoothUuid(u)); device.setServiceUuids(uuids, QBluetoothDeviceInfo::DataIncomplete); device.setCached(dict.value(QStringLiteral("Cached")).toBool()); + + + /* + * Bluez v4.1 does not have extra bit which gives information if device is Bluetooth + * Low Energy device and the way to discover it is with Class property of the Bluetooth device. + * Low Energy devices do not have property Class. + */ + if (btClass == 0) + device.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); + else + device.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); for (int i = 0; i < discoveredDevices.size(); i++) { if (discoveredDevices[i].address() == device.address()) { if (discoveredDevices[i] == device) { @@ -326,7 +363,7 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFoundBluez5(const QString& dev qCDebug(QT_BT_BLUEZ) << "Discovered: " << btAddress.toString() << btName << "Num UUIDs" << device.uUIDs().count() << "total device" << discoveredDevices.count() << "cached" - << "RSSI" << device.rSSI(); + << "RSSI" << device.rSSI() << "Class" << btClass; OrgFreedesktopDBusPropertiesInterface *prop = new OrgFreedesktopDBusPropertiesInterface( QStringLiteral("org.bluez"), devicePath, QDBusConnection::systemBus(), q); @@ -337,6 +374,12 @@ void QBluetoothDeviceDiscoveryAgentPrivate::deviceFoundBluez5(const QString& dev // read information QBluetoothDeviceInfo deviceInfo(btAddress, btName, btClass); + + if (!btClass) + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); + else + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); + deviceInfo.setRssi(device.rSSI()); QList<QBluetoothUuid> uuids; foreach (const QString &u, device.uUIDs()) @@ -365,23 +408,56 @@ void QBluetoothDeviceDiscoveryAgentPrivate::_q_propertyChanged(const QString &na { qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << name << value.variant(); - if (name == QLatin1String("Discovering") && !value.variant().toBool()) { - Q_Q(QBluetoothDeviceDiscoveryAgent); - adapter->deleteLater(); - adapter = 0; - if (pendingCancel && !pendingStart) { - emit q->canceled(); - pendingCancel = false; - } else if (pendingStart) { - pendingStart = false; - pendingCancel = false; - start(); + if (name == QLatin1String("Discovering")) { + if (!value.variant().toBool()) { + Q_Q(QBluetoothDeviceDiscoveryAgent); + if (pendingCancel && !pendingStart) { + adapter->deleteLater(); + adapter = 0; + + emit q->canceled(); + pendingCancel = false; + } else if (pendingStart) { + adapter->deleteLater(); + adapter = 0; + + pendingStart = false; + pendingCancel = false; + start(); + } else { + if (useExtendedDiscovery) { + useExtendedDiscovery = false; + /* We don't use the Start/StopDiscovery combo here + Using this combo surppresses the BTLE device. + */ + extendedDiscoveryTimer.start(); + return; + } + + adapter->deleteLater(); + adapter = 0; + emit q->finished(); + } } else { - emit q->finished(); + if (extendedDiscoveryTimer.isActive()) + extendedDiscoveryTimer.stop(); } } } +void QBluetoothDeviceDiscoveryAgentPrivate::_q_extendedDeviceDiscoveryTimeout() +{ + + if (adapter) { + adapter->deleteLater(); + adapter = 0; + } + if (isActive()) { + Q_Q(QBluetoothDeviceDiscoveryAgent); + emit q->finished(); + } +} + void QBluetoothDeviceDiscoveryAgentPrivate::_q_InterfacesAdded(const QDBusObjectPath &object_path, InterfaceList interfaces_and_properties) { diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h index 168b6c0c..c4e804d3 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_p.h @@ -102,6 +102,7 @@ public: void _q_PropertiesChanged(const QString &interface, const QVariantMap &changed_properties, const QStringList &invalidated_properties); + void _q_extendedDeviceDiscoveryTimeout(); #endif private: @@ -136,6 +137,10 @@ private: void deviceFoundBluez5(const QString& devicePath); void startBluez5(); + + bool useExtendedDiscovery; + QTimer extendedDiscoveryTimer; + #elif defined(QT_QNX_BLUETOOTH) private slots: void finished(); diff --git a/src/bluetooth/qbluetoothdevicediscoveryagent_qnx.cpp b/src/bluetooth/qbluetoothdevicediscoveryagent_qnx.cpp index 3a7c1af5..771ce56f 100644 --- a/src/bluetooth/qbluetoothdevicediscoveryagent_qnx.cpp +++ b/src/bluetooth/qbluetoothdevicediscoveryagent_qnx.cpp @@ -175,11 +175,18 @@ void QBluetoothDeviceDiscoveryAgentPrivate::remoteDevicesChanged(int fd) int cod = 0; int dev_type = 0; int rssi = 0; - + bool hasGatt = false; pps_decoder_get_bool(&ppsDecoder, "paired", &paired); pps_decoder_get_int(&ppsDecoder, "cod", &cod); pps_decoder_get_int(&ppsDecoder, "dev_type", &dev_type); pps_decoder_get_int(&ppsDecoder, "rssi", &rssi); + pps_decoder_push(&ppsDecoder, "gatt_available_services"); + const char *next_service = 0; + + for (int service_count=0; pps_decoder_get_string(&ppsDecoder, 0, &next_service ) == PPS_DECODER_OK; service_count++) { + hasGatt = true; + //qBluetoothDebug() << next_service; + } pps_decoder_cleanup(&ppsDecoder); QBluetoothDeviceInfo deviceInfo(deviceAddr, deviceName, cod); @@ -202,6 +209,20 @@ void QBluetoothDeviceDiscoveryAgentPrivate::remoteDevicesChanged(int fd) m_finishedTimer.start(7000); if (!deviceAddr.isNull()) { qCDebug(QT_BT_QNX) << "Device discovered: " << deviceName << deviceAddr.toString(); + /* Looking for device type. Only Low energy devices will be added + * BT_DEVICE_TYPE_LE_PUBLIC is 0 --->LE device + * BT_DEVICE_TYPE_LE_PRIVATE is 1 ---> LE device + * BT_DEVICE_TYPE_REGULAR is 32 + * BT_DEVICE_TYPE_UNKNOWN is 255 + */ + if (dev_type == 0 || dev_type == 1) + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::LowEnergyCoreConfiguration); + else{ + if (hasGatt) + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration); + else + deviceInfo.setCoreConfigurations(QBluetoothDeviceInfo::BaseRateCoreConfiguration); + } discoveredDevices.append(deviceInfo); if (!updated) // We are not allowed to emit a signal with the updated version emit q_ptr->deviceDiscovered(discoveredDevices.last()); diff --git a/src/bluetooth/qbluetoothdeviceinfo.cpp b/src/bluetooth/qbluetoothdeviceinfo.cpp index 408222bd..d43cbab6 100644 --- a/src/bluetooth/qbluetoothdeviceinfo.cpp +++ b/src/bluetooth/qbluetoothdeviceinfo.cpp @@ -246,6 +246,16 @@ QT_BEGIN_NAMESPACE \value DataUnavailable No data is available. */ +/*! + \enum QBluetoothDeviceInfo::CoreConfiguration + + This enum describes the configuration of the device. + + \value BaseRateCoreConfiguration The device is a standard Bluetooth device. + \value BaseRateAndLowEnergyCoreConfiguration The device is a Bluetooth Smart device with support + for standard and Low Energy device. + \value LowEnergyCoreConfiguration The device is a Bluetooth Low Energy device. +*/ QBluetoothDeviceInfoPrivate::QBluetoothDeviceInfoPrivate() : valid(false), cached(false), @@ -253,7 +263,8 @@ QBluetoothDeviceInfoPrivate::QBluetoothDeviceInfoPrivate() : serviceClasses(QBluetoothDeviceInfo::NoService), majorDeviceClass(QBluetoothDeviceInfo::MiscellaneousDevice), minorDeviceClass(0), - serviceUuidsCompleteness(QBluetoothDeviceInfo::DataUnavailable) + serviceUuidsCompleteness(QBluetoothDeviceInfo::DataUnavailable), + deviceCoreConfiguration(QBluetoothDeviceInfo::BaseRateCoreConfiguration) { } @@ -362,6 +373,7 @@ QBluetoothDeviceInfo &QBluetoothDeviceInfo::operator=(const QBluetoothDeviceInfo d->serviceUuidsCompleteness = other.d_func()->serviceUuidsCompleteness; d->serviceUuids = other.d_func()->serviceUuids; d->rssi = other.d_func()->rssi; + d->deviceCoreConfiguration = other.d_func()->deviceCoreConfiguration; return *this; } @@ -393,6 +405,8 @@ bool QBluetoothDeviceInfo::operator==(const QBluetoothDeviceInfo &other) const return false; if (d->serviceUuids != other.d_func()->serviceUuids) return false; + if (d->deviceCoreConfiguration != other.d_func()->deviceCoreConfiguration) + return false; return true; } @@ -501,6 +515,32 @@ QBluetoothDeviceInfo::DataCompleteness QBluetoothDeviceInfo::serviceUuidsComplet } /*! + Sets the CoreConfigurations of the device to \a coreConfigs. This will help to make a difference + between regular and Low Energy devices. + + \sa coreConfigurations() +*/ +void QBluetoothDeviceInfo::setCoreConfigurations(QBluetoothDeviceInfo::CoreConfigurations coreConfigs) +{ + Q_D(QBluetoothDeviceInfo); + + d->deviceCoreConfiguration = coreConfigs; +} + +/*! + Returns the configuration of the device. If device configuration is not set, + basic rate device configuration will be returned. + + \sa setCoreConfigurations() +*/ +QBluetoothDeviceInfo::CoreConfigurations QBluetoothDeviceInfo::coreConfigurations() const +{ + Q_D(const QBluetoothDeviceInfo); + + return d->deviceCoreConfiguration; +} + +/*! Returns true if the QBluetoothDeviceInfo object is created from cached data. */ bool QBluetoothDeviceInfo::isCached() const diff --git a/src/bluetooth/qbluetoothdeviceinfo.h b/src/bluetooth/qbluetoothdeviceinfo.h index 13892990..f2cc1529 100644 --- a/src/bluetooth/qbluetoothdeviceinfo.h +++ b/src/bluetooth/qbluetoothdeviceinfo.h @@ -196,6 +196,13 @@ public: DataUnavailable }; + enum CoreConfiguration { + LowEnergyCoreConfiguration = 0x01, + BaseRateCoreConfiguration = 0x02, + BaseRateAndLowEnergyCoreConfiguration = 0x03 + }; + Q_DECLARE_FLAGS(CoreConfigurations, CoreConfiguration) + QBluetoothDeviceInfo(); QBluetoothDeviceInfo(const QBluetoothAddress &address, const QString &name, quint32 classOfDevice); @@ -225,6 +232,9 @@ public: QList<QBluetoothUuid> serviceUuids(DataCompleteness *completeness = 0) const; DataCompleteness serviceUuidsCompleteness() const; + void setCoreConfigurations(QBluetoothDeviceInfo::CoreConfigurations coreConfigs); + QBluetoothDeviceInfo::CoreConfigurations coreConfigurations() const; + protected: QBluetoothDeviceInfoPrivate *d_ptr; diff --git a/src/bluetooth/qbluetoothdeviceinfo_p.h b/src/bluetooth/qbluetoothdeviceinfo_p.h index b08a8fec..f5e575aa 100644 --- a/src/bluetooth/qbluetoothdeviceinfo_p.h +++ b/src/bluetooth/qbluetoothdeviceinfo_p.h @@ -69,6 +69,7 @@ public: QBluetoothDeviceInfo::DataCompleteness serviceUuidsCompleteness; QList<QBluetoothUuid> serviceUuids; + QBluetoothDeviceInfo::CoreConfigurations deviceCoreConfiguration; }; QT_END_NAMESPACE diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.cpp b/src/bluetooth/qbluetoothservicediscoveryagent.cpp index 0e185990..864b6d05 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent.cpp @@ -105,6 +105,12 @@ QT_BEGIN_NAMESPACE */ /*! + \fn QBluetoothServiceDiscoveryAgent::serviceDiscovered(const QLowEnergyServiceInfo &info) + + This signal is emitted when the Bluetooth Low Energy service described by \a info is discovered. +*/ + +/*! \fn QBluetoothServiceDiscoveryAgent::finished() This signal is emitted when the Bluetooth service discovery completes. @@ -248,7 +254,6 @@ bool QBluetoothServiceDiscoveryAgent::setRemoteAddress(const QBluetoothAddress & if (!address.isNull()) d_ptr->singleDevice = true; d_ptr->deviceAddress = address; - return true; } diff --git a/src/bluetooth/qbluetoothservicediscoveryagent.h b/src/bluetooth/qbluetoothservicediscoveryagent.h index 37c26a78..f1e754db 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent.h @@ -49,6 +49,7 @@ #include <QtBluetooth/QBluetoothServiceInfo> #include <QtBluetooth/QBluetoothUuid> #include <QtBluetooth/QBluetoothDeviceDiscoveryAgent> +#include <QtBluetooth/QLowEnergyServiceInfo> QT_BEGIN_NAMESPACE @@ -100,6 +101,7 @@ public Q_SLOTS: Q_SIGNALS: void serviceDiscovered(const QBluetoothServiceInfo &info); + void serviceDiscovered(const QLowEnergyServiceInfo &info); void finished(); void canceled(); void error(QBluetoothServiceDiscoveryAgent::Error error); diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp index 3610d7bc..2f2f26d8 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_bluez.cpp @@ -41,6 +41,7 @@ #include "qbluetoothservicediscoveryagent.h" #include "qbluetoothservicediscoveryagent_p.h" +#include "qlowenergyserviceinfo_p.h" #include "bluez/manager_p.h" #include "bluez/adapter_p.h" @@ -126,6 +127,15 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr adapter = new OrgBluezAdapterInterface(QStringLiteral("org.bluez"), reply.value().path(), QDBusConnection::systemBus()); + if (m_deviceAdapterAddress.isNull()) { + QDBusPendingReply<QVariantMap> reply = adapter->GetProperties(); + reply.waitForFinished(); + if (!reply.isError()) { + const QBluetoothAddress path_address(reply.value().value(QStringLiteral("Address")).toString()); + m_deviceAdapterAddress = path_address; + } + } + QDBusPendingReply<QDBusObjectPath> deviceObjectPath = adapter->CreateDevice(address.toString()); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(deviceObjectPath, q); @@ -287,7 +297,8 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_finishSdpScan(QBluetoothServiceD emit q->error(error); } else if (!xmlRecords.isEmpty() && discoveryState() != Inactive) { foreach (const QString &record, xmlRecords) { - const QBluetoothServiceInfo serviceInfo = parseServiceXml(record); + bool isBtleService = false; + const QBluetoothServiceInfo serviceInfo = parseServiceXml(record, &isBtleService); //apply uuidFilter if (!uuidFilter.isEmpty()) { @@ -389,17 +400,73 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_createdDevice(QDBusPendingCallWa delete adapter; adapter = 0; - QString pattern; - foreach (const QBluetoothUuid &uuid, uuidFilter) - pattern += uuid.toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')) + QLatin1Char(' '); + QVariantMap deviceProperties; + QString classType; + QDBusPendingReply<QVariantMap> deviceReply = device->GetProperties(); + deviceReply.waitForFinished(); + if (!deviceReply.isError()) { + deviceProperties = deviceReply.value(); + classType = deviceProperties.value(QStringLiteral("Class")).toString(); + } - pattern = pattern.trimmed(); - qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Discover restrictions:" << pattern; + /* + * Low Energy services in bluez are represented as the list of the paths that can be + * accessed with org.bluez.Characteristic + */ + //QDBusArgument services = v.value(QLatin1String("Services")).value<QDBusArgument>(); + + + /* + * Bluez v4.1 does not have extra bit which gives information if device is Bluetooth + * Low Energy device and the way to discover it is with Class property of the Bluetooth device. + * Low Energy devices do not have property Class. + * In case we have LE device finish service discovery; otherwise search for regular services. + */ + if (classType.isEmpty()) { //is BLE device or device properties above not retrievable + qCDebug(QT_BT_BLUEZ) << "Discovered BLE-only device. Normal service discovery skipped."; + delete device; + device = 0; - QDBusPendingReply<ServiceMap> discoverReply = device->DiscoverServices(pattern); - watcher = new QDBusPendingCallWatcher(discoverReply, q); - QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), - q, SLOT(_q_discoveredServices(QDBusPendingCallWatcher*))); + const QStringList deviceUuids = deviceProperties.value(QStringLiteral("UUIDs")).toStringList(); + for (int i = 0; i < deviceUuids.size(); i++) { + QString b = deviceUuids.at(i); + b = b.remove(QLatin1Char('{')).remove(QLatin1Char('}')); + const QBluetoothUuid uuid(b); + + qCDebug(QT_BT_BLUEZ) << "Discovered BLE service" << uuid << uuidFilter.size(); + QLowEnergyServiceInfo lowEnergyService(uuid); + lowEnergyService.setDevice(discoveredDevices.at(0)); + if (uuidFilter.isEmpty()) + emit q->serviceDiscovered(lowEnergyService); + else { + for (int j = 0; j < uuidFilter.size(); j++) { + if (uuidFilter.at(j) == uuid) + emit q->serviceDiscovered(lowEnergyService); + + } + } + + } + + if (singleDevice && deviceReply.isError()) { + error = QBluetoothServiceDiscoveryAgent::InputOutputError; + errorString = QBluetoothServiceDiscoveryAgent::tr("Unable to access device"); + emit q->error(error); + } + _q_serviceDiscoveryFinished(); + } else { + QString pattern; + foreach (const QBluetoothUuid &uuid, uuidFilter) + pattern += uuid.toString().remove(QLatin1Char('{')).remove(QLatin1Char('}')) + QLatin1Char(' '); + + pattern = pattern.trimmed(); + qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO << "Discover restrictions:" << pattern; + + QDBusPendingReply<ServiceMap> discoverReply = device->DiscoverServices(pattern); + watcher = new QDBusPendingCallWatcher(discoverReply, q); + QObject::connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)), + q, SLOT(_q_discoveredServices(QDBusPendingCallWatcher*))); + } } // Bluez 4 @@ -409,13 +476,13 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_discoveredServices(QDBusPendingC return; qCDebug(QT_BT_BLUEZ) << Q_FUNC_INFO; + Q_Q(QBluetoothServiceDiscoveryAgent); QDBusPendingReply<ServiceMap> reply = *watcher; if (reply.isError()) { qCDebug(QT_BT_BLUEZ) << "discoveredServices error: " << error << reply.error().message(); watcher->deleteLater(); if (singleDevice) { - Q_Q(QBluetoothServiceDiscoveryAgent); error = QBluetoothServiceDiscoveryAgent::UnknownError; errorString = reply.error().message(); emit q->error(error); @@ -430,8 +497,17 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_discoveredServices(QDBusPendingC qCDebug(QT_BT_BLUEZ) << "Parsing xml" << discoveredDevices.at(0).address().toString() << discoveredDevices.count() << map.count(); + + foreach (const QString &record, reply.value()) { - QBluetoothServiceInfo serviceInfo = parseServiceXml(record); + bool isBtleService = false; + QBluetoothServiceInfo serviceInfo = parseServiceXml(record, &isBtleService); + + if (isBtleService) { + qCDebug(QT_BT_BLUEZ) << "Discovered BLE services" << discoveredDevices.at(0).address().toString() + << serviceInfo.serviceName() << serviceInfo.serviceUuid() << serviceInfo.serviceClassUuids(); + continue; + } if (!serviceInfo.isValid()) continue; @@ -474,7 +550,8 @@ void QBluetoothServiceDiscoveryAgentPrivate::_q_discoveredServices(QDBusPendingC _q_serviceDiscoveryFinished(); } -QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(const QString& xmlRecord) +QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml( + const QString& xmlRecord, bool *isBtleService) { QXmlStreamReader xml(xmlRecord); @@ -491,7 +568,24 @@ QBluetoothServiceInfo QBluetoothServiceDiscoveryAgentPrivate::parseServiceXml(co if (xml.readNextStartElement()) { QVariant value = readAttributeValue(xml); - + if (isBtleService) { + if (attributeId == 1) {// Attribute with id 1 contains UUID of the service + const QBluetoothServiceInfo::Sequence seq = + value.value<QBluetoothServiceInfo::Sequence>(); + for (int i = 0; i < seq.count(); i++) { + const QBluetoothUuid uuid = seq.at(i).value<QBluetoothUuid>(); + if ((uuid.data1 & 0x1800) == 0x1800) {// We are taking into consideration that LE services starts at 0x1800 + //TODO don't emit in the middle of nowhere + Q_Q(QBluetoothServiceDiscoveryAgent); + QLowEnergyServiceInfo leService(uuid); + leService.setDevice(discoveredDevices.at(0)); + *isBtleService = true; + emit q->serviceDiscovered(leService); + break; + } + } + } + } serviceInfo.setAttribute(attributeId, value); } } @@ -532,7 +626,6 @@ void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(cons == details.value(QStringLiteral("Address")).toString()) { uuidStrings = details.value(QStringLiteral("UUIDs")).toStringList(); break; - } } } @@ -541,6 +634,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(cons } if (uuidStrings.isEmpty() || discoveredDevices.isEmpty()) { + qCWarning(QT_BT_BLUEZ) << "No uuids found for" << deviceAddress.toString(); // nothing found -> go to next uuid _q_serviceDiscoveryFinished(); return; @@ -558,6 +652,7 @@ void QBluetoothServiceDiscoveryAgentPrivate::performMinimalServiceDiscovery(cons if (!uuidFilter.isEmpty() && !uuidFilter.contains(uuid)) continue; + // TODO deal with BTLE services under Bluez 5 -> right now they are normal services QBluetoothServiceInfo serviceInfo; serviceInfo.setDevice(discoveredDevices.at(0)); diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_p.h b/src/bluetooth/qbluetoothservicediscoveryagent_p.h index 2bf4ff72..0377ce1a 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_p.h +++ b/src/bluetooth/qbluetoothservicediscoveryagent_p.h @@ -46,14 +46,17 @@ #include "qbluetoothdeviceinfo.h" #include "qbluetoothserviceinfo.h" #include "qbluetoothservicediscoveryagent.h" +#include "qlowenergyserviceinfo.h" #include <QStack> +#include <QStringList> #ifdef QT_BLUEZ_BLUETOOTH class OrgBluezManagerInterface; class OrgBluezAdapterInterface; class OrgBluezDeviceInterface; class OrgFreedesktopDBusObjectManagerInterface; + QT_BEGIN_NAMESPACE class QDBusPendingCallWatcher; class QXmlStreamReader; @@ -109,7 +112,6 @@ public: void setDiscoveryMode(QBluetoothServiceDiscoveryAgent::DiscoveryMode m) { mode = m; } QBluetoothServiceDiscoveryAgent::DiscoveryMode DiscoveryMode() { return mode; } - // private slots void _q_deviceDiscoveryFinished(); void _q_deviceDiscovered(const QBluetoothDeviceInfo &info); void _q_serviceDiscoveryFinished(); @@ -117,6 +119,12 @@ public: #ifdef QT_BLUEZ_BLUETOOTH void _q_discoveredServices(QDBusPendingCallWatcher *watcher); void _q_createdDevice(QDBusPendingCallWatcher *watcher); + //Slots below are used for discovering Bluetooth Low Energy devices. It will be used with Bluez 5.x version. + /* + void _g_discoveredGattService(); + void _q_discoverGattCharacteristics(QDBusPendingCallWatcher *watcher); + void _q_discoveredGattCharacteristic(QDBusPendingCallWatcher *watcher); + */ void _q_finishSdpScan(QBluetoothServiceDiscoveryAgent::Error errorCode, const QString &errorDescription, const QStringList &xmlRecords); @@ -140,7 +148,7 @@ private: void runSdpScan(const QBluetoothAddress &remoteAddress, const QBluetoothAddress localAddress); QVariant readAttributeValue(QXmlStreamReader &xml); - QBluetoothServiceInfo parseServiceXml(const QString& xml); + QBluetoothServiceInfo parseServiceXml(const QString& xml, bool *isBtleService); void performMinimalServiceDiscovery(const QBluetoothAddress &deviceAddress); #endif @@ -159,6 +167,7 @@ private: QSocketNotifier *rdNotifier; QTimer m_queryTimer; bool m_btInitialized; + bool m_serviceScanDone; #endif public: @@ -178,7 +187,6 @@ private: QBluetoothServiceDiscoveryAgent::DiscoveryMode mode; bool singleDevice; - #ifdef QT_BLUEZ_BLUETOOTH QString foundHostAdapterPath; OrgBluezManagerInterface *manager; diff --git a/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp b/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp index d65d638d..a2abbebc 100644 --- a/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp +++ b/src/bluetooth/qbluetoothservicediscoveryagent_qnx.cpp @@ -45,16 +45,21 @@ #include "qbluetoothdeviceinfo.h" #include "qbluetoothdevicediscoveryagent.h" +#include "qlowenergyserviceinfo_p.h" + #include <QStringList> #include "qbluetoothuuid.h" - +#include <stdio.h> +#include <unistd.h> #include <sys/pps.h> #ifdef QT_QNX_BT_BLUETOOTH #include <errno.h> #include <QPointer> #endif - #include <QFile> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> #include <QtCore/private/qcore_unix_p.h> @@ -196,33 +201,45 @@ void QBluetoothServiceDiscoveryAgentPrivate::start(const QBluetoothAddress &addr #else qCDebug(QT_BT_QNX) << "Starting Service discovery for" << address.toString(); const QString filePath = QStringLiteral("/pps/services/bluetooth/remote_devices/").append(address.toString()); + bool hasError = false; if ((m_rdfd = qt_safe_open(filePath.toLocal8Bit().constData(), O_RDONLY)) == -1) { if (QFile::exists(filePath + QStringLiteral("-00")) || - QFile::exists(filePath + QStringLiteral("-01"))) { + QFile::exists(filePath + QStringLiteral("-01"))) + { qCDebug(QT_BT_QNX) << "LE device discovered...skipping"; + QString lePath = filePath + QStringLiteral("-00"); + if ((m_rdfd = qt_safe_open(lePath.toLocal8Bit().constData(), O_RDONLY)) == -1) { + lePath = filePath + QStringLiteral("-01"); + if ((m_rdfd = qt_safe_open(lePath.toLocal8Bit().constData(), O_RDONLY)) == -1) + hasError = true; + } } else { - qCWarning(QT_BT_QNX) << "Failed to open " << filePath; - error = QBluetoothServiceDiscoveryAgent::InputOutputError; - errorString = QBluetoothServiceDiscoveryAgent::tr("Failed to open remote device file"); - q->error(error); + hasError = true; } + } + if (hasError) { + qCWarning(QT_BT_QNX) << "Failed to open " << filePath; + error = QBluetoothServiceDiscoveryAgent::InputOutputError; + errorString = QBluetoothServiceDiscoveryAgent::tr("Failed to open remote device file"); + q->error(error); _q_serviceDiscoveryFinished(); return; + } + + if (rdNotifier) + delete rdNotifier; + rdNotifier = new QSocketNotifier(m_rdfd, QSocketNotifier::Read, this); + if (rdNotifier) { + connect(rdNotifier, SIGNAL(activated(int)), this, SLOT(remoteDevicesChanged(int))); } else { - if (rdNotifier) - delete rdNotifier; - rdNotifier = new QSocketNotifier(m_rdfd, QSocketNotifier::Read, this); - if (rdNotifier) { - connect(rdNotifier, SIGNAL(activated(int)), this, SLOT(remoteDevicesChanged(int))); - } else { - qCWarning(QT_BT_QNX) << "Service Discovery: Failed to connect to rdNotifier"; - error = QBluetoothServiceDiscoveryAgent::InputOutputError; - errorString = QBluetoothServiceDiscoveryAgent::tr("Failed to connect to notifier"); - q->error(error); - _q_serviceDiscoveryFinished(); - return; - } + qWarning() << "Service Discovery: Failed to connect to rdNotifier"; + error = QBluetoothServiceDiscoveryAgent::InputOutputError; + errorString = QStringLiteral("Failed to connect to rdNotifier"); + q->error(error); + _q_serviceDiscoveryFinished(); + return; } + m_queryTimer.start(10000); ppsSendControlMessage("service_query", QStringLiteral("{\"addr\":\"%1\"}").arg(address.toString()), this); #endif @@ -258,14 +275,14 @@ void QBluetoothServiceDiscoveryAgentPrivate::remoteDevicesChanged(int fd) pps_decoder_cleanup(&ppsDecoder); return; } - + // Checking for standard Bluetooth services pps_decoder_push(&ppsDecoder, "available_services"); - + bool standardService = false; const char *next_service = 0; for (int service_count=0; pps_decoder_get_string(&ppsDecoder, 0, &next_service ) == PPS_DECODER_OK; service_count++) { if (next_service == 0) break; - + standardService = true; qCDebug(QT_BT_QNX) << Q_FUNC_INFO << "Service" << next_service; QBluetoothServiceInfo serviceInfo; @@ -314,7 +331,38 @@ void QBluetoothServiceDiscoveryAgentPrivate::remoteDevicesChanged(int fd) } } + if (standardService) // we need to pop back for the LE service scan + pps_decoder_pop(&ppsDecoder); + //Checking for Bluetooth Low Energy services + pps_decoder_push(&ppsDecoder, "gatt_available_services"); + + for (int service_count=0; pps_decoder_get_string(&ppsDecoder, 0, &next_service ) == PPS_DECODER_OK; service_count++) { + if (next_service == 0) + break; + + QString lowEnergyUuid(next_service); + qCDebug(QT_BT_QNX) << "LE Service: " << lowEnergyUuid << next_service; + QBluetoothUuid leUuid; + + //In case of custom UUIDs (e.g. Texas Instruments SenstorTag LE Device) + if ( lowEnergyUuid.length() > 4 ) { + leUuid = QBluetoothUuid(lowEnergyUuid); + } + else {// Official UUIDs are presented in 4 characters (for instance 180A) + lowEnergyUuid = QStringLiteral("0x") + lowEnergyUuid; + leUuid = QBluetoothUuid(lowEnergyUuid.toUShort(0,0)); + } + + QLowEnergyServiceInfo lowEnergyService(leUuid); + lowEnergyService.setDevice(discoveredDevices.at(0)); + qCDebug(QT_BT_QNX) << "Adding Low Energy service" << lowEnergyService.serviceUuid(); + q_ptr->serviceDiscovered(lowEnergyService); + } + pps_decoder_cleanup(&ppsDecoder); + //Deleting notifier since services will not change. + delete rdNotifier; + rdNotifier = 0; } void QBluetoothServiceDiscoveryAgentPrivate::controlReply(ppsResult result) @@ -327,6 +375,8 @@ void QBluetoothServiceDiscoveryAgentPrivate::controlReply(ppsResult result) if (!result.errorMsg.isEmpty()) { qCWarning(QT_BT_QNX) << Q_FUNC_INFO << result.errorMsg; errorString = result.errorMsg; + if (errorString == QObject::tr("Operation canceled")) + _q_serviceDiscoveryFinished(); error = QBluetoothServiceDiscoveryAgent::InputOutputError; q->error(error); } else { diff --git a/src/bluetooth/qbluetoothsocket.h b/src/bluetooth/qbluetoothsocket.h index b06330c7..9f56764f 100644 --- a/src/bluetooth/qbluetoothsocket.h +++ b/src/bluetooth/qbluetoothsocket.h @@ -162,6 +162,7 @@ protected: private: Q_PRIVATE_SLOT(d_func(), void _q_readNotify()) Q_PRIVATE_SLOT(d_func(), void _q_writeNotify()) + friend class QLowEnergyControllerPrivate; }; #ifndef QT_NO_DEBUG_STREAM diff --git a/src/bluetooth/qbluetoothsocket_bluez.cpp b/src/bluetooth/qbluetoothsocket_bluez.cpp index 57071153..1807a225 100644 --- a/src/bluetooth/qbluetoothsocket_bluez.cpp +++ b/src/bluetooth/qbluetoothsocket_bluez.cpp @@ -71,7 +71,8 @@ QBluetoothSocketPrivate::QBluetoothSocketPrivate() readNotifier(0), connectWriteNotifier(0), connecting(false), - discoveryAgent(0) + discoveryAgent(0), + isLowEnergySocket(false) { } @@ -158,7 +159,22 @@ void QBluetoothSocketPrivate::connectToService(const QBluetoothAddress &address, memset(&addr, 0, sizeof(addr)); addr.l2_family = AF_BLUETOOTH; + // This is an ugly hack but the socket class does what's needed already. + // For L2CP GATT we need a channel rather than a socket and the LE address type + // We don't want to make this public API offering for now especially since + // only Linux (of all platforms supported by this library) supports this type + // of socket. + +#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) + if (isLowEnergySocket) { + addr.l2_cid = htobs(port); + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + } else { + addr.l2_psm = port; + } +#else addr.l2_psm = port; +#endif convertAddress(address.toUInt64(), addr.l2_bdaddr.b); diff --git a/src/bluetooth/qbluetoothsocket_p.h b/src/bluetooth/qbluetoothsocket_p.h index 63309069..2c44bb0c 100644 --- a/src/bluetooth/qbluetoothsocket_p.h +++ b/src/bluetooth/qbluetoothsocket_p.h @@ -187,6 +187,11 @@ private slots: void controlReply(ppsResult result); void controlEvent(ppsResult result); #endif + +#ifdef QT_BLUEZ_BLUETOOTH +public: + bool isLowEnergySocket; +#endif }; diff --git a/src/bluetooth/qbluetoothuuid.cpp b/src/bluetooth/qbluetoothuuid.cpp index 0d20b2ec..3f8487b4 100644 --- a/src/bluetooth/qbluetoothuuid.cpp +++ b/src/bluetooth/qbluetoothuuid.cpp @@ -108,73 +108,295 @@ Q_GLOBAL_STATIC_WITH_ARGS(QUuid, baseUuid, ("{00000000-0000-1000-8000-00805F9B34 The list below explicitly states as what type each UUID shall be used. - \value ServiceDiscoveryServer Service discovery server UUID (service) - \value BrowseGroupDescriptor Browser group descriptor (service) - \value PublicBrowseGroup Public browse group service class. Services which have the public - browse group in their \l {QBluetoothServiceInfo::BrowseGroupList}{browse group list} - are discoverable by the remote devices. - \value SerialPort Serial Port Profile UUID (service & profile) - \value LANAccessUsingPPP LAN Access Profile UUID (service & profile) - \value DialupNetworking Dial-up Networking Profile UUID (service & profile) - \value IrMCSync Synchronization Profile UUID (service & profile) - \value ObexObjectPush OBEX object push service UUID (service & profile) - \value OBEXFileTransfer File Transfer Profile (FTP) UUID (service & profile) - \value IrMCSyncCommand Synchronization Profile UUID (profile) - \value Headset Headset Profile (HSP) UUID (service & profile) - \value AudioSource Advanced Audio Distribution Profile (A2DP) UUID (service) - \value AudioSink Advanced Audio Distribution Profile (A2DP) UUID (service) - \value AV_RemoteControlTarget Audio/Video Remote Control Profile (AVRCP) UUID (service) - \value AdvancedAudioDistribution Advanced Audio Distribution Profile (A2DP) UUID (profile) - \value AV_RemoteControl Audio/Video Remote Control Profile (AVRCP) UUID (service & profile) + \value ServiceDiscoveryServer Service discovery server UUID (service) + \value BrowseGroupDescriptor Browser group descriptor (service) + \value PublicBrowseGroup Public browse group service class. Services which have the public + browse group in their \l {QBluetoothServiceInfo::BrowseGroupList}{browse group list} + are discoverable by the remote devices. + \value SerialPort Serial Port Profile UUID (service & profile) + \value LANAccessUsingPPP LAN Access Profile UUID (service & profile) + \value DialupNetworking Dial-up Networking Profile UUID (service & profile) + \value IrMCSync Synchronization Profile UUID (service & profile) + \value ObexObjectPush OBEX object push service UUID (service & profile) + \value OBEXFileTransfer File Transfer Profile (FTP) UUID (service & profile) + \value IrMCSyncCommand Synchronization Profile UUID (profile) + \value Headset Headset Profile (HSP) UUID (service & profile) + \value AudioSource Advanced Audio Distribution Profile (A2DP) UUID (service) + \value AudioSink Advanced Audio Distribution Profile (A2DP) UUID (service) + \value AV_RemoteControlTarget Audio/Video Remote Control Profile (AVRCP) UUID (service) + \value AdvancedAudioDistribution Advanced Audio Distribution Profile (A2DP) UUID (profile) + \value AV_RemoteControl Audio/Video Remote Control Profile (AVRCP) UUID (service & profile) \value AV_RemoteControlController Audio/Video Remote Control Profile UUID (service) - \value HeadsetAG Headset Profile (HSP) UUID (service) - \value PANU Personal Area Networking Profile (PAN) UUID (service & profile) - \value NAP Personal Area Networking Profile (PAN) UUID (service & profile) - \value GN Personal Area Networking Profile (PAN) UUID (service & profile) - \value DirectPrinting Basic Printing Profile (BPP) UUID (service) - \value ReferencePrinting Related to Basic Printing Profile (BPP) UUID (service) - \value BasicImage Basic Imaging Profile (BIP) UUID (profile) - \value ImagingResponder Basic Imaging Profile (BIP) UUID (service) + \value HeadsetAG Headset Profile (HSP) UUID (service) + \value PANU Personal Area Networking Profile (PAN) UUID (service & profile) + \value NAP Personal Area Networking Profile (PAN) UUID (service & profile) + \value GN Personal Area Networking Profile (PAN) UUID (service & profile) + \value DirectPrinting Basic Printing Profile (BPP) UUID (service) + \value ReferencePrinting Related to Basic Printing Profile (BPP) UUID (service) + \value BasicImage Basic Imaging Profile (BIP) UUID (profile) + \value ImagingResponder Basic Imaging Profile (BIP) UUID (service) \value ImagingAutomaticArchive Basic Imaging Profile (BIP) UUID (service) \value ImagingReferenceObjects Basic Imaging Profile (BIP) UUID (service) - \value Handsfree Hands-Free Profile (HFP) UUID (service & profile) - \value HandsfreeAudioGateway Hands-Free Audio Gateway (HFP) UUID (service) + \value Handsfree Hands-Free Profile (HFP) UUID (service & profile) + \value HandsfreeAudioGateway Hands-Free Audio Gateway (HFP) UUID (service) \value DirectPrintingReferenceObjectsService Basic Printing Profile (BPP) UUID (service) - \value ReflectedUI Basic Printing Profile (BPP) UUID (service) - \value BasicPrinting Basic Printing Profile (BPP) UUID (profile) - \value PrintingStatus Basic Printing Profile (BPP) UUID (service) + \value ReflectedUI Basic Printing Profile (BPP) UUID (service) + \value BasicPrinting Basic Printing Profile (BPP) UUID (profile) + \value PrintingStatus Basic Printing Profile (BPP) UUID (service) \value HumanInterfaceDeviceService Human Interface Device (HID) UUID (service & profile) \value HardcopyCableReplacement Hardcopy Cable Replacement Profile (HCRP) (profile) - \value HCRPrint Hardcopy Cable Replacement Profile (HCRP) (service) - \value HCRScan Hardcopy Cable Replacement Profile (HCRP) (service) - \value SIMAccess SIM Access Profile (SAP) UUID (service and profile) - \value PhonebookAccessPCE Phonebook Access Profile (PBAP) UUID (service) - \value PhonebookAccessPSE Phonebook Access Profile (PBAP) UUID (service) - \value PhonebookAccess Phonebook Access Profile (PBAP) (profile) - \value HeadsetHS Headset Profile (HSP) UUID (service) - \value MessageAccessServer Message Access Profile (MAP) UUID (service) - \value MessageNotificationServer Message Access Profile (MAP) UUID (service) - \value MessageAccessProfile Message Access Profile (MAP) UUID (profile) - \value GNSS Global Navigation Satellite System UUID (profile) - \value GNSSServer Global Navigation Satellite System Server (UUID) (service) - \value Display3D 3D Synchronization Display UUID (service) - \value Glasses3D 3D Synchronization Glasses UUID (service) - \value Synchronization3D 3D Synchronization UUID (profile) - \value MPSProfile Multi-Profile Specification UUID (profile) - \value MPSService Multi-Profile Specification UUID (service) - \value PnPInformation Device Identification (DID) UUID (service & profile) - \value GenericNetworking Generic networking UUID (service) - \value GenericFileTransfer Generic file transfer UUID (service) - \value GenericAudio Generic audio UUID (service) - \value GenericTelephony Generic telephone UUID (service) - \value VideoSource Video Distribution Profile (VDP) UUID (service) - \value VideoSink Video Distribution Profile (VDP) UUID (service) - \value VideoDistribution Video Distribution Profile (VDP) UUID (profile) - \value HDP Health Device Profile (HDP) UUID (profile) - \value HDPSource Health Device Profile Source (HDP) UUID (service) - \value HDPSink Health Device Profile Sink (HDP) UUID (service) - - \sa QBluetoothServiceInfo::ServiceClassIds + \value HCRPrint Hardcopy Cable Replacement Profile (HCRP) (service) + \value HCRScan Hardcopy Cable Replacement Profile (HCRP) (service) + \value SIMAccess SIM Access Profile (SAP) UUID (service and profile) + \value PhonebookAccessPCE Phonebook Access Profile (PBAP) UUID (service) + \value PhonebookAccessPSE Phonebook Access Profile (PBAP) UUID (service) + \value PhonebookAccess Phonebook Access Profile (PBAP) (profile) + \value HeadsetHS Headset Profile (HSP) UUID (service) + \value MessageAccessServer Message Access Profile (MAP) UUID (service) + \value MessageNotificationServer Message Access Profile (MAP) UUID (service) + \value MessageAccessProfile Message Access Profile (MAP) UUID (profile) + \value GNSS Global Navigation Satellite System UUID (profile) + \value GNSSServer Global Navigation Satellite System Server (UUID) (service) + \value Display3D 3D Synchronization Display UUID (service) + \value Glasses3D 3D Synchronization Glasses UUID (service) + \value Synchronization3D 3D Synchronization UUID (profile) + \value MPSProfile Multi-Profile Specification UUID (profile) + \value MPSService Multi-Profile Specification UUID (service) + \value PnPInformation Device Identification (DID) UUID (service & profile) + \value GenericNetworking Generic networking UUID (service) + \value GenericFileTransfer Generic file transfer UUID (service) + \value GenericAudio Generic audio UUID (service) + \value GenericTelephony Generic telephone UUID (service) + \value VideoSource Video Distribution Profile (VDP) UUID (service) + \value VideoSink Video Distribution Profile (VDP) UUID (service) + \value VideoDistribution Video Distribution Profile (VDP) UUID (profile) + \value HDP Health Device Profile (HDP) UUID (profile) + \value HDPSource Health Device Profile Source (HDP) UUID (service) + \value HDPSink Health Device Profile Sink (HDP) UUID (service) + \value GenericAccess Generic access service for Bluetooth Low Energy devices UUID (service). + It contains generic information about the device. All available Characteristics are readonly. + \value GenericAttribute + \value ImmediateAlert Immediate Alert UUID (service). The service exposes a control point to allow a peer + device to cause the device to immediately alert. + \value LinkLoss Link Loss UUID (service). The service defines behavior when a link is lost between two devices. + \value TxPower Transmission Power UUID (service). The service exposes a device’s current + transmit power level when in a connection. + \value CurrentTimeService Current Time UUID (service). The service defines how the current time can be exposed using + the Generic Attribute Profile (GATT). + \value ReferenceTimeUpdateService Reference Time update UUID (service). The service defines how a client can request + an update from a reference time source from a time server. + \value NextDSTChangeService Next DST change UUID (service). The service defines how the information about an + upcoming DST change can be exposed. + \value Glucose Glucose UUID (service). The service exposes glucose and other data from a glucose sensor + for use in consumer and professional healthcare applications. + \value HealthThermometer Health Thermometer UUID (service). The Health Thermometer service exposes temperature + and other data from a thermometer intended for healthcare and fitness applications. + \value DeviceInformation Device Information UUID (service). The Device Information Service exposes manufacturer + and/or vendor information about a device. + \value HeartRate Heart Rate UUID (service). The service exposes the heart rate and other data from a + Heart Rate Sensor intended for fitness applications. + \value PhoneAlertStatusService Phone Alert Status UUID (service). The service exposes the phone alert status when + in a connection. + \value BatteryService Battery UUID (service). The Battery Service exposes the state of a battery within a device. + \value BloodPressure Blood Pressure UUID (service). The service exposes blood pressure and other data from a blood pressure + monitor intended for healthcare applications. + \value AlertNotificationService Alert Notification UUID (service). The Alert Notification service exposes alert + information on a device. + \value HumanInterfaceDevice Human Interface UUID (service). The service exposes the HID reports and other HID data + intended for HID Hosts and HID Devices. + \value ScanParameters Scan Parameters UUID (service). The Scan Parameters Service enables a GATT Server device + to expose a characteristic for the GATT Client to write its scan interval and scan window + on the GATT Server device. + \value RunningSpeedAndCadence Runnung Speed and Cadence UUID (service). The service exposes speed, cadence and other + data from a Running Speed and Cadence Sensor intended for fitness applications. + \value CyclingSpeedAndCadence Cycling Speed and Cadence UUID (service). The service exposes speed-related and + cadence-related data from a Cycling Speed and Cadence sensor intended for fitness + applications. + \value CyclingPower Cycling Speed UUID (service). The service exposes power- and force-related data and + optionally speed- and cadence-related data from a Cycling Power + sensor intended for sports and fitness applications. + \value LocationAndNavigation Location Navigation UUID (service). The service exposes location and navigation-related + data from a Location and Navigation sensor intended for outdoor activity applications. +*/ + +/*! + \enum QBluetoothUuid::CharacteristicType + + This enum is a convienience type for Bluetooth low energy service characteristics class UUIDs. Values of this type + will be implicitly converted into a QBluetoothUuid when necessary. + + \value AlertCategoryID Categories of alerts/messages. + \value AlertCategoryIDBitMask Categories of alerts/messages. + \value AlertLevel The level of an alert a device is to sound. + If this level is changed while the alert is being sounded, + the new level should take effect. + \value AlertNotificationControlPoint Control point of the Alert Notification server. + Client can write the command here to request the several + functions toward the server. + \value AlertStatus The Alert Status characteristic defines the Status of alert. + \value Appearance The external appearance of this device. The values are composed + of a category (10-bits) and sub-categories (6-bits). + \value BatteryLevel The current charge level of a battery. 100% represents fully charged + while 0% represents fully discharged. + \value BloodPressureFeature The Blood Pressure Feature characteristic is used to describe the supported + features of the Blood Pressure Sensor. + \value BloodPressureMeasurement The Blood Pressure Measurement characteristic is a variable length structure + containing a Flags field, a Blood Pressure Measurement Compound Value field, + and contains additional fields such as Time Stamp, Pulse Rate and User ID + as determined by the contents of the Flags field. + \value BodySensorLocation + \value BootKeyboardInputReport The Boot Keyboard Input Report characteristic is used to transfer fixed format + and length Input Report data between a HID Host operating in Boot Protocol Mode + and a HID Service corresponding to a boot keyboard. + \value BootKeyboardOutputReport The Boot Keyboard Output Report characteristic is used to transfer fixed format + and length Output Report data between a HID Host operating in Boot Protocol Mode + and a HID Service corresponding to a boot keyboard. + \value BootMouseInputReport The Boot Mouse Input Report characteristic is used to transfer fixed format and + length Input Report data between a HID Host operating in Boot Protocol Mode and + a HID Service corresponding to a boot mouse. + \value CSCFeature The CSC (Cycling Speed and Cadence) Feature characteristic is used to describe + the supported features of the Server. + \value CSCMeasurement The CSC Measurement characteristic (CSC refers to Cycling Speed and Cadence) + is a variable length structure containing a Flags field and, based on the contents + of the Flags field, may contain one or more additional fields as shown in the tables + below. + \value CurrentTime + \value CyclingPowerControlPoint The Cycling Power Control Point characteristic is used to request a specific function + to be executed on the receiving device. + \value CyclingPowerFeature The CP Feature characteristic is used to report a list of features supported by + the device. + \value CyclingPowerMeasurement The Cycling Power Measurement characteristic is a variable length structure containing + a Flags field, an Instantaneous Power field and, based on the contents of the Flags + field, may contain one or more additional fields as shown in the table below. + \value CyclingPowerVector The Cycling Power Vector characteristic is a variable length structure containing + a Flags fieldand based on the contents of the Flags field, may contain one or more + additional fields as shown in the table below. + \value DateTime The Date Time characteristic is used to represent time. + \value DayDateTime + \value DayOfWeek + \value DeviceName + \value DSTOffset + \value ExactTime256 + \value FirmwareRevisionString The value of this characteristic is a UTF-8 string representing the firmware revision + for the firmware within the device. + \value GlucoseFeature The Glucose Feature characteristic is used to describe the supported features + of the Server. When read, the Glucose Feature characteristic returns a value + that is used by a Client to determine the supported features of the Server. + \value GlucoseMeasurement The Glucose Measurement characteristic is a variable length structure containing + a Flags field, a Sequence Number field, a Base Time field and, based upon the contents + of the Flags field, may contain a Time Offset field, Glucose Concentration field, + Type-Sample Location field and a Sensor Status Annunciation field. + \value GlucoseMeasurementContext + \value HardwareRevisionString The value of this characteristic is a UTF-8 string representing the hardware revision + for the hardware within the device. + \value HeartRateControlPoint + \value HeartRateMeasurement + \value HIDControlPoint The HID Control Point characteristic is a control-point attribute that defines the + HID Commands when written. + \value HIDInformation The HID Information Characteristic returns the HID attributes when read. + \value IEEE1107320601RegulatoryCertificationDataList The value of the characteristic is an opaque structure listing + various regulatory and/or certification compliance items to which the device + claims adherence. + \value IntermediateCuffPressure This characteristic has the same format as the Blood Pressure Measurement + characteristic. + \value IntermediateTemperature The Intermediate Temperature characteristic has the same format as the + Temperature Measurement characteristic + \value LNControlPoint The LN Control Point characteristic is used to request a specific function + to be executed on the receiving device. + \value LNFeature The LN Feature characteristic is used to report a list of features supported + by the device. + \value LocalTimeInformation + \value LocationAndSpeed The Location and Speed characteristic is a variable length structure containing + a Flags field and, based on the contents of the Flags field, may contain a combination + of data fields. + \value ManufacturerNameString The value of this characteristic is a UTF-8 string representing the name of the + manufacturer of the device. + \value MeasurementInterval The Measurement Interval characteristic defines the time between measurements. + \value ModelNumberString The value of this characteristic is a UTF-8 string representing the model number + assigned by the device vendor. + \value Navigation The Navigation characteristic is a variable length structure containing a Flags field, + a Bearing field, a Heading field and, based on the contents of the Flags field. + \value NewAlert This characteristic defines the category of the alert and how many new alerts of + that category have occurred in the server device. + \value PeripheralPreferredConnectionParameters + \value PeripheralPrivacyFlag + \value PnPID The PnP_ID characteristic returns its value when read using the GATT Characteristic + Value Read procedure. + \value PositionQuality The Position Quality characteristic is a variable length structure containing a + Flags field and at least one of the optional data + \value ProtocolMode The Protocol Mode characteristic is used to expose the current protocol mode of + the HID Service with which it is associated, or to set the desired protocol + mode of the HID Service. + \value ReconnectionAddress The Information included in this page is informative. The normative descriptions + are contained in the applicable specification. + \value RecordAccessControlPoint This control point is used with a service to provide basic management functionality + for the Glucose Sensor patient record database. + \value ReferenceTimeInformation + \value Report The Report characteristic is used to exchange data between a HID Device and a HID Host. + \value ReportMap Only a single instance of this characteristic exists as part of a HID Service. + \value RingerControlPoint The Ringer Control Point characteristic defines the Control Point of Ringer. + \value RingerSetting The Ringer Setting characteristic defines the Setting of the Ringer. + \value RSCFeature The RSC (Running Speed and Cadence) Feature characteristic is used to describe the + supported features of the Server. + \value RSCMeasurement RSC refers to Running Speed and Cadence. + \value SCControlPoint The SC Control Point characteristic is used to request a specific function to be + executed on the receiving device. + \value ScanIntervalWindow The Scan Interval Window characteristic is used to store the scan parameters of + the GATT Client. + \value ScanRefresh The Scan Refresh characteristic is used to notify the Client that the Server + requires the Scan Interval Window characteristic to be written with the latest + values upon notification. + \value SensorLocation The Sensor Location characteristic is used to expose the location of the sensor. + \value SerialNumberString The value of this characteristic is a variable-length UTF-8 string representing + the serial number for a particular instance of the device. + \value ServiceChanged + \value SoftwareRevisionString The value of this characteristic is a UTF-8 string representing the software + revision for the software within the device. + \value SupportedNewAlertCategory Category that the server supports for new alert. + \value SupportedUnreadAlertCategory Category that the server supports for unread alert. + \value SystemID If the system ID is based of a Bluetooth Device Address with a Company Identifier + (OUI) is 0x123456 and the Company Assigned Identifier is 0x9ABCDE, then the System + Identifier is required to be 0x123456FFFE9ABCDE. + \value TemperatureMeasurement The Temperature Measurement characteristic is a variable length structure containing + a Flags field, a Temperature Measurement Value field and, based upon the contents of + the Flags field, optionally a Time Stamp field and/or a Temperature Type field. + \value TemperatureType The Temperature Type characteristic is an enumeration that indicates where the + temperature was measured. + \value TimeAccuracy + \value TimeSource + \value TimeUpdateControlPoint + \value TimeUpdateState + \value TimeWithDST + \value TimeZone + \value TxPowerLevel The value of the characteristic is a signed 8 bit integer that has a fixed point + exponent of 0. + \value UnreadAlertStatus This characteristic shows how many numbers of unread alerts exist in the specific + category in the device. +*/ + +/*! + \enum QBluetoothUuid::DescriptorType + + Descriptors are attributes that describe Bluetooth Low Energy characteristic values. + + This enum is a convienience type for descriptor class UUIDs. Values of this type + will be implicitly converted into a QBluetoothUuid when necessary. + + \value CharacteristicExtendedProperties Descriptor defines additional Characteristic Properties. + \value CharacteristicUserDescription Descriptor provides a textual user description for a characteristic value. + \value ClientCharacteristicConfiguration Descriptor defines how the characteristic may be configured by a specific client. + \value ServerCharacteristicConfiguration Descriptor defines how the characteristic descriptor is associated with may be + configured for the server. + \value CharacteristicPresentationFormat Descriptor defines the format of the Characteristic Value. + \value CharacteristicAggregateFormat Descriptor defines the format of an aggregated Characteristic Value. + \value ValidRange descriptor is used for defining the range of a characteristics. + Two mandatory fields are contained (upper and lower bounds) which define the range. + \value ExternalReportReference Allows a HID Host to map information from the Report Map characteristic value for + Input Report, Output Report or Feature Report data to the Characteristic UUID of + external service characteristics used to transfer the associated data. + \value ReportReference Mapping information in the form of a Report ID and Report Type which maps the + current parent characteristic to the Report ID(s) and Report Type (s) defined + within the Report Map characteristic. */ /*! @@ -206,6 +428,27 @@ QBluetoothUuid::QBluetoothUuid(ServiceClassUuid uuid) } /*! + Constructs a new Bluetooth UUID from the characteristic type \a uuid. +*/ +QBluetoothUuid::QBluetoothUuid(CharacteristicType uuid) +: QUuid(uuid, baseUuid()->data2, baseUuid()->data3, baseUuid()->data4[0], baseUuid()->data4[1], + baseUuid()->data4[2], baseUuid()->data4[3], baseUuid()->data4[4], baseUuid()->data4[5], + baseUuid()->data4[6], baseUuid()->data4[7]) +{ +} + +/*! + Constructs a new Bluetooth UUID from the descriptor type \a uuid. +*/ +QBluetoothUuid::QBluetoothUuid(DescriptorType uuid) + : QUuid(uuid, baseUuid()->data2, baseUuid()->data3, baseUuid()->data4[0], baseUuid()->data4[1], + baseUuid()->data4[2], baseUuid()->data4[3], baseUuid()->data4[4], baseUuid()->data4[5], + baseUuid()->data4[6], baseUuid()->data4[7]) +{ + +} + +/*! Constructs a new Bluetooth UUID from the 16 bit \a uuid. */ QBluetoothUuid::QBluetoothUuid(quint16 uuid) @@ -227,6 +470,8 @@ QBluetoothUuid::QBluetoothUuid(quint32 uuid) /*! Constructs a new Bluetooth UUID from the 128 bit \a uuid. + + Note that \a uuid must be in big endian order. */ QBluetoothUuid::QBluetoothUuid(quint128 uuid) { @@ -437,6 +682,28 @@ QString QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid uu case QBluetoothUuid::HDP: return QBluetoothServiceDiscoveryAgent::tr("Health Device"); case QBluetoothUuid::HDPSource: return QBluetoothServiceDiscoveryAgent::tr("Health Device Source"); case QBluetoothUuid::HDPSink: return QBluetoothServiceDiscoveryAgent::tr("Health Device Sink"); + case QBluetoothUuid::GenericAccess: return QBluetoothServiceDiscoveryAgent::tr("Generic Access"); + case QBluetoothUuid::GenericAttribute: return QBluetoothServiceDiscoveryAgent::tr("Generic Attribute"); + case QBluetoothUuid::ImmediateAlert: return QBluetoothServiceDiscoveryAgent::tr("Immediate Alert"); + case QBluetoothUuid::LinkLoss: return QBluetoothServiceDiscoveryAgent::tr("Link Loss"); + case QBluetoothUuid::TxPower: return QBluetoothServiceDiscoveryAgent::tr("Tx Power"); + case QBluetoothUuid::CurrentTimeService: return QBluetoothServiceDiscoveryAgent::tr("Current Time Service"); + case QBluetoothUuid::ReferenceTimeUpdateService: return QBluetoothServiceDiscoveryAgent::tr("Reference Time Update Service"); + case QBluetoothUuid::NextDSTChangeService: return QBluetoothServiceDiscoveryAgent::tr("Next DST Change Service"); + case QBluetoothUuid::Glucose: return QBluetoothServiceDiscoveryAgent::tr("Glucose"); + case QBluetoothUuid::HealthThermometer: return QBluetoothServiceDiscoveryAgent::tr("Health Thermometer"); + case QBluetoothUuid::DeviceInformation: return QBluetoothServiceDiscoveryAgent::tr("Device Information"); + case QBluetoothUuid::HeartRate: return QBluetoothServiceDiscoveryAgent::tr("Heart Rate"); + case QBluetoothUuid::PhoneAlertStatusService: return QBluetoothServiceDiscoveryAgent::tr("Phone Alert Status Service"); + case QBluetoothUuid::BatteryService: return QBluetoothServiceDiscoveryAgent::tr("Battery Service"); + case QBluetoothUuid::BloodPressure: return QBluetoothServiceDiscoveryAgent::tr("Blood Pressure"); + case QBluetoothUuid::AlertNotificationService: return QBluetoothServiceDiscoveryAgent::tr("Alert Notification Service"); + case QBluetoothUuid::HumanInterfaceDevice: return QBluetoothServiceDiscoveryAgent::tr("Human Interface Device"); + case QBluetoothUuid::ScanParameters: return QBluetoothServiceDiscoveryAgent::tr("Scan Parameters"); + case QBluetoothUuid::RunningSpeedAndCadence: return QBluetoothServiceDiscoveryAgent::tr("Running Speed and Cadance"); + case QBluetoothUuid::CyclingSpeedAndCadence: return QBluetoothServiceDiscoveryAgent::tr("Cycling Speed and Cadance"); + case QBluetoothUuid::CyclingPower: return QBluetoothServiceDiscoveryAgent::tr("Cycling Power"); + case QBluetoothUuid::LocationAndNavigation: return QBluetoothServiceDiscoveryAgent::tr("Location and Navigation"); default: break; } @@ -451,7 +718,7 @@ QString QBluetoothUuid::serviceClassToString(QBluetoothUuid::ServiceClassUuid uu \sa QBluetoothUuid::ProtocolUuid - \since Qt 5.4 + \since 5.4 */ QString QBluetoothUuid::protocolToString(QBluetoothUuid::ProtocolUuid uuid) { @@ -489,6 +756,161 @@ QString QBluetoothUuid::protocolToString(QBluetoothUuid::ProtocolUuid uuid) } /*! + Returns a human-readable and translated name for the given characteristic type + represented by \a uuid. + + \sa QBluetoothUuid::CharacteristicType + + \since 5.4 +*/ +QString QBluetoothUuid::characteristicToString(CharacteristicType uuid) +{ + switch (uuid) { + case QBluetoothUuid::DeviceName: return QBluetoothServiceDiscoveryAgent::tr("GAP Device Name"); + case QBluetoothUuid::Appearance: return QBluetoothServiceDiscoveryAgent::tr("GAP Appearance"); + case QBluetoothUuid::PeripheralPrivacyFlag: + return QBluetoothServiceDiscoveryAgent::tr("GAP Peripheral Privacy Flag"); + case QBluetoothUuid::ReconnectionAddress: + return QBluetoothServiceDiscoveryAgent::tr("GAP Reconnection Address"); + case QBluetoothUuid::PeripheralPreferredConnectionParameters: + return QBluetoothServiceDiscoveryAgent::tr("GAP Peripheral Preferred Connection Parameters"); + case QBluetoothUuid::ServiceChanged: return QBluetoothServiceDiscoveryAgent::tr("GATT Service Changed"); + case QBluetoothUuid::AlertLevel: return QBluetoothServiceDiscoveryAgent::tr("Alert Level"); + case QBluetoothUuid::TxPowerLevel: return QBluetoothServiceDiscoveryAgent::tr("TX Power"); + case QBluetoothUuid::DateTime: return QBluetoothServiceDiscoveryAgent::tr("Date Time"); + case QBluetoothUuid::DayOfWeek: return QBluetoothServiceDiscoveryAgent::tr("Day Of Week"); + case QBluetoothUuid::DayDateTime: return QBluetoothServiceDiscoveryAgent::tr("Day Date Time"); + case QBluetoothUuid::ExactTime256: return QBluetoothServiceDiscoveryAgent::tr("Exact Time 256"); + case QBluetoothUuid::DSTOffset: return QBluetoothServiceDiscoveryAgent::tr("DST Offset"); + case QBluetoothUuid::TimeZone: return QBluetoothServiceDiscoveryAgent::tr("Time Zone"); + case QBluetoothUuid::LocalTimeInformation: + return QBluetoothServiceDiscoveryAgent::tr("Local Time Information"); + case QBluetoothUuid::TimeWithDST: return QBluetoothServiceDiscoveryAgent::tr("Time With DST"); + case QBluetoothUuid::TimeAccuracy: return QBluetoothServiceDiscoveryAgent::tr("Time Accuracy"); + case QBluetoothUuid::TimeSource: return QBluetoothServiceDiscoveryAgent::tr("Time Source"); + case QBluetoothUuid::ReferenceTimeInformation: + return QBluetoothServiceDiscoveryAgent::tr("Reference Time Information"); + case QBluetoothUuid::TimeUpdateControlPoint: + return QBluetoothServiceDiscoveryAgent::tr("Time Update Control Point"); + case QBluetoothUuid::TimeUpdateState: return QBluetoothServiceDiscoveryAgent::tr("Time Update State"); + case QBluetoothUuid::GlucoseMeasurement: return QBluetoothServiceDiscoveryAgent::tr("Glucose Measurement"); + case QBluetoothUuid::BatteryLevel: return QBluetoothServiceDiscoveryAgent::tr("Battery Level"); + case QBluetoothUuid::TemperatureMeasurement: + return QBluetoothServiceDiscoveryAgent::tr("Temperature Measurement"); + case QBluetoothUuid::TemperatureType: return QBluetoothServiceDiscoveryAgent::tr("Temperature Type"); + case QBluetoothUuid::IntermediateTemperature: + return QBluetoothServiceDiscoveryAgent::tr("Intermediate Temperature"); + case QBluetoothUuid::MeasurementInterval: return QBluetoothServiceDiscoveryAgent::tr("Measurement Interval"); + case QBluetoothUuid::BootKeyboardInputReport: return QBluetoothServiceDiscoveryAgent::tr("Boot Keyboard Input Report"); + case QBluetoothUuid::SystemID: return QBluetoothServiceDiscoveryAgent::tr("System ID"); + case QBluetoothUuid::ModelNumberString: return QBluetoothServiceDiscoveryAgent::tr("Model Number String"); + case QBluetoothUuid::SerialNumberString: return QBluetoothServiceDiscoveryAgent::tr("Serial Number String"); + case QBluetoothUuid::FirmwareRevisionString: return QBluetoothServiceDiscoveryAgent::tr("Firmware Revision String"); + case QBluetoothUuid::HardwareRevisionString: return QBluetoothServiceDiscoveryAgent::tr("Hardware Revision String"); + case QBluetoothUuid::SoftwareRevisionString: return QBluetoothServiceDiscoveryAgent::tr("Software Revision String"); + case QBluetoothUuid::ManufacturerNameString: return QBluetoothServiceDiscoveryAgent::tr("Manufacturer Name String"); + case QBluetoothUuid::IEEE1107320601RegulatoryCertificationDataList: + return QBluetoothServiceDiscoveryAgent::tr("IEEE 11073 20601 Regulatory Certification Data List"); + case QBluetoothUuid::CurrentTime: return QBluetoothServiceDiscoveryAgent::tr("Current Time"); + case QBluetoothUuid::ScanRefresh: return QBluetoothServiceDiscoveryAgent::tr("Scan Refresh"); + case QBluetoothUuid::BootKeyboardOutputReport: + return QBluetoothServiceDiscoveryAgent::tr("Boot Keyboard Output Report"); + case QBluetoothUuid::BootMouseInputReport: return QBluetoothServiceDiscoveryAgent::tr("Boot Mouse Input Report"); + case QBluetoothUuid::GlucoseMeasurementContext: + return QBluetoothServiceDiscoveryAgent::tr("Glucose Measurement Context"); + case QBluetoothUuid::BloodPressureMeasurement: + return QBluetoothServiceDiscoveryAgent::tr("Blood Pressure Measurement"); + case QBluetoothUuid::IntermediateCuffPressure: + return QBluetoothServiceDiscoveryAgent::tr("Intermediate Cuff Pressure"); + case QBluetoothUuid::HeartRateMeasurement: return QBluetoothServiceDiscoveryAgent::tr("Heart Rate Measurement"); + case QBluetoothUuid::BodySensorLocation: return QBluetoothServiceDiscoveryAgent::tr("Body Sensor Location"); + case QBluetoothUuid::HeartRateControlPoint: return QBluetoothServiceDiscoveryAgent::tr("Heart Rate Control Point"); + case QBluetoothUuid::AlertStatus: return QBluetoothServiceDiscoveryAgent::tr("Alert Status"); + case QBluetoothUuid::RingerControlPoint: return QBluetoothServiceDiscoveryAgent::tr("Ringer Control Point"); + case QBluetoothUuid::RingerSetting: return QBluetoothServiceDiscoveryAgent::tr("Ringer Setting"); + case QBluetoothUuid::AlertCategoryIDBitMask: + return QBluetoothServiceDiscoveryAgent::tr("Alert Category ID Bit Mask"); + case QBluetoothUuid::AlertCategoryID: return QBluetoothServiceDiscoveryAgent::tr("Alert Category ID"); + case QBluetoothUuid::AlertNotificationControlPoint: + return QBluetoothServiceDiscoveryAgent::tr("Alert Notification Control Point"); + case QBluetoothUuid::UnreadAlertStatus: return QBluetoothServiceDiscoveryAgent::tr("Unread Alert Status"); + case QBluetoothUuid::NewAlert: return QBluetoothServiceDiscoveryAgent::tr("New Alert"); + case QBluetoothUuid::SupportedNewAlertCategory: + return QBluetoothServiceDiscoveryAgent::tr("Supported New Alert Category"); + case QBluetoothUuid::SupportedUnreadAlertCategory: + return QBluetoothServiceDiscoveryAgent::tr("Supported Unread Alert Category"); + case QBluetoothUuid::BloodPressureFeature: return QBluetoothServiceDiscoveryAgent::tr("Blood Pressure Feature"); + case QBluetoothUuid::HIDInformation: return QBluetoothServiceDiscoveryAgent::tr("HID Information"); + case QBluetoothUuid::ReportMap: return QBluetoothServiceDiscoveryAgent::tr("Report Map"); + case QBluetoothUuid::HIDControlPoint: return QBluetoothServiceDiscoveryAgent::tr("HID Control Point"); + case QBluetoothUuid::Report: return QBluetoothServiceDiscoveryAgent::tr("Report"); + case QBluetoothUuid::ProtocolMode: return QBluetoothServiceDiscoveryAgent::tr("Protocol Mode"); + case QBluetoothUuid::ScanIntervalWindow: return QBluetoothServiceDiscoveryAgent::tr("Scan Interval Window"); + case QBluetoothUuid::PnPID: return QBluetoothServiceDiscoveryAgent::tr("PnP ID"); + case QBluetoothUuid::GlucoseFeature: return QBluetoothServiceDiscoveryAgent::tr("Glucose Feature"); + case QBluetoothUuid::RecordAccessControlPoint: + return QBluetoothServiceDiscoveryAgent::tr("Record Access Control Point"); + case QBluetoothUuid::RSCMeasurement: return QBluetoothServiceDiscoveryAgent::tr("RSC Measurement"); + case QBluetoothUuid::RSCFeature: return QBluetoothServiceDiscoveryAgent::tr("RSC Feature"); + case QBluetoothUuid::SCControlPoint: return QBluetoothServiceDiscoveryAgent::tr("SC Control Point"); + case QBluetoothUuid::CSCMeasurement: return QBluetoothServiceDiscoveryAgent::tr("CSC Measurement"); + case QBluetoothUuid::CSCFeature: return QBluetoothServiceDiscoveryAgent::tr("CSC Feature"); + case QBluetoothUuid::SensorLocation: return QBluetoothServiceDiscoveryAgent::tr("Sensor Location"); + case QBluetoothUuid::CyclingPowerMeasurement: + return QBluetoothServiceDiscoveryAgent::tr("Cycling Power Measurement"); + case QBluetoothUuid::CyclingPowerVector: return QBluetoothServiceDiscoveryAgent::tr("Cycling Power Vector"); + case QBluetoothUuid::CyclingPowerFeature: return QBluetoothServiceDiscoveryAgent::tr("Cycling Power Feature"); + case QBluetoothUuid::CyclingPowerControlPoint: + return QBluetoothServiceDiscoveryAgent::tr("Cycling Power COntrol Point"); + case QBluetoothUuid::LocationAndSpeed: return QBluetoothServiceDiscoveryAgent::tr("Location And Speed"); + case QBluetoothUuid::Navigation: return QBluetoothServiceDiscoveryAgent::tr("Navigation"); + case QBluetoothUuid::PositionQuality: return QBluetoothServiceDiscoveryAgent::tr("Position Quality"); + case QBluetoothUuid::LNFeature: return QBluetoothServiceDiscoveryAgent::tr("LN Feature"); + case QBluetoothUuid::LNControlPoint: return QBluetoothServiceDiscoveryAgent::tr("LN Control Point"); + default: + break; + } + + return QString(); +} + +/*! + Returns a human-readable and translated name for the given descriptor type + represented by \a uuid. + + \sa QBluetoothUuid::CharacteristicType + + \since 5.4 +*/ +QString QBluetoothUuid::descriptorToString(QBluetoothUuid::DescriptorType uuid) +{ + switch (uuid) { + case QBluetoothUuid::CharacteristicExtendedProperties: + return QBluetoothServiceDiscoveryAgent::tr("Characteristic Extended Properties"); + case QBluetoothUuid::CharacteristicUserDescription: + return QBluetoothServiceDiscoveryAgent::tr("Characteristic User Description"); + case QBluetoothUuid::ClientCharacteristicConfiguration: + return QBluetoothServiceDiscoveryAgent::tr("Client Characteristic Configuration"); + case QBluetoothUuid::ServerCharacteristicConfiguration: + return QBluetoothServiceDiscoveryAgent::tr("Server Characteristic Configuratio"); + case QBluetoothUuid::CharacteristicPresentationFormat: + return QBluetoothServiceDiscoveryAgent::tr("Characteristic Presentation Format"); + case QBluetoothUuid::CharacteristicAggregateFormat: + return QBluetoothServiceDiscoveryAgent::tr("Characteristic Aggregate Format"); + case QBluetoothUuid::ValidRange: + return QBluetoothServiceDiscoveryAgent::tr("Valid Range"); + case QBluetoothUuid::ExternalReportReference: + return QBluetoothServiceDiscoveryAgent::tr("External Report Reference"); + case QBluetoothUuid::ReportReference: + return QBluetoothServiceDiscoveryAgent::tr("Report Reference"); + default: + break; + } + + return QString(); +} + +/*! Returns true if \a other is equal to this Bluetooth UUID, otherwise false. */ bool QBluetoothUuid::operator==(const QBluetoothUuid &other) const diff --git a/src/bluetooth/qbluetoothuuid.h b/src/bluetooth/qbluetoothuuid.h index 957a4ed2..274e98ab 100644 --- a/src/bluetooth/qbluetoothuuid.h +++ b/src/bluetooth/qbluetoothuuid.h @@ -151,12 +151,133 @@ public: VideoDistribution = 0x1305, HDP = 0x1400, HDPSource = 0x1401, - HDPSink = 0x1402 + HDPSink = 0x1402, + GenericAccess = 0x1800, + GenericAttribute = 0x1801, + ImmediateAlert = 0x1802, + LinkLoss = 0x1803, + TxPower = 0x1804, + CurrentTimeService = 0x1805, + ReferenceTimeUpdateService = 0x1806, + NextDSTChangeService = 0x1807, + Glucose = 0x1808, + HealthThermometer = 0x1809, + DeviceInformation = 0x180a, + HeartRate = 0x180d, + PhoneAlertStatusService = 0x180e, + BatteryService = 0x180f, + BloodPressure = 0x1810, + AlertNotificationService = 0x1811, + HumanInterfaceDevice = 0x1812, + ScanParameters = 0x1813, + RunningSpeedAndCadence = 0x1814, + CyclingSpeedAndCadence = 0x1816, + CyclingPower = 0x1818, + LocationAndNavigation = 0x1819, + }; + + enum CharacteristicType { + DeviceName = 0x2a00, + Appearance = 0x2a01, + PeripheralPrivacyFlag = 0x2a02, + ReconnectionAddress = 0x2a03, + PeripheralPreferredConnectionParameters = 0x2a04, + ServiceChanged = 0x2a05, + AlertLevel = 0x2a06, + TxPowerLevel = 0x2a07, + DateTime = 0x2a08, + DayOfWeek = 0x2a09, + DayDateTime = 0x2a0a, + ExactTime256 = 0x2a0c, + DSTOffset = 0x2a0d, + TimeZone = 0x2a0e, + LocalTimeInformation = 0x2a0f, + TimeWithDST = 0x2a11, + TimeAccuracy = 0x2a12, + TimeSource = 0x2a13, + ReferenceTimeInformation = 0x2a14, + TimeUpdateControlPoint = 0x2a16, + TimeUpdateState = 0x2a17, + GlucoseMeasurement = 0x2a18, + BatteryLevel = 0x2a19, + TemperatureMeasurement = 0x2a1c, + TemperatureType = 0x2a1d, + IntermediateTemperature = 0x2a1e, + MeasurementInterval = 0x2a21, + BootKeyboardInputReport = 0x2a22, + SystemID = 0x2a23, + ModelNumberString = 0x2a24, + SerialNumberString = 0x2a25, + FirmwareRevisionString = 0x2a26, + HardwareRevisionString = 0x2a27, + SoftwareRevisionString = 0x2a28, + ManufacturerNameString = 0x2a29, + IEEE1107320601RegulatoryCertificationDataList = 0x2a2a, + CurrentTime = 0x2a2b, + ScanRefresh = 0x2a31, + BootKeyboardOutputReport = 0x2a32, + BootMouseInputReport = 0x2a33, + GlucoseMeasurementContext = 0x2a34, + BloodPressureMeasurement = 0x2a35, + IntermediateCuffPressure = 0x2a36, + HeartRateMeasurement = 0x2a37, + BodySensorLocation = 0x2a38, + HeartRateControlPoint = 0x2a39, + AlertStatus = 0x2a3f, + RingerControlPoint = 0x2a40, + RingerSetting = 0x2a41, + AlertCategoryIDBitMask = 0x2a42, + AlertCategoryID = 0x2a43, + AlertNotificationControlPoint = 0x2a44, + UnreadAlertStatus = 0x2a45, + NewAlert = 0x2a46, + SupportedNewAlertCategory = 0x2a47, + SupportedUnreadAlertCategory = 0x2a48, + BloodPressureFeature = 0x2a49, + HIDInformation = 0x2a4a, + ReportMap = 0x2a4b, + HIDControlPoint = 0x2a4c, + Report = 0x2a4d, + ProtocolMode = 0x2a4e, + ScanIntervalWindow = 0x2a4f, + PnPID = 0x2a50, + GlucoseFeature = 0x2a51, + RecordAccessControlPoint = 0x2a52, + RSCMeasurement = 0x2a53, + RSCFeature = 0x2a54, + SCControlPoint = 0x2a55, + CSCMeasurement = 0x2a5b, + CSCFeature = 0x2a5c, + SensorLocation = 0x2a5d, + CyclingPowerMeasurement = 0x2a63, + CyclingPowerVector = 0x2a64, + CyclingPowerFeature = 0x2a65, + CyclingPowerControlPoint = 0x2a66, + LocationAndSpeed = 0x2a67, + Navigation = 0x2a68, + PositionQuality = 0x2a69, + LNFeature = 0x2a6a, + LNControlPoint = 0x2a6b, + }; + + enum DescriptorType { + UnknownDescriptorType = 0x0, + CharacteristicExtendedProperties = 0x2900, + CharacteristicUserDescription = 0x2901, + ClientCharacteristicConfiguration = 0x2902, + ServerCharacteristicConfiguration = 0x2903, + CharacteristicPresentationFormat = 0x2904, + CharacteristicAggregateFormat = 0x2905, + ValidRange = 0x2906, + ExternalReportReference = 0x2907, + ReportReference = 0x2908 }; QBluetoothUuid(); QBluetoothUuid(ProtocolUuid uuid); QBluetoothUuid(ServiceClassUuid uuid); + QBluetoothUuid(CharacteristicType uuid); + QBluetoothUuid(DescriptorType uuid); explicit QBluetoothUuid(quint16 uuid); explicit QBluetoothUuid(quint32 uuid); explicit QBluetoothUuid(quint128 uuid); @@ -175,6 +296,8 @@ public: static QString serviceClassToString(ServiceClassUuid uuid); static QString protocolToString(ProtocolUuid uuid); + static QString characteristicToString(CharacteristicType uuid); + static QString descriptorToString(DescriptorType uuid); }; #ifndef QT_NO_DEBUG_STREAM diff --git a/src/bluetooth/qlowenergycharacteristic.cpp b/src/bluetooth/qlowenergycharacteristic.cpp new file mode 100644 index 00000000..0c3dc7af --- /dev/null +++ b/src/bluetooth/qlowenergycharacteristic.cpp @@ -0,0 +1,325 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergycharacteristic.h" +#include "qlowenergyserviceprivate_p.h" +#include <QHash> + +QT_BEGIN_NAMESPACE + +/*! + \class QLowEnergyCharacteristic + \inmodule QtBluetooth + \brief The QLowEnergyCharacteristic class stores information about a Bluetooth + Low Energy service characteristic. + + \since 5.4 + + QLowEnergyCharacteristic provides information about a Bluetooth Low Energy + service characteristic's name, UUID, value, permissions, handle and descriptors. + To get the full characteristic specification and information it is necessary to + connect to the service using QLowEnergyServiceInfo and QLowEnergyController classes. + Some characteristics can contain none, one or more descriptors. +*/ + +/*! + \enum QLowEnergyCharacteristic::PropertyType + + This enum describes the properties of a characteristic. + + \value Unknown The type is not known. + \value Broadcasting Allow for the broadcasting of Generic Attributes (GATT) characteristic values. + \value Read Allow the characteristic values to be read. + \value WriteNoResponse Allow characteristic values without responses to be written. + \value Write Allow for characteristic values to be written. + \value Notify Permits notification of characteristic values. + \value Indicate Permits indications of characteristic values. + \value WriteSigned Permits signed writes of the GATT characteristic values. + \value ExtendedProperty Additional characteristic properties are defined in the characteristic + extended properties descriptor. + + \sa properties() +*/ + +struct QLowEnergyCharacteristicPrivate +{ + QLowEnergyHandle handle; +}; + +/*! + Construct a new QLowEnergyCharacteristic. +*/ +QLowEnergyCharacteristic::QLowEnergyCharacteristic(): + d_ptr(0), data(0) +{ + +} + +/*! + Construct a new QLowEnergyCharacteristic that is a copy of \a other. + + The two copies continue to share the same underlying data which does not detach + upon write. +*/ +QLowEnergyCharacteristic::QLowEnergyCharacteristic(const QLowEnergyCharacteristic &other): + d_ptr(other.d_ptr), data(0) +{ + if (other.data) { + data = new QLowEnergyCharacteristicPrivate(); + data->handle = other.data->handle; + } +} + +/*! + \internal +*/ +QLowEnergyCharacteristic::QLowEnergyCharacteristic( + QSharedPointer<QLowEnergyServicePrivate> p, QLowEnergyHandle handle): + d_ptr(p) +{ + data = new QLowEnergyCharacteristicPrivate(); + data->handle = handle; +} + +/*! + Destroys the QLowEnergyCharacteristic object. +*/ +QLowEnergyCharacteristic::~QLowEnergyCharacteristic() +{ + delete data; +} + +/*! + Returns the name of the gatt characteristic type. +*/ +QString QLowEnergyCharacteristic::name() const +{ + return QBluetoothUuid::characteristicToString( + static_cast<QBluetoothUuid::CharacteristicType>(uuid().toUInt16())); +} + +/*! + Returns the UUID of the gatt characteristic. +*/ +QBluetoothUuid QLowEnergyCharacteristic::uuid() const +{ + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->handle)) + return QBluetoothUuid(); + + return d_ptr->characteristicList[data->handle].uuid; +} + +/*! + Returns the properties of the gatt characteristic. +*/ +QLowEnergyCharacteristic::PropertyTypes QLowEnergyCharacteristic::properties() const +{ + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->handle)) + return QLowEnergyCharacteristic::Unknown; + + return d_ptr->characteristicList[data->handle].properties; +} + +/*! + Returns value of the GATT characteristic. +*/ +QByteArray QLowEnergyCharacteristic::value() const +{ + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->handle)) + return QByteArray(); + + return d_ptr->characteristicList[data->handle].value; +} + +/*! + Returns the handle of the GATT characteristic's value attribute; + otherwise returns \c 0. +*/ +QLowEnergyHandle QLowEnergyCharacteristic::handle() const +{ + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->handle)) + return 0; + + return d_ptr->characteristicList[data->handle].valueHandle; +} + +/*! + Makes a copy of \a other and assigns it to this QLowEnergyCharacteristic object. + The two copies continue to share the same service and registration details. +*/ +QLowEnergyCharacteristic &QLowEnergyCharacteristic::operator=(const QLowEnergyCharacteristic &other) +{ + d_ptr = other.d_ptr; + + if (!other.data) { + if (data) { + delete data; + data = 0; + } + } else { + if (!data) + data = new QLowEnergyCharacteristicPrivate(); + + data->handle = other.data->handle; + } + return *this; +} + +/*! + Returns \c true if \a other is equal to this QLowEnergyCharacteristic; otherwise \c false. + + Two QLowEnergyCharcteristic instances are considered to be equal if they refer to + the same charcteristic on the same remote Bluetooth Low Energy device. + */ +bool QLowEnergyCharacteristic::operator==(const QLowEnergyCharacteristic &other) const +{ + if (d_ptr != other.d_ptr) + return false; + + if ((data && !other.data) || (!data && other.data)) + return false; + + if (!data) + return true; + + if (data->handle != other.data->handle) + return false; + + return true; +} + +/*! + Returns \c true if \a other is not equal to this QLowEnergyCharacteristic; otherwise \c false. + + Two QLowEnergyCharcteristic instances are considered to be equal if they refer to + the same charcteristic on the same remote Bluetooth Low Energy device. + */ + +bool QLowEnergyCharacteristic::operator!=(const QLowEnergyCharacteristic &other) const +{ + return !(*this == other); +} + +/*! + Returns \c true if the QLowEnergyCharacteristic object is valid, otherwise returns \c false. + + An invalid characteristic object is not associated to any service + or the associated service is no longer valid due to for example a disconnect from + the underlying Bluetooth Low Energy device. Once the object is invalid + it cannot become valid anymore. + + \note If a QLowEnergyCharacteristic instance turns invalid due to a disconnect + from the underlying device, the information encapsulated by the current + instance remains as it was at the time of the disconnect. +*/ +bool QLowEnergyCharacteristic::isValid() const +{ + if (d_ptr.isNull() || !data) + return false; + + if (d_ptr->state == QLowEnergyService::InvalidService) + return false; + + return true; +} + +QLowEnergyHandle QLowEnergyCharacteristic::attributeHandle() const +{ + if (d_ptr.isNull() || !data) + return 0; + + return data->handle; +} + + +/*! + Returns the descriptor with \a uuid; otherwise an invalid \c QLowEnergyDescriptor + instance. + + \sa descriptors() +*/ +QLowEnergyDescriptor QLowEnergyCharacteristic::descriptor(const QBluetoothUuid &uuid) const +{ + if (d_ptr.isNull() || !data) + return QLowEnergyDescriptor(); + + QList<QLowEnergyHandle> descriptorKeys = d_ptr->characteristicList[data->handle]. + descriptorList.keys(); + foreach (const QLowEnergyHandle descHandle, descriptorKeys) { + if (uuid == d_ptr->characteristicList[data->handle].descriptorList[descHandle].uuid) + return QLowEnergyDescriptor(d_ptr, data->handle, descHandle); + } + + return QLowEnergyDescriptor(); +} + +/*! + Returns the list of descriptors belonging to this characteristic; otherwise + an empty list. + + \sa descriptor() +*/ +QList<QLowEnergyDescriptor> QLowEnergyCharacteristic::descriptors() const +{ + QList<QLowEnergyDescriptor> result; + + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->handle)) + return result; + + QList<QLowEnergyHandle> descriptorKeys = d_ptr->characteristicList[data->handle]. + descriptorList.keys(); + + std::sort(descriptorKeys.begin(), descriptorKeys.end()); + + foreach (const QLowEnergyHandle descHandle, descriptorKeys) { + QLowEnergyDescriptor descriptor(d_ptr, data->handle, descHandle); + result.append(descriptor); + } + + return result; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycharacteristic.h b/src/bluetooth/qlowenergycharacteristic.h new file mode 100644 index 00000000..1c30a939 --- /dev/null +++ b/src/bluetooth/qlowenergycharacteristic.h @@ -0,0 +1,111 @@ +/*************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYCHARACTERISTIC_H +#define QLOWENERGYCHARACTERISTIC_H +#include <QtCore/QSharedPointer> +#include <QtCore/QObject> +#include <QtBluetooth/qbluetooth.h> +#include <QtBluetooth/QBluetoothUuid> +#include <QtBluetooth/QLowEnergyDescriptor> + +QT_BEGIN_NAMESPACE + +class QBluetoothUuid; +class QLowEnergyServicePrivate; +struct QLowEnergyCharacteristicPrivate; +class Q_BLUETOOTH_EXPORT QLowEnergyCharacteristic +{ +public: + + enum PropertyType { + Unknown = 0x00, + Broadcasting = 0x01, + Read = 0x02, + WriteNoResponse = 0x04, + Write = 0x08, + Notify = 0x10, + Indicate = 0x20, + WriteSigned = 0x40, + ExtendedProperty = 0x80 + }; + Q_DECLARE_FLAGS(PropertyTypes, PropertyType) + + QLowEnergyCharacteristic(); + QLowEnergyCharacteristic(const QLowEnergyCharacteristic &other); + ~QLowEnergyCharacteristic(); + + QLowEnergyCharacteristic &operator=(const QLowEnergyCharacteristic &other); + bool operator==(const QLowEnergyCharacteristic &other) const; + bool operator!=(const QLowEnergyCharacteristic &other) const; + + QString name() const; + + QBluetoothUuid uuid() const; + + QByteArray value() const; + + QLowEnergyCharacteristic::PropertyTypes properties() const; + QLowEnergyHandle handle() const; + + QLowEnergyDescriptor descriptor(const QBluetoothUuid &uuid) const; + QList<QLowEnergyDescriptor> descriptors() const; + + bool isValid() const; + +protected: + QLowEnergyHandle attributeHandle() const; + + QSharedPointer<QLowEnergyServicePrivate> d_ptr; + + friend class QLowEnergyService; + friend class QLowEnergyControllerPrivate; + QLowEnergyCharacteristicPrivate *data; + QLowEnergyCharacteristic(QSharedPointer<QLowEnergyServicePrivate> p, + QLowEnergyHandle handle); +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(QLowEnergyCharacteristic::PropertyTypes) + +QT_END_NAMESPACE + +#endif // QLOWENERGYCHARACTERISTIC_H diff --git a/src/bluetooth/qlowenergycontroller.cpp b/src/bluetooth/qlowenergycontroller.cpp new file mode 100644 index 00000000..d0c70ba3 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller.cpp @@ -0,0 +1,360 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergycontroller.h" +#include "qlowenergycontroller_p.h" + +#include <QtBluetooth/QBluetoothLocalDevice> + +#include <algorithm> + +QT_BEGIN_NAMESPACE + +void QLowEnergyControllerPrivate::setError( + QLowEnergyController::Error newError) +{ + Q_Q(QLowEnergyController); + error = newError; + + switch (newError) { + case QLowEnergyController::UnknownRemoteDeviceError: + errorString = QLowEnergyController::tr("Remote device cannot be found"); + break; + case QLowEnergyController::InvalidBluetoothAdapterError: + errorString = QLowEnergyController::tr("Cannot find local adapter"); + break; + case QLowEnergyController::NetworkError: + errorString = QLowEnergyController::tr("Error occurred during connection I/O"); + break; + case QLowEnergyController::UnknownError: + default: + errorString = QLowEnergyController::tr("Unknown Error"); + break; + } + + emit q->error(newError); +} + +bool QLowEnergyControllerPrivate::isValidLocalAdapter() +{ + if (localAdapter.isNull()) + return false; + + const QList<QBluetoothHostInfo> foundAdapters = QBluetoothLocalDevice::allDevices(); + bool adapterFound = false; + + foreach (const QBluetoothHostInfo &info, foundAdapters) { + if (info.address() == localAdapter) { + adapterFound = true; + break; + } + } + + return adapterFound; +} + +void QLowEnergyControllerPrivate::setState( + QLowEnergyController::ControllerState newState) +{ + Q_Q(QLowEnergyController); + if (state == newState) + return; + + state = newState; + emit q->stateChanged(state); +} + +void QLowEnergyControllerPrivate::invalidateServices() +{ + foreach (const QSharedPointer<QLowEnergyServicePrivate> service, serviceList.values()) { + service->setController(0); + service->setState(QLowEnergyService::InvalidService); + } + + serviceList.clear(); +} + +QSharedPointer<QLowEnergyServicePrivate> QLowEnergyControllerPrivate::serviceForHandle( + QLowEnergyHandle handle) +{ + foreach (QSharedPointer<QLowEnergyServicePrivate> service, serviceList.values()) + if (service->startHandle <= handle && handle <= service->endHandle) + return service; + + return QSharedPointer<QLowEnergyServicePrivate>(); +} + +/*! + Returns a valid characteristic if the given handle is the + handle of the characteristic itself or one of its descriptors + */ +QLowEnergyCharacteristic QLowEnergyControllerPrivate::characteristicForHandle( + QLowEnergyHandle handle) +{ + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(handle); + if (service.isNull()) + return QLowEnergyCharacteristic(); + + if (service->characteristicList.isEmpty()) + return QLowEnergyCharacteristic(); + + // check whether it is the handle of a characteristic header + if (service->characteristicList.contains(handle)) + return QLowEnergyCharacteristic(service, handle); + + // check whether it is the handle of the characteristic value or its descriptors + QList<QLowEnergyHandle> charHandles = service->characteristicList.keys(); + std::sort(charHandles.begin(), charHandles.end()); + for (int i = charHandles.size() - 1; i >= 0; i--) { + if (charHandles.at(i) > handle) + continue; + + return QLowEnergyCharacteristic(service, charHandles.at(i)); + } + + return QLowEnergyCharacteristic(); +} + +/*! + Returns a valid descriptor if \a handle blongs to a descriptor; + otherwise an invalid one. + */ +QLowEnergyDescriptor QLowEnergyControllerPrivate::descriptorForHandle( + QLowEnergyHandle handle) +{ + const QLowEnergyCharacteristic matchingChar = characteristicForHandle(handle); + if (!matchingChar.isValid()) + return QLowEnergyDescriptor(); + + const QLowEnergyServicePrivate::CharData charData = matchingChar. + d_ptr->characteristicList[matchingChar.attributeHandle()]; + + if (charData.descriptorList.contains(handle)) + return QLowEnergyDescriptor(matchingChar.d_ptr, matchingChar.attributeHandle(), + handle); + + return QLowEnergyDescriptor(); +} + +/*! + Returns the length of the updated characteristic value. + */ +quint16 QLowEnergyControllerPrivate::updateValueOfCharacteristic( + QLowEnergyHandle charHandle,const QByteArray &value, bool appendValue) +{ + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + if (!service.isNull() && service->characteristicList.contains(charHandle)) { + if (appendValue) + service->characteristicList[charHandle].value += value; + else + service->characteristicList[charHandle].value = value; + + return service->characteristicList[charHandle].value.size(); + } + + return 0; +} + +/*! + Returns the length of the updated descriptor value. + */ +quint16 QLowEnergyControllerPrivate::updateValueOfDescriptor( + QLowEnergyHandle charHandle, QLowEnergyHandle descriptorHandle, + const QByteArray &value, bool appendValue) +{ + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + if (service.isNull() || !service->characteristicList.contains(charHandle)) + return 0; + + if (!service->characteristicList[charHandle].descriptorList.contains(descriptorHandle)) + return 0; + + if (appendValue) + service->characteristicList[charHandle].descriptorList[descriptorHandle].value += value; + else + service->characteristicList[charHandle].descriptorList[descriptorHandle].value = value; + + return service->characteristicList[charHandle].descriptorList[descriptorHandle].value.size(); +} + +QLowEnergyController::QLowEnergyController( + const QBluetoothAddress &remoteDevice, + QObject *parent) + : QObject(parent), d_ptr(new QLowEnergyControllerPrivate()) +{ + Q_D(QLowEnergyController); + d->q_ptr = this; + d->remoteDevice = remoteDevice; + d->localAdapter = QBluetoothLocalDevice().address(); +} + +QLowEnergyController::QLowEnergyController( + const QBluetoothAddress &remoteDevice, + const QBluetoothAddress &localDevice, + QObject *parent) + : QObject(parent), d_ptr(new QLowEnergyControllerPrivate()) +{ + Q_D(QLowEnergyController); + d->q_ptr = this; + d->remoteDevice = remoteDevice; + d->localAdapter = localDevice; +} + +QLowEnergyController::~QLowEnergyController() +{ + disconnectFromDevice(); //in case we were connected + delete d_ptr; +} + +/*! + Returns the address of the local Bluetooth adapter being used for the + communication. + + If this class instance was requested to use the default adapter + but there was no default adapter when creating this + class instance, the returned \l QBluetoothAddress will be null. + + \sa QBluetoothAddress::isNull() + */ +QBluetoothAddress QLowEnergyController::localAddress() const +{ + return d_ptr->localAdapter; +} + +QBluetoothAddress QLowEnergyController::remoteAddress() const +{ + return d_ptr->remoteDevice; +} + +QLowEnergyController::ControllerState QLowEnergyController::state() const +{ + return d_ptr->state; +} + +void QLowEnergyController::connectToDevice() +{ + Q_D(QLowEnergyController); + + if (!d->isValidLocalAdapter()) { + d->setError(QLowEnergyController::InvalidBluetoothAdapterError); + return; + } + + if (state() != QLowEnergyController::UnconnectedState) + return; + + d->connectToDevice(); +} + +void QLowEnergyController::disconnectFromDevice() +{ + Q_D(QLowEnergyController); + + if (state() == QLowEnergyController::UnconnectedState) + return; + + d->invalidateServices(); + d->disconnectFromDevice(); +} + +void QLowEnergyController::discoverServices() +{ + Q_D(QLowEnergyController); + + if (d->state != QLowEnergyController::ConnectedState) + return; + + d->discoverServices(); +} + +/*! + Returns the list of services offered by the remote device. + + The list contains all primary and secondary services. + + \sa createServiceObject() + */ +QList<QBluetoothUuid> QLowEnergyController::services() const +{ + return d_ptr->serviceList.keys(); +} + +/*! + Creates an instance of the service represented by \a serviceUuid. + The \a serviceUuid parameter must have been obtained via + \l services(). + + The caller takes ownership of the returned pointer and may pass + a \a parent parameter as default owner. + + This function returns a null pointer if no service with + \a serviceUUid can be found on the remote device. + + This function can return instances for secondary services + too. The include relationships between services can be expressed + via \l QLowEnergyService::includedServices(). + + \sa services() + */ +QLowEnergyService *QLowEnergyController::createServiceObject( + const QBluetoothUuid &serviceUuid, QObject *parent) +{ + Q_D(QLowEnergyController); + if (!d->serviceList.contains(serviceUuid)) + return 0; + + QLowEnergyService *service = new QLowEnergyService( + d->serviceList.value(serviceUuid), parent); + + return service; +} + +QLowEnergyController::Error QLowEnergyController::error() const +{ + return d_ptr->error; +} + +QString QLowEnergyController::errorString() const +{ + return d_ptr->errorString; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller.h b/src/bluetooth/qlowenergycontroller.h new file mode 100644 index 00000000..27421cf1 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller.h @@ -0,0 +1,112 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYCONTROLLER_H +#define QLOWENERGYCONTROLLER_H + +#include <QObject> +#include <QtBluetooth/QBluetoothAddress> +#include <QtBluetooth/QBluetoothUuid> +#include <QtBluetooth/QLowEnergyService> + +QT_BEGIN_NAMESPACE + +class QLowEnergyControllerPrivate; +class Q_BLUETOOTH_EXPORT QLowEnergyController : public QObject +{ + Q_OBJECT +public: + enum Error { + NoError, + UnknownError, + UnknownRemoteDeviceError, + NetworkError, + InvalidBluetoothAdapterError + }; + + enum ControllerState { + UnconnectedState = 0, + ConnectingState, + ConnectedState, + ClosingState, + }; + + explicit QLowEnergyController(const QBluetoothAddress &remoteDevice, + QObject *parent = 0); + explicit QLowEnergyController(const QBluetoothAddress &remoteDevice, + const QBluetoothAddress &localDevice, + QObject *parent = 0); + ~QLowEnergyController(); + + QBluetoothAddress localAddress() const; + QBluetoothAddress remoteAddress() const; + + ControllerState state() const; + + void connectToDevice(); + void disconnectFromDevice(); + + // TODO add a way of detecting whether discoverDetails() as already called + void discoverServices(); + QList<QBluetoothUuid> services() const; + QLowEnergyService *createServiceObject( + const QBluetoothUuid &service, QObject *parent = 0); + + Error error() const; + QString errorString() const; + +Q_SIGNALS: + void connected(); + void disconnected(); + void stateChanged(QLowEnergyController::ControllerState state); + void error(QLowEnergyController::Error newError); + + void serviceDiscovered(const QBluetoothUuid &newService); + void discoveryFinished(); + +private: + Q_DECLARE_PRIVATE(QLowEnergyController) + QLowEnergyControllerPrivate *d_ptr; +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYCONTROLLER_H diff --git a/src/bluetooth/qlowenergycontroller_bluez.cpp b/src/bluetooth/qlowenergycontroller_bluez.cpp new file mode 100644 index 00000000..dcb99736 --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_bluez.cpp @@ -0,0 +1,1192 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 Javier S. Pedro <maemo@javispedro.com> +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergycontroller_p.h" +#include "qbluetoothsocket_p.h" +#include "bluez/bluez_data_p.h" + +#include <QtCore/QLoggingCategory> +#include <QtBluetooth/QBluetoothSocket> +#include <QtBluetooth/QLowEnergyService> + +#define ATTRIBUTE_CHANNEL_ID 4 + +#define ATT_DEFAULT_LE_MTU 23 +#define ATT_MAX_LE_MTU 0x200 + +#define GATT_PRIMARY_SERVICE 0x2800 +#define GATT_SECONDARY_SERVICE 0x2801 +#define GATT_INCLUDED_SERVICE 0x2802 +#define GATT_CHARACTERISTIC 0x2803 + +// GATT commands +#define ATT_OP_ERROR_RESPONSE 0x1 +#define ATT_OP_EXCHANGE_MTU_REQUEST 0x2 //send own mtu +#define ATT_OP_EXCHANGE_MTU_RESPONSE 0x3 //receive server MTU +#define ATT_OP_FIND_INFORMATION_REQUEST 0x4 //discover individual attribute info +#define ATT_OP_FIND_INFORMATION_RESPONSE 0x5 +#define ATT_OP_READ_BY_TYPE_REQUEST 0x8 //discover characteristics +#define ATT_OP_READ_BY_TYPE_RESPONSE 0x9 +#define ATT_OP_READ_REQUEST 0xA //read characteristic & descriptor values +#define ATT_OP_READ_RESPONSE 0xB +#define ATT_OP_READ_BLOB_REQUEST 0xC //read values longer than MTU-1 +#define ATT_OP_READ_BLOB_RESPONSE 0xD +#define ATT_OP_READ_BY_GROUP_REQUEST 0x10 //discover services +#define ATT_OP_READ_BY_GROUP_RESPONSE 0x11 +#define ATT_OP_WRITE_REQUEST 0x12 //write characteristic +#define ATT_OP_WRITE_RESPONSE 0x13 +#define ATT_OP_HANDLE_VAL_NOTIFICATION 0x1b //informs about value change +#define ATT_OP_HANDLE_VAL_INDICATION 0x1d //informs about value change -> requires reply +#define ATT_OP_HANDLE_VAL_CONFIRMATION 0x1e //answer for ATT_OP_HANDLE_VAL_INDICATION + +//GATT command sizes in bytes +#define FIND_INFO_REQUEST_SIZE 5 +#define GRP_TYPE_REQ_SIZE 7 +#define READ_BY_TYPE_REQ_SIZE 7 +#define READ_REQUEST_SIZE 3 +#define READ_BLOB_REQUEST_SIZE 5 +#define WRITE_REQUEST_SIZE 3 +#define MTU_EXCHANGE_SIZE 3 + +// GATT error codes +#define ATT_ERROR_INVALID_HANDLE 0x01 +#define ATT_ERROR_READ_NOT_PERM 0x02 +#define ATT_ERROR_WRITE_NOT_PERM 0x03 +#define ATT_ERROR_INVALID_PDU 0x04 +#define ATT_ERROR_INSUF_AUTHENTICATION 0x05 +#define ATT_ERROR_REQUEST_NOT_SUPPORTED 0x06 +#define ATT_ERROR_INVALID_OFFSET 0x07 +#define ATT_ERROR_INSUF_AUTHORIZATION 0x08 +#define ATT_ERROR_PREPARE_QUEUE_FULL 0x09 +#define ATT_ERROR_ATTRIBUTE_NOT_FOUND 0x0A +#define ATT_ERROR_ATTRIBUTE_NOT_LONG 0x0B +#define ATT_ERROR_INSUF_ENCR_KEY_SIZE 0x0C +#define ATT_ERROR_INVAL_ATTR_VALUE_LEN 0x0D +#define ATT_ERROR_UNLIKELY 0x0E +#define ATT_ERROR_INSUF_ENCRYPTION 0x0F +#define ATT_ERROR_APPLICATION 0x10 +#define ATT_ERROR_INSUF_RESOURCES 0x11 + +#define APPEND_VALUE true +#define NEW_VALUE false + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(QT_BT_BLUEZ) + +static inline QBluetoothUuid convert_uuid128(const quint128 *p) +{ + quint128 dst_hostOrder, dst_bigEndian; + + // Bluetooth LE data comes as little endian + // uuids are constructed using high endian + btoh128(p, &dst_hostOrder); + hton128(&dst_hostOrder, &dst_bigEndian); + + // convert to Qt's own data type + quint128 qtdst; + memcpy(&qtdst, &dst_bigEndian, sizeof(quint128)); + + return QBluetoothUuid(qtdst); +} + +static void dumpErrorInformation(const QByteArray &response) +{ + const char *data = response.constData(); + if (response.size() != 5 || data[0] != ATT_OP_ERROR_RESPONSE) { + qCWarning(QT_BT_BLUEZ) << QLatin1String("Not a valid error response"); + return; + } + + quint8 lastCommand = data[1]; + quint16 handle = bt_get_le16(&data[2]); + quint8 errorCode = data[4]; + + QString errorString; + switch (errorCode) { + case ATT_ERROR_INVALID_HANDLE: + errorString = QStringLiteral("invalid handle"); break; + case ATT_ERROR_READ_NOT_PERM: + errorString = QStringLiteral("not readable attribute - permissions"); break; + case ATT_ERROR_WRITE_NOT_PERM: + errorString = QStringLiteral("not writable attribute - permissions"); break; + case ATT_ERROR_INVALID_PDU: + errorString = QStringLiteral("PDU invalid"); break; + case ATT_ERROR_INSUF_AUTHENTICATION: + errorString = QStringLiteral("needs authentication - permissions"); break; + case ATT_ERROR_REQUEST_NOT_SUPPORTED: + errorString = QStringLiteral("server does not support request"); break; + case ATT_ERROR_INVALID_OFFSET: + errorString = QStringLiteral("offset past end of attribute"); break; + case ATT_ERROR_INSUF_AUTHORIZATION: + errorString = QStringLiteral("need authorization - permissions"); break; + case ATT_ERROR_PREPARE_QUEUE_FULL: + errorString = QStringLiteral("run out of prepare queue space"); break; + case ATT_ERROR_ATTRIBUTE_NOT_FOUND: + errorString = QStringLiteral("no attribute in given range found"); break; + case ATT_ERROR_ATTRIBUTE_NOT_LONG: + errorString = QStringLiteral("attribute not read/written using read blob"); break; + case ATT_ERROR_INSUF_ENCR_KEY_SIZE: + errorString = QStringLiteral("need encryption key size - permissions"); break; + case ATT_ERROR_INVAL_ATTR_VALUE_LEN: + errorString = QStringLiteral("written value is invalid size"); break; + case ATT_ERROR_UNLIKELY: + errorString = QStringLiteral("unlikely error"); break; + case ATT_ERROR_INSUF_ENCRYPTION: + errorString = QStringLiteral("needs encryption - permissions"); break; + case ATT_ERROR_APPLICATION: + errorString = QStringLiteral("application error"); break; + case ATT_ERROR_INSUF_RESOURCES: + errorString = QStringLiteral("insufficient resources to complete request"); break; + default: + errorString = QStringLiteral("unknown error code"); break; + } + + qCDebug(QT_BT_BLUEZ) << "Error1:" << errorString + << "last command:" << hex << lastCommand + << "handle:" << handle; +} + +QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() + : QObject(), + state(QLowEnergyController::UnconnectedState), + error(QLowEnergyController::NoError), + l2cpSocket(0), requestPending(false), + mtuSize(ATT_DEFAULT_LE_MTU) +{ + qRegisterMetaType<QList<QLowEnergyHandle> >(); +} + +QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() +{ +} + +void QLowEnergyControllerPrivate::connectToDevice() +{ + setState(QLowEnergyController::ConnectingState); + if (l2cpSocket) + delete l2cpSocket; + + l2cpSocket = new QBluetoothSocket(QBluetoothServiceInfo::L2capProtocol, this); + connect(l2cpSocket, SIGNAL(connected()), this, SLOT(l2cpConnected())); + connect(l2cpSocket, SIGNAL(disconnected()), this, SLOT(l2cpDisconnected())); + connect(l2cpSocket, SIGNAL(error(QBluetoothSocket::SocketError)), + this, SLOT(l2cpErrorChanged(QBluetoothSocket::SocketError))); + connect(l2cpSocket, SIGNAL(readyRead()), this, SLOT(l2cpReadyRead())); + + l2cpSocket->d_ptr->isLowEnergySocket = true; + + // bind the socket to the local device + int sockfd = l2cpSocket->socketDescriptor(); + if (sockfd < 0) { + qCWarning(QT_BT_BLUEZ) << "l2cp socket not initialised"; + setError(QLowEnergyController::UnknownError); + return; + } + + struct sockaddr_l2 addr; + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + addr.l2_cid = htobs(ATTRIBUTE_CHANNEL_ID); + addr.l2_bdaddr_type = BDADDR_LE_PUBLIC; + convertAddress(localAdapter.toUInt64(), addr.l2_bdaddr.b); + + if (::bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) { + qCWarning(QT_BT_BLUEZ) << qt_error_string(errno); + setError(QLowEnergyController::UnknownError); + return; + } + + // connect + l2cpSocket->connectToService(remoteDevice, ATTRIBUTE_CHANNEL_ID); +} + +void QLowEnergyControllerPrivate::l2cpConnected() +{ + Q_Q(QLowEnergyController); + + exchangeMTU(); + + setState(QLowEnergyController::ConnectedState); + emit q->connected(); +} + +void QLowEnergyControllerPrivate::disconnectFromDevice() +{ + setState(QLowEnergyController::ClosingState); + l2cpSocket->close(); +} + +void QLowEnergyControllerPrivate::l2cpDisconnected() +{ + Q_Q(QLowEnergyController); + + setState(QLowEnergyController::UnconnectedState); + emit q->disconnected(); +} + +void QLowEnergyControllerPrivate::l2cpErrorChanged(QBluetoothSocket::SocketError e) +{ + switch (e) { + case QBluetoothSocket::HostNotFoundError: + setError(QLowEnergyController::UnknownRemoteDeviceError); + qCDebug(QT_BT_BLUEZ) << "The passed remote device address cannot be found"; + break; + case QBluetoothSocket::NetworkError: + setError(QLowEnergyController::NetworkError); + qCDebug(QT_BT_BLUEZ) << "Network IO error while talking to LE device"; + break; + case QBluetoothSocket::UnknownSocketError: + case QBluetoothSocket::UnsupportedProtocolError: + case QBluetoothSocket::OperationError: + case QBluetoothSocket::ServiceNotFoundError: + default: + // these errors shouldn't happen -> as it means + // the code in this file has bugs + qCDebug(QT_BT_BLUEZ) << "Unknown l2cp socket error: " << e << l2cpSocket->errorString(); + setError(QLowEnergyController::UnknownError); + break; + } + + invalidateServices(); + setState(QLowEnergyController::UnconnectedState); +} + +void QLowEnergyControllerPrivate::l2cpReadyRead() +{ + const QByteArray reply = l2cpSocket->readAll(); + qCDebug(QT_BT_BLUEZ) << "Received size:" << reply.size() << "data:" << reply.toHex(); + if (reply.isEmpty()) + return; + + const quint8 command = reply.constData()[0]; + switch (command) { + case ATT_OP_HANDLE_VAL_NOTIFICATION: + { + processUnsolicitedReply(reply); + return; + } + case ATT_OP_HANDLE_VAL_INDICATION: + { + //send confirmation + QByteArray packet; + packet.append(static_cast<char>(ATT_OP_HANDLE_VAL_CONFIRMATION)); + sendCommand(packet); + + processUnsolicitedReply(reply); + return; + } + case ATT_OP_EXCHANGE_MTU_REQUEST: + case ATT_OP_READ_BY_GROUP_REQUEST: + case ATT_OP_READ_BY_TYPE_REQUEST: + case ATT_OP_READ_REQUEST: + case ATT_OP_FIND_INFORMATION_REQUEST: + case ATT_OP_WRITE_REQUEST: + qCWarning(QT_BT_BLUEZ) << "Unexpected message type" << hex << command + << "will be ignored" ; + return; + default: + //only solicited replies finish pending requests + requestPending = false; + break; + } + + Q_ASSERT(!openRequests.isEmpty()); + const Request request = openRequests.dequeue(); + processReply(request, reply); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::sendCommand(const QByteArray &packet) +{ + qint64 result = l2cpSocket->write(packet.constData(), + packet.size()); + if (result == -1) { + qCDebug(QT_BT_BLUEZ) << "Cannot write L2CP command:" << hex + << packet.toHex() + << l2cpSocket->errorString(); + setError(QLowEnergyController::NetworkError); + } +} + +void QLowEnergyControllerPrivate::sendNextPendingRequest() +{ + if (openRequests.isEmpty() || requestPending) + return; + + const Request &request = openRequests.head(); +// qCDebug(QT_BT_BLUEZ) << "Sending request, type:" << hex << request.command +// << request.payload.toHex(); + + requestPending = true; + sendCommand(request.payload); +} + +QLowEnergyHandle parseReadByTypeCharDiscovery( + QLowEnergyServicePrivate::CharData *charData, + const char *data, quint16 elementLength) +{ + Q_ASSERT(charData); + Q_ASSERT(data); + + QLowEnergyHandle attributeHandle = bt_get_le16(&data[0]); + charData->properties = + (QLowEnergyCharacteristic::PropertyTypes)data[2]; + charData->valueHandle = bt_get_le16(&data[3]); + + if (elementLength == 7) // 16 bit uuid + charData->uuid = QBluetoothUuid(bt_get_le16(&data[5])); + else + charData->uuid = convert_uuid128((quint128 *)&data[5]); + + qCDebug(QT_BT_BLUEZ) << "Found handle:" << hex << attributeHandle + << "properties:" << charData->properties + << "value handle:" << charData->valueHandle + << "uuid:" << charData->uuid.toString(); + + return attributeHandle; +} + +QLowEnergyHandle parseReadByTypeIncludeDiscovery( + QList<QBluetoothUuid> *foundServices, + const char *data, quint16 elementLength) +{ + Q_ASSERT(foundServices); + Q_ASSERT(data); + + QLowEnergyHandle attributeHandle = bt_get_le16(&data[0]); + + // the next 2 elements are not required as we have discovered + // all (primary/secondary) services already. Now we are only + // interested in their relationship to each other + // data[2] -> included service start handle + // data[4] -> included service end handle + + if (elementLength == 8) //16 bit uuid + foundServices->append(QBluetoothUuid(bt_get_le16(&data[6]))); + else + foundServices->append(convert_uuid128((quint128 *) &data[6])); + + qCDebug(QT_BT_BLUEZ) << "Found included service: " << hex + << attributeHandle << "uuid:" << *foundServices; + + return attributeHandle; +} + +void QLowEnergyControllerPrivate::processReply( + const Request &request, const QByteArray &response) +{ + Q_Q(QLowEnergyController); + + quint8 command = response.constData()[0]; + + bool isErrorResponse = false; + // if error occurred 2. byte is previous request type + if (command == ATT_OP_ERROR_RESPONSE) { + dumpErrorInformation(response); + command = response.constData()[1]; + isErrorResponse = true; + } + + switch (command) { + case ATT_OP_EXCHANGE_MTU_REQUEST: // in case of error + case ATT_OP_EXCHANGE_MTU_RESPONSE: + { + Q_ASSERT(request.command == ATT_OP_EXCHANGE_MTU_REQUEST); + if (isErrorResponse) { + mtuSize = ATT_DEFAULT_LE_MTU; + break; + } + + const char *data = response.constData(); + quint16 mtu = bt_get_le16(&data[1]); + mtuSize = mtu; + if (mtuSize < ATT_DEFAULT_LE_MTU) + mtuSize = ATT_DEFAULT_LE_MTU; + + qCDebug(QT_BT_BLUEZ) << "Server MTU:" << mtu << "resulting mtu:" << mtuSize; + } + break; + case ATT_OP_READ_BY_GROUP_REQUEST: // in case of error + case ATT_OP_READ_BY_GROUP_RESPONSE: + { + // Discovering services + Q_ASSERT(request.command == ATT_OP_READ_BY_GROUP_REQUEST); + + const quint16 type = request.reference.toUInt(); + + if (isErrorResponse) { + if (type == GATT_SECONDARY_SERVICE) + q->discoveryFinished(); + else // search for secondary services + sendReadByGroupRequest(0x0001, 0xFFFF, GATT_SECONDARY_SERVICE); + break; + } + + QLowEnergyHandle start = 0, end = 0; + const quint16 elementLength = response.constData()[1]; + const quint16 numElements = (response.size() - 2) / elementLength; + quint16 offset = 2; + const char *data = response.constData(); + for (int i = 0; i < numElements; i++) { + start = bt_get_le16(&data[offset]); + end = bt_get_le16(&data[offset+2]); + + QBluetoothUuid uuid; + if (elementLength == 6) //16 bit uuid + uuid = QBluetoothUuid(bt_get_le16(&data[offset+4])); + else if (elementLength == 20) //128 bit uuid + uuid = convert_uuid128((quint128 *)&data[offset+4]); + //else -> do nothing + + offset += elementLength; + + + qCDebug(QT_BT_BLUEZ) << "Found uuid:" << uuid << "start handle:" << hex + << start << "end handle:" << end; + + QLowEnergyServicePrivate *priv = new QLowEnergyServicePrivate(); + priv->uuid = uuid; + priv->startHandle = start; + priv->endHandle = end; + if (type != GATT_PRIMARY_SERVICE) //unset PrimaryService bit + priv->type &= ~QLowEnergyService::PrimaryService; + priv->setController(this); + + QSharedPointer<QLowEnergyServicePrivate> pointer(priv); + + serviceList.insert(uuid, pointer); + emit q->serviceDiscovered(uuid); + } + + if (end != 0xFFFF) { + sendReadByGroupRequest(end+1, 0xFFFF, type); + } else { + if (type == GATT_SECONDARY_SERVICE) + emit q->discoveryFinished(); + else // search for secondary services + sendReadByGroupRequest(0x0001, 0xFFFF, GATT_SECONDARY_SERVICE); + } + } + break; + case ATT_OP_READ_BY_TYPE_REQUEST: //in case of error + case ATT_OP_READ_BY_TYPE_RESPONSE: + { + // Discovering characteristics + Q_ASSERT(request.command == ATT_OP_READ_BY_TYPE_REQUEST); + + QSharedPointer<QLowEnergyServicePrivate> p = + request.reference.value<QSharedPointer<QLowEnergyServicePrivate> >(); + const quint16 attributeType = request.reference2.toUInt(); + + if (isErrorResponse) { + if (attributeType == GATT_CHARACTERISTIC) { + // we reached end of service handle + // just finished up characteristic discovery + // continue with values of characteristics + if (!p->characteristicList.isEmpty()) { + readServiceValues(p->uuid, true); + } else { + // discovery finished since the service doesn't have any + // characteristics + p->setState(QLowEnergyService::ServiceDiscovered); + } + } else if (attributeType == GATT_INCLUDED_SERVICE) { + // finished up include discovery + // continue with characteristic discovery + sendReadByTypeRequest(p, p->startHandle, GATT_CHARACTERISTIC); + } + break; + } + + /* packet format: + * if GATT_CHARACTERISTIC discovery + * <opcode><elementLength> + * [<handle><property><charHandle><uuid>]+ + * + * if GATT_INCLUDE discovery + * <opcode><elementLength> + * [<handle><startHandle_included><endHandle_included><uuid>]+ + * + * The uuid can be 16 or 128 bit. + */ + QLowEnergyHandle lastHandle; + const quint16 elementLength = response.constData()[1]; + const quint16 numElements = (response.size() - 2) / elementLength; + quint16 offset = 2; + const char *data = response.constData(); + for (int i = 0; i < numElements; i++) { + if (attributeType == GATT_CHARACTERISTIC) { + QLowEnergyServicePrivate::CharData characteristic; + lastHandle = parseReadByTypeCharDiscovery( + &characteristic, &data[offset], elementLength); + p->characteristicList[lastHandle] = characteristic; + } else if (attributeType == GATT_INCLUDED_SERVICE) { + QList<QBluetoothUuid> includedServices; + lastHandle = parseReadByTypeIncludeDiscovery( + &includedServices, &data[offset], elementLength); + p->includedServices = includedServices; + foreach (const QBluetoothUuid &uuid, includedServices) { + if (serviceList.contains(uuid)) + serviceList[uuid]->type |= QLowEnergyService::IncludedService; + } + } + } + + if (lastHandle + 1 < p->endHandle) { // more chars to discover + sendReadByTypeRequest(p, lastHandle + 1, attributeType); + } else { + if (attributeType == GATT_INCLUDED_SERVICE) + sendReadByTypeRequest(p, p->startHandle, GATT_CHARACTERISTIC); + else + readServiceValues(p->uuid, true); + } + } + break; + case ATT_OP_READ_REQUEST: //error case + case ATT_OP_READ_RESPONSE: + { + //Reading characteristics and descriptors + Q_ASSERT(request.command == ATT_OP_READ_REQUEST); + + uint handleData = request.reference.toUInt(); + const QLowEnergyHandle charHandle = (handleData & 0xffff); + const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff); + + // we ignore error response + if (!isErrorResponse) { + if (!descriptorHandle) + updateValueOfCharacteristic(charHandle, response.mid(1), NEW_VALUE); + else + updateValueOfDescriptor(charHandle, descriptorHandle, + response.mid(1), NEW_VALUE); + + if (response.size() == mtuSize) { + // Potentially more data -> switch to blob reads + readServiceValuesByOffset(handleData, mtuSize-1, + request.reference2.toBool()); + break; + } + } + + if (request.reference2.toBool()) { + //last characteristic -> progress to descriptor discovery + //last descriptor -> service discovery is done + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + Q_ASSERT(!service.isNull()); + if (!descriptorHandle) + discoverServiceDescriptors(service->uuid); + else + service->setState(QLowEnergyService::ServiceDiscovered); + } + } + break; + case ATT_OP_READ_BLOB_REQUEST: //error case + case ATT_OP_READ_BLOB_RESPONSE: + { + //Reading characteristic or descriptor with value longer than MTU + Q_ASSERT(request.command == ATT_OP_READ_BLOB_REQUEST); + + uint handleData = request.reference.toUInt(); + const QLowEnergyHandle charHandle = (handleData & 0xffff); + const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff); + + //ignore errors + if (!isErrorResponse) { + quint16 length = 0; + if (!descriptorHandle) + length = updateValueOfCharacteristic(charHandle, response.mid(1), APPEND_VALUE); + else + length = updateValueOfDescriptor(charHandle, descriptorHandle, + response.mid(1), APPEND_VALUE); + if (response.size() == mtuSize) { + readServiceValuesByOffset(handleData, length, + request.reference2.toBool()); + break; + } + } + + if (request.reference2.toBool()) { + //last overlong characteristic -> progress to descriptor discovery + //last overlong descriptor -> service discovery is done + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + Q_ASSERT(!service.isNull()); + if (!descriptorHandle) + discoverServiceDescriptors(service->uuid); + else + service->setState(QLowEnergyService::ServiceDiscovered); + } + + } + break; + case ATT_OP_FIND_INFORMATION_REQUEST: //error case + case ATT_OP_FIND_INFORMATION_RESPONSE: + { + //Discovering descriptors + Q_ASSERT(request.command == ATT_OP_FIND_INFORMATION_REQUEST); + + /* packet format: + * <opcode><format>[<handle><descriptor_uuid>]+ + * + * The uuid can be 16 or 128 bit which is indicated by format. + */ + + QList<QLowEnergyHandle> keys = request.reference.value<QList<QLowEnergyHandle> >(); + if (keys.isEmpty()) { + qCWarning(QT_BT_BLUEZ) << "Descriptor discovery for unknown characteristic received"; + break; + } + QLowEnergyHandle charHandle = keys.first(); + + QSharedPointer<QLowEnergyServicePrivate> p = + serviceForHandle(charHandle); + Q_ASSERT(!p.isNull()); + + if (isErrorResponse) { + readServiceValues(p->uuid, false); //read descriptor values + break; + } + + const quint8 format = response[1]; + quint16 elementLength; + switch (format) { + case 0x01: + elementLength = 2 + 2; //sizeof(QLowEnergyHandle) + 16bit uuid + break; + case 0x02: + elementLength = 2 + 16; //sizeof(QLowEnergyHandle) + 128bit uuid + break; + default: + qCWarning(QT_BT_BLUEZ) << "Unknown format in FIND_INFORMATION_RESPONSE"; + return; + } + + const quint16 numElements = (response.size() - 2) / elementLength; + + quint16 offset = 2; + QLowEnergyHandle descriptorHandle; + QBluetoothUuid uuid; + const char *data = response.constData(); + for (int i = 0; i < numElements; i++) { + descriptorHandle = bt_get_le16(&data[offset]); + + if (format == 0x01) + uuid = QBluetoothUuid(bt_get_le16(&data[offset+2])); + else if (format == 0x02) + uuid = convert_uuid128((quint128 *)&data[offset+2]); + + offset += elementLength; + + // ignore all attributes which are not of type descriptor + // examples are the characteristics value or + bool ok = false; + quint16 shortUuid = uuid.toUInt16(&ok); + if (ok && shortUuid >= QLowEnergyServicePrivate::PrimaryService + && shortUuid <= QLowEnergyServicePrivate::Characteristic){ + qCDebug(QT_BT_BLUEZ) << "Suppressing primary/characteristic" << hex << shortUuid; + continue; + } + + // ignore value handle + if (descriptorHandle == p->characteristicList[charHandle].valueHandle) { + qCDebug(QT_BT_BLUEZ) << "Suppressing char handle" << hex << descriptorHandle; + continue; + } + + QLowEnergyServicePrivate::DescData data; + data.uuid = uuid; + p->characteristicList[charHandle].descriptorList.insert( + descriptorHandle, data); + + qCDebug(QT_BT_BLUEZ) << "Descriptor found, uuid:" + << uuid.toString() + << "descriptor handle:" << hex << descriptorHandle; + } + + const QLowEnergyHandle nextPotentialHandle = descriptorHandle + 1; + if (keys.count() == 1) { + // Reached last characteristic of service + + // The endhandle of a service is always the last handle of + // the current service. We must either continue until we have reached + // the starting handle of the next service (endHandle+1) or + // the last physical handle address (0xffff). Note that + // the endHandle of the last service on the device is 0xffff. + + if ((p->endHandle != 0xffff && nextPotentialHandle >= p->endHandle + 1) + || (descriptorHandle == 0xffff)) { + keys.removeFirst(); + // last descriptor of last characteristic found + // continue with reading descriptor values + readServiceValues(p->uuid, false); + } else { + discoverNextDescriptor(p, keys, nextPotentialHandle); + } + } else { + if (nextPotentialHandle >= keys[1]) //reached next char + keys.removeFirst(); + discoverNextDescriptor(p, keys, nextPotentialHandle); + } + } + break; + case ATT_OP_WRITE_REQUEST: //error case + case ATT_OP_WRITE_RESPONSE: + { + //Write command response + Q_ASSERT(request.command == ATT_OP_WRITE_REQUEST); + + uint ref = request.reference.toUInt(); + const QLowEnergyHandle charHandle = (ref & 0xffff); + const QLowEnergyHandle descriptorHandle = ((ref >> 16) & 0xffff); + + QSharedPointer<QLowEnergyServicePrivate> service = serviceForHandle(charHandle); + if (service.isNull() || !service->characteristicList.contains(charHandle)) + break; + + if (isErrorResponse) { + if (!descriptorHandle) + service->setError(QLowEnergyService::CharacteristicWriteError); + else + service->setError(QLowEnergyService::DescriptorWriteError); + break; + } + + const QByteArray newValue = request.reference2.toByteArray(); + if (!descriptorHandle) { + service->characteristicList[charHandle].value = newValue; + QLowEnergyCharacteristic ch(service, charHandle); + emit service->characteristicChanged(ch, newValue); + } else { + service->characteristicList[charHandle].descriptorList[descriptorHandle].value = newValue; + QLowEnergyDescriptor descriptor(service, charHandle, descriptorHandle); + emit service->descriptorChanged(descriptor, newValue); + } + } + break; + default: + qCDebug(QT_BT_BLUEZ) << "Unknown packet: " << response.toHex(); + break; + } +} + +void QLowEnergyControllerPrivate::discoverServices() +{ + sendReadByGroupRequest(0x0001, 0xFFFF, GATT_PRIMARY_SERVICE); +} + +void QLowEnergyControllerPrivate::sendReadByGroupRequest( + QLowEnergyHandle start, QLowEnergyHandle end, quint16 type) +{ + //call for primary and secondary services + quint8 packet[GRP_TYPE_REQ_SIZE]; + + packet[0] = ATT_OP_READ_BY_GROUP_REQUEST; + bt_put_unaligned(htobs(start), (quint16 *) &packet[1]); + bt_put_unaligned(htobs(end), (quint16 *) &packet[3]); + bt_put_unaligned(htobs(type), (quint16 *) &packet[5]); + + QByteArray data(GRP_TYPE_REQ_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, GRP_TYPE_REQ_SIZE); + qCDebug(QT_BT_BLUEZ) << "Sending read_by_group_type request, startHandle:" << hex + << start << "endHandle:" << end << type; + + Request request; + request.payload = data; + request.command = ATT_OP_READ_BY_GROUP_REQUEST; + request.reference = type; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &service) +{ + if (!serviceList.contains(service)) { + qCWarning(QT_BT_BLUEZ) << "Discovery of unknown service" << service.toString() + << "not possible"; + return; + } + + QSharedPointer<QLowEnergyServicePrivate> serviceData = serviceList.value(service); + serviceData->characteristicList.clear(); + sendReadByTypeRequest(serviceData, serviceData->startHandle, GATT_INCLUDED_SERVICE); +} + +void QLowEnergyControllerPrivate::sendReadByTypeRequest( + QSharedPointer<QLowEnergyServicePrivate> serviceData, + QLowEnergyHandle nextHandle, quint16 attributeType) +{ + quint8 packet[READ_BY_TYPE_REQ_SIZE]; + + packet[0] = ATT_OP_READ_BY_TYPE_REQUEST; + bt_put_unaligned(htobs(nextHandle), (quint16 *) &packet[1]); + bt_put_unaligned(htobs(serviceData->endHandle), (quint16 *) &packet[3]); + bt_put_unaligned(htobs(attributeType), (quint16 *) &packet[5]); + + QByteArray data(READ_BY_TYPE_REQ_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, READ_BY_TYPE_REQ_SIZE); + qCDebug(QT_BT_BLUEZ) << "Sending read_by_type request, startHandle:" << hex + << nextHandle << "endHandle:" << serviceData->endHandle + << "type:" << attributeType << "packet:" << data.toHex(); + + Request request; + request.payload = data; + request.command = ATT_OP_READ_BY_TYPE_REQUEST; + request.reference = QVariant::fromValue(serviceData); + request.reference2 = attributeType; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +/*! + \internal + + Reads the value of characteristics and descriptors. + + \a readCharacteristics determines whether we intend to read a characteristic; + otherwise we read a descriptor. + */ +void QLowEnergyControllerPrivate::readServiceValues( + const QBluetoothUuid &serviceUuid, bool readCharacteristics) +{ + // TODO Long charactertistic value reads not yet supported (larger than MTU) + quint8 packet[READ_REQUEST_SIZE]; + if (QT_BT_BLUEZ().isDebugEnabled()) { + if (readCharacteristics) + qCDebug(QT_BT_BLUEZ) << "Reading characteristic values for" + << serviceUuid.toString(); + else + qCDebug(QT_BT_BLUEZ) << "Reading descriptor values for" + << serviceUuid.toString(); + } + + QSharedPointer<QLowEnergyServicePrivate> service = serviceList.value(serviceUuid); + + // pair.first -> target attribute + // pair.second -> context information for read request + QPair<QLowEnergyHandle, quint32> pair; + + // Create list of attribute handles which need to be read + QList<QPair<QLowEnergyHandle, quint32> > targetHandles; + const QList<QLowEnergyHandle> keys = service->characteristicList.keys(); + for (int i = 0; i < keys.count(); i++) { + const QLowEnergyHandle charHandle = keys[i]; + const QLowEnergyServicePrivate::CharData &charDetails = + service->characteristicList[charHandle]; + + + if (readCharacteristics) { + // Collect handles of all characteristic value attributes + + // Don't try to read writeOnly characteristic + if (!(charDetails.properties & QLowEnergyCharacteristic::Read)) + continue; + + pair.first = charDetails.valueHandle; + pair.second = charHandle; + targetHandles.append(pair); + + } else { + // Collect handles of all descriptor attributes + foreach (QLowEnergyHandle descriptorHandle, charDetails.descriptorList.keys()) { + pair.first = descriptorHandle; + pair.second = (charHandle | (descriptorHandle << 16)); + targetHandles.append(pair); + } + } + } + + + if (targetHandles.isEmpty()) { + if (readCharacteristics) { + // none of the characteristics is readable + // -> continue with descriptor discovery + discoverServiceDescriptors(service->uuid); + } else { + // characteristic w/o descriptors + service->setState(QLowEnergyService::ServiceDiscovered); + } + return; + } + + for (int i = 0; i < targetHandles.count(); i++) { + pair = targetHandles.at(i); + packet[0] = ATT_OP_READ_REQUEST; + bt_put_unaligned(htobs(pair.first), (quint16 *) &packet[1]); + + QByteArray data(READ_REQUEST_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, READ_REQUEST_SIZE); + + Request request; + request.payload = data; + request.command = ATT_OP_READ_REQUEST; + request.reference = pair.second; + // last entry? + request.reference2 = QVariant((bool)(i + 1 == targetHandles.count())); + openRequests.enqueue(request); + } + + sendNextPendingRequest(); +} + +/*! + \internal + + This function is used when reading a handle value that is + longer than the mtuSize. + + The BLOB read request is prepended to the list of + open requests to finish the current value read up before + starting the next read request. + */ +void QLowEnergyControllerPrivate::readServiceValuesByOffset( + quint16 handleData, quint16 offset, bool isLastValue) +{ + const QLowEnergyHandle charHandle = (handleData & 0xffff); + const QLowEnergyHandle descriptorHandle = ((handleData >> 16) & 0xffff); + quint8 packet[READ_REQUEST_SIZE]; + + packet[0] = ATT_OP_READ_BLOB_REQUEST; + + QLowEnergyHandle handleToRead = charHandle; + if (descriptorHandle) { + handleToRead = descriptorHandle; + qCDebug(QT_BT_BLUEZ) << "Reading descriptor via blob request" + << hex << descriptorHandle; + } else { + //charHandle is not the char's value handle + QSharedPointer<QLowEnergyServicePrivate> service = + serviceForHandle(charHandle); + if (!service.isNull() + && service->characteristicList.contains(charHandle)) { + handleToRead = service->characteristicList[charHandle].valueHandle; + qCDebug(QT_BT_BLUEZ) << "Reading characteristic via blob request" + << hex << handleToRead; + } else { + Q_ASSERT(false); + } + } + + bt_put_unaligned(htobs(handleToRead), (quint16 *) &packet[1]); + bt_put_unaligned(htobs(offset), (quint16 *) &packet[3]); + + QByteArray data(READ_BLOB_REQUEST_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, READ_BLOB_REQUEST_SIZE); + + Request request; + request.payload = data; + request.command = ATT_OP_READ_BLOB_REQUEST; + request.reference = handleData; + request.reference2 = isLastValue; + openRequests.prepend(request); +} + +void QLowEnergyControllerPrivate::discoverServiceDescriptors( + const QBluetoothUuid &serviceUuid) +{ + qCDebug(QT_BT_BLUEZ) << "Discovering descriptor values for" + << serviceUuid.toString(); + QSharedPointer<QLowEnergyServicePrivate> service = serviceList.value(serviceUuid); + // start handle of all known characteristics + QList<QLowEnergyHandle> keys = service->characteristicList.keys(); + + if (keys.isEmpty()) { // service has no characteristics + // implies that characteristic & descriptor discovery can be skipped + service->setState(QLowEnergyService::ServiceDiscovered); + return; + } + + std::sort(keys.begin(), keys.end()); + + discoverNextDescriptor(service, keys, keys[0]); +} + +void QLowEnergyControllerPrivate::processUnsolicitedReply(const QByteArray &payload) +{ + const char *data = payload.constData(); + bool isNotification = (data[0] == ATT_OP_HANDLE_VAL_NOTIFICATION); + const QLowEnergyHandle changedHandle = bt_get_le16(&data[1]); + + if (QT_BT_BLUEZ().isDebugEnabled()) { + if (isNotification) + qCDebug(QT_BT_BLUEZ) << "Change notification for handle" << hex << changedHandle; + else + qCDebug(QT_BT_BLUEZ) << "Change indication for handle" << hex << changedHandle; + } + + const QLowEnergyCharacteristic ch = characteristicForHandle(changedHandle); + if (ch.isValid() && ch.handle() == changedHandle) { + updateValueOfCharacteristic(ch.attributeHandle(), payload.mid(3), NEW_VALUE); + emit ch.d_ptr->characteristicChanged(ch, payload.mid(3)); + } else { + qCWarning(QT_BT_BLUEZ) << "Cannot find matching characteristic for " + "notification/indication"; + } +} + +void QLowEnergyControllerPrivate::exchangeMTU() +{ + qCDebug(QT_BT_BLUEZ) << "Exchanging MTU"; + + quint8 packet[MTU_EXCHANGE_SIZE]; + packet[0] = ATT_OP_EXCHANGE_MTU_REQUEST; + bt_put_unaligned(htobs(ATT_MAX_LE_MTU), (quint16 *) &packet[1]); + + QByteArray data(MTU_EXCHANGE_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, MTU_EXCHANGE_SIZE); + + Request request; + request.payload = data; + request.command = ATT_OP_EXCHANGE_MTU_REQUEST; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::discoverNextDescriptor( + QSharedPointer<QLowEnergyServicePrivate> serviceData, + const QList<QLowEnergyHandle> pendingCharHandles, + const QLowEnergyHandle startingHandle) +{ + Q_ASSERT(!pendingCharHandles.isEmpty()); + Q_ASSERT(!serviceData.isNull()); + + qCDebug(QT_BT_BLUEZ) << "Sending find_info request" << hex + << pendingCharHandles << startingHandle; + + quint8 packet[FIND_INFO_REQUEST_SIZE]; + packet[0] = ATT_OP_FIND_INFORMATION_REQUEST; + + const QLowEnergyHandle charStartHandle = startingHandle; + QLowEnergyHandle charEndHandle = 0; + if (pendingCharHandles.count() == 1) //single characteristic + charEndHandle = serviceData->endHandle; + else + charEndHandle = pendingCharHandles[1] - 1; + + bt_put_unaligned(htobs(charStartHandle), (quint16 *) &packet[1]); + bt_put_unaligned(htobs(charEndHandle), (quint16 *) &packet[3]); + + QByteArray data(FIND_INFO_REQUEST_SIZE, Qt::Uninitialized); + memcpy(data.data(), packet, FIND_INFO_REQUEST_SIZE); + + Request request; + request.payload = data; + request.command = ATT_OP_FIND_INFORMATION_REQUEST; + request.reference = QVariant::fromValue<QList<QLowEnergyHandle> >(pendingCharHandles); + request.reference2 = startingHandle; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::writeCharacteristic( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue) +{ + Q_ASSERT(!service.isNull()); + + if (!service->characteristicList.contains(charHandle)) + return; + + const QLowEnergyHandle valueHandle = service->characteristicList[charHandle].valueHandle; + // sizeof(command) + sizeof(handle) + sizeof(newValue) + const int size = 1 + 2 + newValue.size(); + + quint8 packet[WRITE_REQUEST_SIZE]; + packet[0] = ATT_OP_WRITE_REQUEST; + bt_put_unaligned(htobs(valueHandle), (quint16 *) &packet[1]); + + + QByteArray data(size, Qt::Uninitialized); + memcpy(data.data(), packet, WRITE_REQUEST_SIZE); + memcpy(&(data.data()[WRITE_REQUEST_SIZE]), newValue.constData(), newValue.size()); + + qCDebug(QT_BT_BLUEZ) << "Writing characteristic" << hex << charHandle + << "(size:" << size << ")"; + + Request request; + request.payload = data; + request.command = ATT_OP_WRITE_REQUEST; + request.reference = charHandle; + request.reference2 = newValue; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +void QLowEnergyControllerPrivate::writeDescriptor( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) +{ + Q_ASSERT(!service.isNull()); + + // sizeof(command) + sizeof(handle) + sizeof(newValue) + const int size = 1 + 2 + newValue.size(); + + quint8 packet[WRITE_REQUEST_SIZE]; + packet[0] = ATT_OP_WRITE_REQUEST; + bt_put_unaligned(htobs(descriptorHandle), (quint16 *) &packet[1]); + + QByteArray data(size, Qt::Uninitialized); + memcpy(data.data(), packet, WRITE_REQUEST_SIZE); + memcpy(&(data.data()[WRITE_REQUEST_SIZE]), newValue.constData(), newValue.size()); + + qCDebug(QT_BT_BLUEZ) << "Writing descriptor" << hex << descriptorHandle + << "(size:" << size << ")"; + + Request request; + request.payload = data; + request.command = ATT_OP_WRITE_REQUEST; + request.reference = (charHandle | (descriptorHandle << 16)); + request.reference2 = newValue; + openRequests.enqueue(request); + + sendNextPendingRequest(); +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.cpp b/src/bluetooth/qlowenergycontroller_p.cpp new file mode 100644 index 00000000..a903494d --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_p.cpp @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergycontroller_p.h" + +QT_BEGIN_NAMESPACE + +QLowEnergyControllerPrivate::QLowEnergyControllerPrivate() + : QObject(), + state(QLowEnergyController::UnconnectedState), + error(QLowEnergyController::NoError) +{ +} + +QLowEnergyControllerPrivate::~QLowEnergyControllerPrivate() +{ +} + +void QLowEnergyControllerPrivate::connectToDevice() +{ + setError(QLowEnergyController::UnknownError); +} + +void QLowEnergyControllerPrivate::disconnectFromDevice() +{ + +} + +void QLowEnergyControllerPrivate::discoverServices() +{ + +} + +void QLowEnergyControllerPrivate::discoverServiceDetails(const QBluetoothUuid &/*service*/) +{ + +} + +void QLowEnergyControllerPrivate::writeCharacteristic( + const QSharedPointer<QLowEnergyServicePrivate> /*service*/, + const QLowEnergyHandle /*charHandle*/, + const QByteArray &/*newValue*/) +{ + +} + +void QLowEnergyControllerPrivate::writeDescriptor( + const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue) +{ + +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergycontroller_p.h b/src/bluetooth/qlowenergycontroller_p.h new file mode 100644 index 00000000..ef21fbff --- /dev/null +++ b/src/bluetooth/qlowenergycontroller_p.h @@ -0,0 +1,166 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYCONTROLLERPRIVATE_P_H +#define QLOWENERGYCONTROLLERPRIVATE_P_H + +#include <qglobal.h> +#include <QtCore/QQueue> +#include <QtBluetooth/qbluetooth.h> +#include "qlowenergycontroller.h" +#include "qlowenergyserviceprivate_p.h" + +#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) +#include <QtBluetooth/QBluetoothSocket> +#endif + +QT_BEGIN_NAMESPACE + +typedef QMap<QBluetoothUuid, QSharedPointer<QLowEnergyServicePrivate> > ServiceDataMap; + +class QLowEnergyControllerPrivate : public QObject +{ + Q_OBJECT + Q_DECLARE_PUBLIC(QLowEnergyController) +public: + QLowEnergyControllerPrivate(); + ~QLowEnergyControllerPrivate(); + + void setError(QLowEnergyController::Error newError); + bool isValidLocalAdapter(); + + void setState(QLowEnergyController::ControllerState newState); + + void connectToDevice(); + void disconnectFromDevice(); + + void discoverServices(); + void invalidateServices(); + + void discoverServiceDetails(const QBluetoothUuid &service); + + // misc helpers + QSharedPointer<QLowEnergyServicePrivate> serviceForHandle( + QLowEnergyHandle handle); + QLowEnergyCharacteristic characteristicForHandle( + QLowEnergyHandle handle); + QLowEnergyDescriptor descriptorForHandle( + QLowEnergyHandle handle); + + quint16 updateValueOfCharacteristic(QLowEnergyHandle charHandle, + const QByteArray &value, + bool appendValue); + quint16 updateValueOfDescriptor(QLowEnergyHandle charHandle, + QLowEnergyHandle descriptorHandle, + const QByteArray &value, + bool appendValue); + + + // write data + void writeCharacteristic(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QByteArray &newValue); + void writeDescriptor(const QSharedPointer<QLowEnergyServicePrivate> service, + const QLowEnergyHandle charHandle, + const QLowEnergyHandle descriptorHandle, + const QByteArray &newValue); + + + QBluetoothAddress remoteDevice; + QBluetoothAddress localAdapter; + + QLowEnergyController::ControllerState state; + QLowEnergyController::Error error; + QString errorString; + + // list of all found service uuids + ServiceDataMap serviceList; + +private: +#if defined(QT_BLUEZ_BLUETOOTH) && !defined(QT_BLUEZ_NO_BTLE) + QBluetoothSocket *l2cpSocket; + struct Request { + quint8 command; + QByteArray payload; + // TODO reference below is ugly but until we know all commands and their + // requirements this is WIP + QVariant reference; + QVariant reference2; + }; + QQueue<Request> openRequests; + bool requestPending; + quint16 mtuSize; + + void sendCommand(const QByteArray &packet); + void sendNextPendingRequest(); + void processReply(const Request &request, const QByteArray &reply); + + void sendReadByGroupRequest(QLowEnergyHandle start, QLowEnergyHandle end, + quint16 type); + void sendReadByTypeRequest(QSharedPointer<QLowEnergyServicePrivate> serviceData, + QLowEnergyHandle nextHandle, quint16 attributeType); + void sendReadValueRequest(QLowEnergyHandle attributeHandle, bool isDescriptor); + void readServiceValues(const QBluetoothUuid &service, + bool readCharacteristics); + void readServiceValuesByOffset(quint16 handleData, quint16 offset, + bool isLastValue); + + void discoverServiceDescriptors(const QBluetoothUuid &serviceUuid); + void discoverNextDescriptor(QSharedPointer<QLowEnergyServicePrivate> serviceData, + const QList<QLowEnergyHandle> pendingCharHandles, + QLowEnergyHandle startingHandle); + void processUnsolicitedReply(const QByteArray &msg); + void exchangeMTU(); + + +private slots: + void l2cpConnected(); + void l2cpDisconnected(); + void l2cpErrorChanged(QBluetoothSocket::SocketError); + void l2cpReadyRead(); +#endif +private: + QLowEnergyController *q_ptr; +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYCONTROLLERPRIVATE_P_H diff --git a/src/bluetooth/qlowenergydescriptor.cpp b/src/bluetooth/qlowenergydescriptor.cpp new file mode 100644 index 00000000..6817d20e --- /dev/null +++ b/src/bluetooth/qlowenergydescriptor.cpp @@ -0,0 +1,293 @@ +/*************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtBluetooth/QLowEnergyService> +#include "qlowenergyserviceprivate_p.h" +#include "qlowenergydescriptor.h" + +QT_BEGIN_NAMESPACE + +/*! + \class QLowEnergyDescriptor + \inmodule QtBluetooth + \brief The QLowEnergyDescriptor class stores information about the Bluetooth + Low Energy descriptor. + \since 5.4 + + QLowEnergyDescriptor provides information about a Bluetooth Low Energy + descriptor's name, UUID, value and handle. Descriptors are contained in the + Bluetooth Low Energy characteristic and they provide additional information + about the characteristic (data format, notification activation, etc). +*/ + +struct QLowEnergyDescriptorPrivate +{ + QLowEnergyHandle charHandle; + QLowEnergyHandle descHandle; +}; + +/*! + Construct a new QLowEnergyDescriptor. +*/ +QLowEnergyDescriptor::QLowEnergyDescriptor(): + d_ptr(0), data(0) +{ +} + +/*! + Construct a new QLowEnergyDesxcriptor that is a copy of \a other. + + The two copies continue to share the same underlying data which does not detach + upon write. +*/ +QLowEnergyDescriptor::QLowEnergyDescriptor(const QLowEnergyDescriptor &other): + d_ptr(other.d_ptr), data(0) +{ + if (other.data) { + data = new QLowEnergyDescriptorPrivate(); + data->charHandle = other.data->charHandle; + data->descHandle = other.data->descHandle; + } +} + +/*! + \internal + +*/ +QLowEnergyDescriptor::QLowEnergyDescriptor(QSharedPointer<QLowEnergyServicePrivate> p, + QLowEnergyHandle charHandle, + QLowEnergyHandle descHandle): + d_ptr(p) +{ + data = new QLowEnergyDescriptorPrivate(); + data->charHandle = charHandle; + data->descHandle = descHandle; + +} + +/*! + Destroys the QLowEnergyDescriptor object. +*/ +QLowEnergyDescriptor::~QLowEnergyDescriptor() +{ + delete data; +} + +/*! + Makes a copy of \a other and assigns it to this QLowEnergyDescriptor object. + The two copies continue to share the same service and registration details. +*/ +QLowEnergyDescriptor &QLowEnergyDescriptor::operator=(const QLowEnergyDescriptor &other) +{ + d_ptr = other.d_ptr; + + if (!other.data) { + if (data) { + delete data; + data = 0; + } + } else { + if (!data) + data = new QLowEnergyDescriptorPrivate(); + + data->charHandle = other.data->charHandle; + data->descHandle = other.data->descHandle; + } + + return *this; +} + +/*! + Returns \c true if \a other is equal to this QLowEnergyCharacteristic; otherwise \c false. + + Two QLowEnergyDescriptor instances are considered to be equal if they refer to + the same descriptor on the same remote Bluetooth Low Energy device. + */ +bool QLowEnergyDescriptor::operator==(const QLowEnergyDescriptor &other) const +{ + if (d_ptr != other.d_ptr) + return false; + + if ((data && !other.data) || (!data && other.data)) + return false; + + if (!data) + return true; + + if (data->charHandle != other.data->charHandle + || data->descHandle != other.data->descHandle) { + return false; + } + + return true; +} + +/*! + Returns \c true if \a other is not equal to this QLowEnergyCharacteristic; otherwise \c false. + + Two QLowEnergyDescriptor instances are considered to be equal if they refer to + the same descriptor on the same remote Bluetooth Low Energy device. + */ +bool QLowEnergyDescriptor::operator!=(const QLowEnergyDescriptor &other) const +{ + return !(*this == other); +} + +/*! + Returns \c true if the QLowEnergyDescriptor object is valid, otherwise returns \c false. + + An invalid descriptor instance is not associated to any service + or the associated service is no longer valid due to for example a disconnect from + the underlying Bluetooth Low Energy device. Once the object is invalid + it cannot become valid anymore. + + \note If a QLowEnergyDescriptor instance turns invalid due to a disconnect + from the underlying device, the information encapsulated by the current + instance remains as it was at the time of the disconnect. +*/ +bool QLowEnergyDescriptor::isValid() const +{ + if (d_ptr.isNull() || !data) + return false; + + if (d_ptr->state == QLowEnergyService::InvalidService) + return false; + + return true; +} + +/*! + Returns the UUID of this descriptor. +*/ +QBluetoothUuid QLowEnergyDescriptor::uuid() const +{ + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->charHandle) + || !d_ptr->characteristicList[data->charHandle]. + descriptorList.contains(data->descHandle)) { + return QBluetoothUuid(); + } + + return d_ptr->characteristicList[data->charHandle].descriptorList[data->descHandle].uuid; +} + +/*! + Returns the handle of the descriptor. +*/ +QLowEnergyHandle QLowEnergyDescriptor::handle() const +{ + if (!data) + return 0; + + return data->descHandle; +} + +/*! + Returns the value of the descriptor. +*/ +QByteArray QLowEnergyDescriptor::value() const +{ + if (d_ptr.isNull() || !data + || !d_ptr->characteristicList.contains(data->charHandle) + || !d_ptr->characteristicList[data->charHandle]. + descriptorList.contains(data->descHandle)) { + return QByteArray(); + } + + return d_ptr->characteristicList[data->charHandle].descriptorList[data->descHandle].value; +} + +/*! + Returns the name of the descriptor type. + + \sa type() +*/ + +QString QLowEnergyDescriptor::name() const +{ + return QBluetoothUuid::descriptorToString(type()); +} + +/*! + Returns the type of descriptor. + */ +QBluetoothUuid::DescriptorType QLowEnergyDescriptor::type() const +{ + const QBluetoothUuid u = uuid(); + bool ok = false; + quint16 shortUuid = u.toUInt16(&ok); + + if (!ok) + return QBluetoothUuid::UnknownDescriptorType; + + switch (shortUuid) { + case QBluetoothUuid::CharacteristicExtendedProperties: + case QBluetoothUuid::CharacteristicUserDescription: + case QBluetoothUuid::ClientCharacteristicConfiguration: + case QBluetoothUuid::ServerCharacteristicConfiguration: + case QBluetoothUuid::CharacteristicPresentationFormat: + case QBluetoothUuid::CharacteristicAggregateFormat: + case QBluetoothUuid::ValidRange: + case QBluetoothUuid::ExternalReportReference: + case QBluetoothUuid::ReportReference: + return (QBluetoothUuid::DescriptorType) shortUuid; + default: + break; + } + + return QBluetoothUuid::UnknownDescriptorType; +} + +/*! + \internal + + Returns the handle of the characteristic to which this descriptor belongs + */ +QLowEnergyHandle QLowEnergyDescriptor::characteristicHandle() const +{ + if (d_ptr.isNull() || !data) + return 0; + + return data->charHandle; +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergydescriptor.h b/src/bluetooth/qlowenergydescriptor.h new file mode 100644 index 00000000..2c0a3296 --- /dev/null +++ b/src/bluetooth/qlowenergydescriptor.h @@ -0,0 +1,93 @@ +/*************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2013 BlackBerry Limited. All rights reserved. +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYDESCRIPTOR_H +#define QLOWENERGYDESCRIPTOR_H + +#include <QtCore/QSharedPointer> +#include <QtCore/QVariantMap> +#include <QtBluetooth/qbluetooth.h> +#include <QtBluetooth/QBluetoothUuid> + +QT_BEGIN_NAMESPACE + +class QLowEnergyDescriptorPrivate; +class QLowEnergyServicePrivate; + +class Q_BLUETOOTH_EXPORT QLowEnergyDescriptor +{ +public: + QLowEnergyDescriptor(); + QLowEnergyDescriptor(const QLowEnergyDescriptor &other); + ~QLowEnergyDescriptor(); + + QLowEnergyDescriptor &operator=(const QLowEnergyDescriptor &other); + bool operator==(const QLowEnergyDescriptor &other) const; + bool operator!=(const QLowEnergyDescriptor &other) const; + + bool isValid() const; + + QByteArray value() const; + + QBluetoothUuid uuid() const; + QLowEnergyHandle handle() const; + QString name() const; + + QBluetoothUuid::DescriptorType type() const; + +protected: + QLowEnergyHandle characteristicHandle() const; + QSharedPointer<QLowEnergyServicePrivate> d_ptr; + + friend class QLowEnergyCharacteristic; + friend class QLowEnergyService; + friend class QLowEnergyControllerPrivate; + QLowEnergyDescriptorPrivate *data; + + QLowEnergyDescriptor(QSharedPointer<QLowEnergyServicePrivate> p, + QLowEnergyHandle charHandle, + QLowEnergyHandle descHandle); +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYDESCRIPTOR_H diff --git a/src/bluetooth/qlowenergyservice.cpp b/src/bluetooth/qlowenergyservice.cpp new file mode 100644 index 00000000..725c82e9 --- /dev/null +++ b/src/bluetooth/qlowenergyservice.cpp @@ -0,0 +1,322 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore/QCoreApplication> +#include <QtCore/QPointer> +#include <QtBluetooth/QLowEnergyService> + +#include <algorithm> + +#include "qlowenergycontroller_p.h" +#include "qlowenergyserviceprivate_p.h" + +QT_BEGIN_NAMESPACE + +/*! + \enum QBluetoothDeviceInfo::ServiceType + + This enum describes the type of the service. + + \value PrimaryService The service is a top-level/primary service. + If this type flag is not set the service is considered + to be a secondary service. Each service may be included + by another service which is indicated by \l IncludedService. + \value IncludedService The service is included by another service. +*/ + +/*! + \internal + + QLowEnergyControllerPrivate creates instances of this class. + The user gets access to class instances via + \l QLowEnergyController::services(). + */ +QLowEnergyService::QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> p, + QObject *parent) + : QObject(parent), + d_ptr(p) +{ + qRegisterMetaType<QLowEnergyService::ServiceState>("QLowEnergyService::ServiceState"); + qRegisterMetaType<QLowEnergyService::ServiceError>("QLowEnergyService::ServiceError"); + + connect(p.data(), SIGNAL(error(QLowEnergyService::ServiceError)), + this, SIGNAL(error(QLowEnergyService::ServiceError))); + connect(p.data(), SIGNAL(stateChanged(QLowEnergyService::ServiceState)), + this, SIGNAL(stateChanged(QLowEnergyService::ServiceState))); + connect(p.data(), SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray)), + this, SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray))); + connect(p.data(), SIGNAL(descriptorChanged(QLowEnergyDescriptor,QByteArray)), + this, SIGNAL(descriptorChanged(QLowEnergyDescriptor,QByteArray))); +} + + +QLowEnergyService::~QLowEnergyService() +{ +} + +/*! + Returns the uuids of all services which are included by the + current service. + + It is possible that an included service contains yet another service. Such + second level includes have to be obtained via their relevant first level + QLowEnergyService instance. Technically it is possible that this can create + a circular dependency. + + \l {QLowEnergyController::createServiceObject} should be used to obtain + service instances for each of the uuids. + + \sa createServiceObject() + */ +QList<QBluetoothUuid> QLowEnergyService::includedServices() const +{ + return d_ptr->includedServices; +} + +QLowEnergyService::ServiceState QLowEnergyService::state() const +{ + return d_ptr->state; +} + +QLowEnergyService::ServiceTypes QLowEnergyService::type() const +{ + return d_ptr->type; +} + +/*! + Returns the matching characteristic for \a uuid; otherwise an invalid + characteristic. + + \sa characteristics() +*/ +QLowEnergyCharacteristic QLowEnergyService::characteristic(const QBluetoothUuid &uuid) const +{ + foreach (const QLowEnergyHandle handle, d_ptr->characteristicList.keys()) { + if (d_ptr->characteristicList[handle].uuid == uuid) + return QLowEnergyCharacteristic(d_ptr, handle); + } + + return QLowEnergyCharacteristic(); +} + +/*! + Returns all characteristics associated with this \c QLowEnergyService instance. + + The returned list will be empty if this service instance is invalid, + \l discoverDetails() was not yet called or there are no known characteristics. + + \sa characteristic(), state(), discoverDetails +*/ + +QList<QLowEnergyCharacteristic> QLowEnergyService::characteristics() const +{ + QList<QLowEnergyCharacteristic> results; + QList<QLowEnergyHandle> handles = d_ptr->characteristicList.keys(); + std::sort(handles.begin(), handles.end()); + + foreach (const QLowEnergyHandle &handle, handles) { + QLowEnergyCharacteristic characteristic(d_ptr, handle); + results.append(characteristic); + } + return results; +} + + +QBluetoothUuid QLowEnergyService::serviceUuid() const +{ + return d_ptr->uuid; +} + + +QString QLowEnergyService::serviceName() const +{ + bool ok = false; + quint16 clsId = d_ptr->uuid.toUInt16(&ok); + if (ok) { + QBluetoothUuid::ServiceClassUuid id + = static_cast<QBluetoothUuid::ServiceClassUuid>(clsId); + const QString name = QBluetoothUuid::serviceClassToString(id); + if (!name.isEmpty()) + return name; + } + return qApp ? + qApp->translate("QBluetoothServiceDiscoveryAgent", "Unknown Service") : + QStringLiteral("Unknown Service"); +} + + +void QLowEnergyService::discoverDetails() +{ + Q_D(QLowEnergyService); + + if (!d->controller || d->state == QLowEnergyService::InvalidService) { + d->setError(QLowEnergyService::ServiceNotValidError); + return; + } + + if (d->state != QLowEnergyService::DiscoveryRequired) + return; + + d->setState(QLowEnergyService::DiscoveringServices); + + d->controller->discoverServiceDetails(d->uuid); +} + +QLowEnergyService::ServiceError QLowEnergyService::error() const +{ + return d_ptr->lastError; +} + + +/*! + Returns \c true if \a characteristic belongs to this service; otherwise \c false. + + A characteristic belongs to a service if \l {QLowEnergyService::characteristics()} + contains the \a characteristic. + */ +bool QLowEnergyService::contains(const QLowEnergyCharacteristic &characteristic) const +{ + if (characteristic.d_ptr.isNull() || !characteristic.data) + return false; + + if (d_ptr == characteristic.d_ptr + && d_ptr->characteristicList.contains(characteristic.attributeHandle())) { + return true; + } + + return false; +} + +/*! + Writes \a newValue as value for the \a characteristic. If the operation is successful + the \l characteristicChanged() signal will be emitted. \a newValue must contain the + hexadecimal representation of new value. + + A characteristic can only be written if this service is in the \l ServiceDiscovered state + and \a characteristic is writable. + */ +void QLowEnergyService::writeCharacteristic( + const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) +{ + //TODO check behavior when writing to WriteNoResponse characteristic + //TODO check behavior when writing to WriteSigned characteristic + //TODO add support for write long characteristic value (newValue.size() > MTU - 3) + Q_D(QLowEnergyService); + + // not a characteristic of this service + if (!contains(characteristic)) + return; + + // don't write if we don't have to + if (characteristic.value() == newValue) + return; + + // don't write write-protected or undiscovered characteristic + if (!(characteristic.properties() & QLowEnergyCharacteristic::Write) + || state() != ServiceDiscovered) { + d->setError(QLowEnergyService::OperationError); + return; + } + + if (!d->controller) + return; + + d->controller->writeCharacteristic(characteristic.d_ptr, + characteristic.attributeHandle(), + newValue); +} + +/*! + Returns \c true if \a descriptor belongs to this service; otherwise \c false. + */ +bool QLowEnergyService::contains(const QLowEnergyDescriptor &descriptor) const +{ + if (descriptor.d_ptr.isNull() || !descriptor.data) + return false; + + const QLowEnergyHandle charHandle = descriptor.characteristicHandle(); + if (!charHandle) + return false; + + if (d_ptr == descriptor.d_ptr + && d_ptr->characteristicList.contains(charHandle) + && d_ptr->characteristicList[charHandle].descriptorList.contains(descriptor.handle())) + { + return true; + } + + return false; +} + +/*! + Writes \a newValue as value for \a descriptor. If the operation is successful + the \l descriptorChanged() signal is emitted. \a newValue must contain the + hexadecimal representation of new value. + + A descriptor can only be written if this service is in the \l ServiceDiscovered state + and \a characteristic is writable. + */ +void QLowEnergyService::writeDescriptor(const QLowEnergyDescriptor &descriptor, + const QByteArray &newValue) +{ + //TODO not all descriptors are writable (how to deal with write errors) + Q_D(QLowEnergyService); + + if (!contains(descriptor)) + return; + + if (descriptor.value() == newValue) + return; + + if (state() != ServiceDiscovered || !d->controller) { + d->setError(QLowEnergyService::OperationError); + return; + } + + d->controller->writeDescriptor(descriptor.d_ptr, + descriptor.characteristicHandle(), + descriptor.handle(), + newValue); +} + + + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergyservice.h b/src/bluetooth/qlowenergyservice.h new file mode 100644 index 00000000..d7033224 --- /dev/null +++ b/src/bluetooth/qlowenergyservice.h @@ -0,0 +1,121 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#ifndef QLOWENERGYSERVICE_H +#define QLOWENERGYSERVICE_H + +#include <QtBluetooth/QBluetoothAddress> +#include <QtBluetooth/QBluetoothUuid> +#include <QtBluetooth/QLowEnergyCharacteristic> + +QT_BEGIN_NAMESPACE + +class QLowEnergyServicePrivate; +class QLowEnergyControllerPrivate; +class Q_BLUETOOTH_EXPORT QLowEnergyService : public QObject +{ + Q_OBJECT +public: + enum ServiceType { + PrimaryService = 0x0001, + IncludedService = 0x0002 + }; + Q_DECLARE_FLAGS(ServiceTypes, ServiceType) + + enum ServiceError { + NoError = 0, + ServiceNotValidError, + OperationError, + CharacteristicWriteError, // emitted when writeCharacteristic() failed + DescriptorWriteError // emitted when writeDescriptor() failed + }; + + enum ServiceState { + InvalidService = 0, // when underlying controller disconnects + DiscoveryRequired, // we know start/end handle but nothing more + DiscoveringServices,// discoverDetails() called and running + ServiceDiscovered, // all details have been synchronized + }; + + ~QLowEnergyService(); + + QList<QBluetoothUuid> includedServices() const; + + QLowEnergyService::ServiceTypes type() const; + QLowEnergyService::ServiceState state() const; + + QLowEnergyCharacteristic characteristic(const QBluetoothUuid &uuid) const; + QList<QLowEnergyCharacteristic> characteristics() const; + QBluetoothUuid serviceUuid() const; + QString serviceName() const; + + void discoverDetails(); + + ServiceError error() const; + + bool contains(const QLowEnergyCharacteristic &characteristic) const; + void writeCharacteristic(const QLowEnergyCharacteristic &characteristic, + const QByteArray &newValue); + + bool contains(const QLowEnergyDescriptor &descriptor) const; + void writeDescriptor(const QLowEnergyDescriptor &descriptor, + const QByteArray &newValue); + +Q_SIGNALS: + void stateChanged(QLowEnergyService::ServiceState newState); + void characteristicChanged(const QLowEnergyCharacteristic &info, + const QByteArray &value); + void descriptorChanged(const QLowEnergyDescriptor &info, + const QByteArray &value); + void error(QLowEnergyService::ServiceError error); + +private: + Q_DECLARE_PRIVATE(QLowEnergyService) + QSharedPointer<QLowEnergyServicePrivate> d_ptr; + + // QLowEnergyController is the factory for this class + friend class QLowEnergyController; + QLowEnergyService(QSharedPointer<QLowEnergyServicePrivate> p, + QObject *parent = 0); +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYSERVICE_H diff --git a/src/bluetooth/qlowenergyserviceinfo.cpp b/src/bluetooth/qlowenergyserviceinfo.cpp new file mode 100644 index 00000000..572e71d7 --- /dev/null +++ b/src/bluetooth/qlowenergyserviceinfo.cpp @@ -0,0 +1,180 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergyserviceinfo.h" +#include "qlowenergyserviceinfo_p.h" +#include <QtCore/QCoreApplication> + +QT_BEGIN_NAMESPACE + +/*! + \class QLowEnergyServiceInfo + \inmodule QtBluetooth + \brief The QLowEnergyServiceInfo class stores information about the Bluetooth + Low Energy service. + \since 5.4 + + QLowEnergyServiceInfo provides information about a Bluetooth Low Energy + service's name, device, UUID, connection status, service type, handle + and characteristics. A Bluetooth Low Energy device can have one or more + low energy services. Each low energy service contains one or more + characteristics. The class is used with the QLowEnergyController + class. It is necessary to connect to the service first in order + to get the all service information and characteristics. +*/ + +/*! + \enum QLowEnergyServiceInfo::ServiceType + + This enum describes the type of the service. One LE device can have one or more primary services. + + \value PrimaryService The primary service. The primary service can have one or + more included services. + \value IncludedService The included service by primary services. +*/ + +/*! + Construct a new QLowEnergyServiceInfo. +*/ +QLowEnergyServiceInfo::QLowEnergyServiceInfo(): + d_ptr(QSharedPointer<QLowEnergyServiceInfoPrivate>(new QLowEnergyServiceInfoPrivate)) +{ + +} + +/*! + Construct a new QLowEnergyServiceInfo object with the given \a uuid. + + Based on uuid, corresponsing service name is given. +*/ +QLowEnergyServiceInfo::QLowEnergyServiceInfo(const QBluetoothUuid &uuid): + d_ptr(QSharedPointer<QLowEnergyServiceInfoPrivate>(new QLowEnergyServiceInfoPrivate)) +{ + d_ptr->uuid = QBluetoothUuid(uuid); +} + +/*! + Construct a new QLowEnergyServiceInfo that is a copy of \a other. + + The two copies continue to share the same underlying data which does not detach + upon write. +*/ +QLowEnergyServiceInfo::QLowEnergyServiceInfo(const QLowEnergyServiceInfo &other): + d_ptr(other.d_ptr) +{ + +} + +/*! + Destroys the QLowEnergyServiceInfo object. +*/ +QLowEnergyServiceInfo::~QLowEnergyServiceInfo() +{ + +} + +/*! + Returns the gatt service uuid. +*/ +QBluetoothUuid QLowEnergyServiceInfo::serviceUuid() const +{ + return d_ptr->uuid; +} + +/*! + Returns the service name. +*/ +QString QLowEnergyServiceInfo::serviceName() const +{ + bool ok = false; + quint16 clsId = d_ptr->uuid.toUInt16(&ok); + if (ok) { + QBluetoothUuid::ServiceClassUuid id + = static_cast<QBluetoothUuid::ServiceClassUuid>(clsId); + return QBluetoothUuid::serviceClassToString(id); + } + return qApp ? + qApp->translate("QBluetoothServiceDiscoveryAgent", "Unknown Service") : + QStringLiteral("Unknown Service"); +} + +/*! + Returns a copy of \a other and assigns it to this QLowEnergyServiceInfo object. + The two copies continue to share the same service and registration details. +*/ +QLowEnergyServiceInfo &QLowEnergyServiceInfo::operator=(const QLowEnergyServiceInfo &other) +{ + d_ptr = other.d_ptr; + return *this; +} + +/*! + Returns the address of the Bluetooth device that provides this service. +*/ +QBluetoothDeviceInfo QLowEnergyServiceInfo::device() const +{ + return d_ptr->deviceInfo; +} + +/*! + Sets the Bluetooth device that provides this service to \a device. +*/ +void QLowEnergyServiceInfo::setDevice(const QBluetoothDeviceInfo &device) +{ + d_ptr->deviceInfo = device; +} + +/*! + Returns true if the QLowEnergyServiceInfo object is valid, otherwise returns false. +*/ +bool QLowEnergyServiceInfo::isValid() const +{ + if (d_ptr->uuid == QBluetoothUuid()) + return false; + if (!d_ptr->deviceInfo.isValid()) + return false; + return true; +} + +QT_END_NAMESPACE + + diff --git a/src/bluetooth/qlowenergyserviceinfo.h b/src/bluetooth/qlowenergyserviceinfo.h new file mode 100644 index 00000000..43ddff30 --- /dev/null +++ b/src/bluetooth/qlowenergyserviceinfo.h @@ -0,0 +1,81 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYSERVICEINFO_H +#define QLOWENERGYSERVICEINFO_H +#include <QtCore/QSharedPointer> +#include <QtBluetooth/QBluetoothAddress> +#include <QtBluetooth/QBluetoothDeviceInfo> +#include <QtBluetooth/QBluetoothUuid> + +QT_BEGIN_NAMESPACE + +class QLowEnergyServiceInfoPrivate; + +class Q_BLUETOOTH_EXPORT QLowEnergyServiceInfo +{ +public: + QLowEnergyServiceInfo(); + QLowEnergyServiceInfo(const QBluetoothUuid &uuid); + QLowEnergyServiceInfo(const QLowEnergyServiceInfo &other); + + ~QLowEnergyServiceInfo(); + + QLowEnergyServiceInfo &operator=(const QLowEnergyServiceInfo &other); + + void setDevice(const QBluetoothDeviceInfo &info); + QBluetoothDeviceInfo device() const; + + QBluetoothUuid serviceUuid() const; + + QString serviceName() const; + + bool isValid() const; + +protected: + QSharedPointer<QLowEnergyServiceInfoPrivate> d_ptr; + +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYSERVICEINFO_H diff --git a/src/bluetooth/qlowenergyserviceinfo_p.h b/src/bluetooth/qlowenergyserviceinfo_p.h new file mode 100644 index 00000000..6d1d3c73 --- /dev/null +++ b/src/bluetooth/qlowenergyserviceinfo_p.h @@ -0,0 +1,58 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYSERVICEINFO_P_H +#define QLOWENERGYSERVICEINFO_P_H +#include "qlowenergyserviceinfo.h" + +QT_BEGIN_NAMESPACE + +class QLowEnergyServiceInfoPrivate +{ +public: + QBluetoothUuid uuid; + QBluetoothDeviceInfo deviceInfo; +}; + +QT_END_NAMESPACE + +#endif // QLOWENERGYSERVICEINFO_P_H diff --git a/src/bluetooth/qlowenergyserviceprivate.cpp b/src/bluetooth/qlowenergyserviceprivate.cpp new file mode 100644 index 00000000..4768582d --- /dev/null +++ b/src/bluetooth/qlowenergyserviceprivate.cpp @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qlowenergyserviceprivate_p.h" + +QT_BEGIN_NAMESPACE + +QLowEnergyServicePrivate::QLowEnergyServicePrivate(QObject *parent) : + QObject(parent), + type(QLowEnergyService::PrimaryService), + state(QLowEnergyService::InvalidService), + lastError(QLowEnergyService::NoError) +{ +} + +QLowEnergyServicePrivate::~QLowEnergyServicePrivate() +{ +} + +void QLowEnergyServicePrivate::setController(QLowEnergyControllerPrivate *control) +{ + controller = control; + + if (control) + setState(QLowEnergyService::DiscoveryRequired); + else + setState(QLowEnergyService::InvalidService); +} + +void QLowEnergyServicePrivate::setError(QLowEnergyService::ServiceError newError) +{ + lastError = newError; + emit error(newError); +} + +void QLowEnergyServicePrivate::setState(QLowEnergyService::ServiceState newState) +{ + state = newState; + emit stateChanged(newState); +} + +QT_END_NAMESPACE diff --git a/src/bluetooth/qlowenergyserviceprivate_p.h b/src/bluetooth/qlowenergyserviceprivate_p.h new file mode 100644 index 00000000..cc0fafee --- /dev/null +++ b/src/bluetooth/qlowenergyserviceprivate_p.h @@ -0,0 +1,113 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QLOWENERGYSERVICEPRIVATE_P_H +#define QLOWENERGYSERVICEPRIVATE_P_H + +#include <QtCore/QObject> +#include <QtCore/QPointer> +#include <QtBluetooth/qbluetooth.h> +#include <QtBluetooth/QLowEnergyService> +#include <QtBluetooth/QLowEnergyCharacteristic> + +#include "qlowenergycontroller_p.h" + +QT_BEGIN_NAMESPACE + +class QLowEnergyServicePrivate : public QObject +{ + Q_OBJECT +public: + explicit QLowEnergyServicePrivate(QObject *parent = 0); + ~QLowEnergyServicePrivate(); + + struct DescData { + QByteArray value; + QBluetoothUuid uuid; + }; + + struct CharData { + QLowEnergyHandle valueHandle; + QBluetoothUuid uuid; + QLowEnergyCharacteristic::PropertyTypes properties; + QByteArray value; + QHash<QLowEnergyHandle, DescData> descriptorList; + }; + + enum GattAttributeTypes { + PrimaryService = 0x2800, + SecondaryService = 0x2801, + IncludeAttribute = 0x2802, + Characteristic = 0x2803 + }; + + void setController(QLowEnergyControllerPrivate* control); + void setError(QLowEnergyService::ServiceError newError); + void setState(QLowEnergyService::ServiceState newState); + +signals: + void stateChanged(QLowEnergyService::ServiceState newState); + void error(QLowEnergyService::ServiceError error); + void characteristicChanged(const QLowEnergyCharacteristic &characteristic, + const QByteArray &newValue); + void descriptorChanged(const QLowEnergyDescriptor &descriptor, + const QByteArray &newValue); + +public: + QLowEnergyHandle startHandle; + QLowEnergyHandle endHandle; + + QBluetoothUuid uuid; + QList<QBluetoothUuid> includedServices; + QLowEnergyService::ServiceTypes type; + QLowEnergyService::ServiceState state; + QLowEnergyService::ServiceError lastError; + + QHash<QLowEnergyHandle, CharData> characteristicList; + + QPointer<QLowEnergyControllerPrivate> controller; +}; + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QSharedPointer<QLowEnergyServicePrivate>) + +#endif // QLOWENERGYSERVICEPRIVATE_P_H diff --git a/src/nfc/qnx/qnxnfcmanager.cpp b/src/nfc/qnx/qnxnfcmanager.cpp index edc2d2be..2ba1029b 100644 --- a/src/nfc/qnx/qnxnfcmanager.cpp +++ b/src/nfc/qnx/qnxnfcmanager.cpp @@ -44,6 +44,7 @@ #include <QMetaObject> #include "../qllcpsocket_qnx_p.h" #include <QCoreApplication> +#include <QStringList> QT_BEGIN_NAMESPACE diff --git a/sync.profile b/sync.profile index 1204b458..0ceddf44 100644 --- a/sync.profile +++ b/sync.profile @@ -17,3 +17,6 @@ "qtxmlpatterns" => "", "qtandroidextras" => "", ); + +my @gato_headers = ("gatoattclient.h", "gatoperipheral.h"); +@ignore_for_master_contents = ( @gato_headers ); diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 5a703944..8966e5d9 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -16,7 +16,11 @@ qtHaveModule(bluetooth) { qbluetoothtransfermanager \ qbluetoothtransferrequest \ qbluetoothuuid \ - qbluetoothserver + qbluetoothserver \ + qlowenergycharacteristic \ + qlowenergydescriptor \ + qlowenergyserviceinfo \ + qlowenergycontroller } qtHaveModule(nfc) { diff --git a/tests/auto/qbluetoothdeviceinfo/qbluetoothdeviceinfo.pro b/tests/auto/qbluetoothdeviceinfo/qbluetoothdeviceinfo.pro index 43ca52ed..e33125c2 100644 --- a/tests/auto/qbluetoothdeviceinfo/qbluetoothdeviceinfo.pro +++ b/tests/auto/qbluetoothdeviceinfo/qbluetoothdeviceinfo.pro @@ -4,3 +4,6 @@ CONFIG += testcase QT = core concurrent bluetooth testlib DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 +blackberry { + LIBS += -lbtapi +} diff --git a/tests/auto/qbluetoothdeviceinfo/tst_qbluetoothdeviceinfo.cpp b/tests/auto/qbluetoothdeviceinfo/tst_qbluetoothdeviceinfo.cpp index adc61199..023c1390 100644 --- a/tests/auto/qbluetoothdeviceinfo/tst_qbluetoothdeviceinfo.cpp +++ b/tests/auto/qbluetoothdeviceinfo/tst_qbluetoothdeviceinfo.cpp @@ -52,6 +52,7 @@ QT_USE_NAMESPACE Q_DECLARE_METATYPE(QBluetoothDeviceInfo::ServiceClasses) Q_DECLARE_METATYPE(QBluetoothDeviceInfo::MajorDeviceClass) +Q_DECLARE_METATYPE(QBluetoothDeviceInfo::CoreConfiguration) class tst_QBluetoothDeviceInfo : public QObject { @@ -101,6 +102,7 @@ void tst_QBluetoothDeviceInfo::tst_construction_data() QTest::addColumn<QBluetoothDeviceInfo::ServiceClasses>("serviceClasses"); QTest::addColumn<QBluetoothDeviceInfo::MajorDeviceClass>("majorDeviceClass"); QTest::addColumn<quint8>("minorDeviceClass"); + QTest::addColumn<QBluetoothDeviceInfo::CoreConfiguration>("coreConfiguration"); // bits 12-8 Major // bits 7-2 Minor @@ -110,120 +112,144 @@ void tst_QBluetoothDeviceInfo::tst_construction_data() << quint32(0x000000) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::MiscellaneousDevice - << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous); + << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous) + << QBluetoothDeviceInfo::BaseRateCoreConfiguration; QTest::newRow("0x000100 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000100) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ComputerDevice - << quint8(QBluetoothDeviceInfo::UncategorizedComputer); + << quint8(QBluetoothDeviceInfo::UncategorizedComputer) + << QBluetoothDeviceInfo::BaseRateCoreConfiguration; QTest::newRow("0x000104 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000104) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ComputerDevice - << quint8(QBluetoothDeviceInfo::DesktopComputer); + << quint8(QBluetoothDeviceInfo::DesktopComputer) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000118 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000118) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ComputerDevice - << quint8(QBluetoothDeviceInfo::WearableComputer); + << quint8(QBluetoothDeviceInfo::WearableComputer) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000200 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000200) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::PhoneDevice - << quint8(QBluetoothDeviceInfo::UncategorizedPhone); + << quint8(QBluetoothDeviceInfo::UncategorizedPhone) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000204 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000204) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::PhoneDevice - << quint8(QBluetoothDeviceInfo::CellularPhone); + << quint8(QBluetoothDeviceInfo::CellularPhone) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000214 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000214) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::PhoneDevice - << quint8(QBluetoothDeviceInfo::CommonIsdnAccessPhone); + << quint8(QBluetoothDeviceInfo::CommonIsdnAccessPhone) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000300 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000300) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::LANAccessDevice - << quint8(QBluetoothDeviceInfo::NetworkFullService); + << quint8(QBluetoothDeviceInfo::NetworkFullService) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000320 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000320) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::LANAccessDevice - << quint8(QBluetoothDeviceInfo::NetworkLoadFactorOne); + << quint8(QBluetoothDeviceInfo::NetworkLoadFactorOne) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x0003E0 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x0003E0) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::LANAccessDevice - << quint8(QBluetoothDeviceInfo::NetworkNoService); + << quint8(QBluetoothDeviceInfo::NetworkNoService) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000400 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000400) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::AudioVideoDevice - << quint8(QBluetoothDeviceInfo::UncategorizedAudioVideoDevice); + << quint8(QBluetoothDeviceInfo::UncategorizedAudioVideoDevice) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000448 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000448) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::AudioVideoDevice - << quint8(QBluetoothDeviceInfo::GamingDevice); + << quint8(QBluetoothDeviceInfo::GamingDevice) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000500 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000500) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::PeripheralDevice - << quint8(QBluetoothDeviceInfo::UncategorizedPeripheral); + << quint8(QBluetoothDeviceInfo::UncategorizedPeripheral) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x0005D8 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x0005D8) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::PeripheralDevice - << quint8(QBluetoothDeviceInfo::KeyboardWithPointingDevicePeripheral | QBluetoothDeviceInfo::CardReaderPeripheral); + << quint8(QBluetoothDeviceInfo::KeyboardWithPointingDevicePeripheral | QBluetoothDeviceInfo::CardReaderPeripheral) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000600 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000600) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ImagingDevice - << quint8(QBluetoothDeviceInfo::UncategorizedImagingDevice); + << quint8(QBluetoothDeviceInfo::UncategorizedImagingDevice) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; QTest::newRow("0x000680 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000680) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ImagingDevice - << quint8(QBluetoothDeviceInfo::ImagePrinter); + << quint8(QBluetoothDeviceInfo::ImagePrinter) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x000700 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000700) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::WearableDevice - << quint8(QBluetoothDeviceInfo::UncategorizedWearableDevice); + << quint8(QBluetoothDeviceInfo::UncategorizedWearableDevice) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x000714 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000714) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::WearableDevice - << quint8(QBluetoothDeviceInfo::WearableGlasses); + << quint8(QBluetoothDeviceInfo::WearableGlasses) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x000800 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000800) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ToyDevice - << quint8(QBluetoothDeviceInfo::UncategorizedToy); + << quint8(QBluetoothDeviceInfo::UncategorizedToy) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x000814 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x000814) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::ToyDevice - << quint8(QBluetoothDeviceInfo::ToyGame); + << quint8(QBluetoothDeviceInfo::ToyGame) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x001f00 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x001f00) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::NoService) << QBluetoothDeviceInfo::UncategorizedDevice - << quint8(0); + << quint8(0) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x002000 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x002000) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::PositioningService) << QBluetoothDeviceInfo::MiscellaneousDevice - << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous); + << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0x100000 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0x100000) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::InformationService) << QBluetoothDeviceInfo::MiscellaneousDevice - << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous); + << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; QTest::newRow("0xFFE000 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" << quint32(0xFFE000) << QBluetoothDeviceInfo::ServiceClasses(QBluetoothDeviceInfo::AllServices) << QBluetoothDeviceInfo::MiscellaneousDevice - << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous); + << quint8(QBluetoothDeviceInfo::UncategorizedMiscellaneous) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; } void tst_QBluetoothDeviceInfo::tst_construction() @@ -241,6 +267,7 @@ void tst_QBluetoothDeviceInfo::tst_construction() QFETCH(QBluetoothDeviceInfo::ServiceClasses, serviceClasses); QFETCH(QBluetoothDeviceInfo::MajorDeviceClass, majorDeviceClass); QFETCH(quint8, minorDeviceClass); + QFETCH(QBluetoothDeviceInfo::CoreConfiguration, coreConfiguration); QBluetoothDeviceInfo deviceInfo(address, name, classOfDevice); @@ -251,9 +278,12 @@ void tst_QBluetoothDeviceInfo::tst_construction() QCOMPARE(deviceInfo.serviceClasses(), serviceClasses); QCOMPARE(deviceInfo.majorDeviceClass(), majorDeviceClass); QCOMPARE(deviceInfo.minorDeviceClass(), minorDeviceClass); + QCOMPARE(deviceInfo.coreConfigurations(), QBluetoothDeviceInfo::BaseRateCoreConfiguration); - QBluetoothDeviceInfo copyInfo(deviceInfo); + deviceInfo.setCoreConfigurations(coreConfiguration); + QCOMPARE(deviceInfo.coreConfigurations(), coreConfiguration); + QBluetoothDeviceInfo copyInfo(deviceInfo); QVERIFY(copyInfo.isValid()); QCOMPARE(copyInfo.address(), address); @@ -261,6 +291,7 @@ void tst_QBluetoothDeviceInfo::tst_construction() QCOMPARE(copyInfo.serviceClasses(), serviceClasses); QCOMPARE(copyInfo.majorDeviceClass(), majorDeviceClass); QCOMPARE(copyInfo.minorDeviceClass(), minorDeviceClass); + QCOMPARE(copyInfo.coreConfigurations(), coreConfiguration); } } @@ -277,8 +308,10 @@ void tst_QBluetoothDeviceInfo::tst_assignment() QFETCH(QBluetoothDeviceInfo::ServiceClasses, serviceClasses); QFETCH(QBluetoothDeviceInfo::MajorDeviceClass, majorDeviceClass); QFETCH(quint8, minorDeviceClass); + QFETCH(QBluetoothDeviceInfo::CoreConfiguration, coreConfiguration); QBluetoothDeviceInfo deviceInfo(address, name, classOfDevice); + deviceInfo.setCoreConfigurations(coreConfiguration); QVERIFY(deviceInfo.isValid()); @@ -292,6 +325,7 @@ void tst_QBluetoothDeviceInfo::tst_assignment() QCOMPARE(copyInfo.serviceClasses(), serviceClasses); QCOMPARE(copyInfo.majorDeviceClass(), majorDeviceClass); QCOMPARE(copyInfo.minorDeviceClass(), minorDeviceClass); + QCOMPARE(copyInfo.coreConfigurations(), coreConfiguration); } { @@ -308,6 +342,7 @@ void tst_QBluetoothDeviceInfo::tst_assignment() QCOMPARE(copyInfo.serviceClasses(), serviceClasses); QCOMPARE(copyInfo.majorDeviceClass(), majorDeviceClass); QCOMPARE(copyInfo.minorDeviceClass(), minorDeviceClass); + QCOMPARE(copyInfo.coreConfigurations(), coreConfiguration); } { @@ -333,6 +368,8 @@ void tst_QBluetoothDeviceInfo::tst_assignment() QCOMPARE(copyInfo2.majorDeviceClass(), majorDeviceClass); QCOMPARE(copyInfo1.minorDeviceClass(), minorDeviceClass); QCOMPARE(copyInfo2.minorDeviceClass(), minorDeviceClass); + QCOMPARE(copyInfo1.coreConfigurations(), coreConfiguration); + QCOMPARE(copyInfo2.coreConfigurations(), coreConfiguration); } { diff --git a/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro b/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro index 7b8ee74a..cdf8a78b 100644 --- a/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro +++ b/tests/auto/qbluetoothservicediscoveryagent/qbluetoothservicediscoveryagent.pro @@ -3,5 +3,8 @@ TARGET = tst_qbluetoothservicediscoveryagent CONFIG += testcase QT = core concurrent bluetooth testlib +blackberry { + LIBS += -lbtapi +} DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 diff --git a/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp b/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp index 6aded142..398fb1ad 100644 --- a/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp +++ b/tests/auto/qbluetoothservicediscoveryagent/tst_qbluetoothservicediscoveryagent.cpp @@ -51,11 +51,13 @@ #include <qbluetoothlocaldevice.h> #include <qbluetoothserver.h> #include <qbluetoothserviceinfo.h> +#include <qlowenergyserviceinfo.h> QT_USE_NAMESPACE Q_DECLARE_METATYPE(QBluetoothDeviceInfo) Q_DECLARE_METATYPE(QBluetoothServiceDiscoveryAgent::Error) +Q_DECLARE_METATYPE(QLowEnergyServiceInfo) // Maximum time to for bluetooth device scan const int MaxScanTime = 5 * 60 * 1000; // 5 minutes in ms @@ -71,6 +73,7 @@ public: public slots: void deviceDiscoveryDebug(const QBluetoothDeviceInfo &info); void serviceDiscoveryDebug(const QBluetoothServiceInfo &info); + void leServiceDiscoveryDebug(const QLowEnergyServiceInfo &info); void serviceError(const QBluetoothServiceDiscoveryAgent::Error err); private slots: @@ -100,6 +103,7 @@ tst_QBluetoothServiceDiscoveryAgent::tst_QBluetoothServiceDiscoveryAgent() qRegisterMetaType<QBluetoothDeviceInfo>("QBluetoothDeviceInfo"); qRegisterMetaType<QBluetoothServiceInfo>("QBluetoothServiceInfo"); + qRegisterMetaType<QLowEnergyServiceInfo>("QLowEnergyServiceInfo"); qRegisterMetaType<QList<QBluetoothUuid> >("QList<QBluetoothUuid>"); qRegisterMetaType<QBluetoothServiceDiscoveryAgent::Error>("QBluetoothServiceDiscoveryAgent::Error"); qRegisterMetaType<QBluetoothDeviceDiscoveryAgent::Error>("QBluetoothDeviceDiscoveryAgent::Error"); @@ -180,6 +184,14 @@ void tst_QBluetoothServiceDiscoveryAgent::serviceDiscoveryDebug(const QBluetooth qDebug() << "\tRFCOMM server channel:" << info.serverChannel(); } +void tst_QBluetoothServiceDiscoveryAgent::leServiceDiscoveryDebug(const QLowEnergyServiceInfo &info) +{ + qDebug() << "Discovered LE service on" + << info.device().name() << info.device().address().toString(); + qDebug() << "\tService name:" << info.serviceName(); + qDebug() << "\tUUID:" << info.serviceUuid(); +} + static void dumpAttributeVariant(const QVariant &var, const QString indent) { if (!var.isValid()) { @@ -316,7 +328,7 @@ void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscoveryAdapters() QVERIFY(serviceInfo.registerService()); QVERIFY(server.isListening()); - qDebug() << "Scanning address" << addresses[0].toString(); + qDebug() << "Scanning address " << addresses[0].toString(); QBluetoothServiceDiscoveryAgent discoveryAgent(addresses[1]); bool setAddress = discoveryAgent.setRemoteAddress(addresses[0]); @@ -364,7 +376,6 @@ void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery() QFETCH(QBluetoothServiceDiscoveryAgent::Error, serviceDiscoveryError); QBluetoothLocalDevice localDevice; - qDebug() << "Scanning address" << deviceInfo.address().toString(); QBluetoothServiceDiscoveryAgent discoveryAgent(localDevice.address()); bool setAddress = discoveryAgent.setRemoteAddress(deviceInfo.address()); @@ -384,8 +395,11 @@ void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery() QSignalSpy finishedSpy(&discoveryAgent, SIGNAL(finished())); QSignalSpy errorSpy(&discoveryAgent, SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error))); QSignalSpy discoveredSpy(&discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo))); + QSignalSpy leDiscoveredSpy(&discoveryAgent, SIGNAL(serviceDiscovered(QLowEnergyServiceInfo))); // connect(&discoveryAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)), // this, SLOT(serviceDiscoveryDebug(QBluetoothServiceInfo))); +// connect(&discoveryAgent, SIGNAL(serviceDiscovered(QLowEnergyServiceInfo)), +// this, SLOT(leServiceDiscoveryDebug(QLowEnergyServiceInfo))); connect(&discoveryAgent, SIGNAL(error(QBluetoothServiceDiscoveryAgent::Error)), this, SLOT(serviceError(QBluetoothServiceDiscoveryAgent::Error))); @@ -451,6 +465,21 @@ void tst_QBluetoothServiceDiscoveryAgent::tst_serviceDiscovery() } + while (!leDiscoveredSpy.isEmpty()) { + const QVariant v = leDiscoveredSpy.takeFirst().at(0); + if (v.userType() == qMetaTypeId<QLowEnergyServiceInfo>()) + { + const QLowEnergyServiceInfo info = + *reinterpret_cast<const QLowEnergyServiceInfo*>(v.constData()); + + QVERIFY(info.isValid()); + QVERIFY(info.device().coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration); + } else { + QFAIL("Unknown type returned by service discovery"); + } + + } + QVERIFY(discoveryAgent.discoveredServices().count() != 0); discoveryAgent.clear(); QVERIFY(discoveryAgent.discoveredServices().count() == 0); diff --git a/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro b/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro new file mode 100644 index 00000000..33302d60 --- /dev/null +++ b/tests/auto/qlowenergycharacteristic/qlowenergycharacteristic.pro @@ -0,0 +1,9 @@ +SOURCES += tst_qlowenergycharacteristic.cpp +TARGET = tst_qlowenergycharacteristic +CONFIG += testcase + +QT = core bluetooth testlib +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 +blackberry { + LIBS += -lbtapi +} diff --git a/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp b/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp new file mode 100644 index 00000000..9ecddbd5 --- /dev/null +++ b/tests/auto/qlowenergycharacteristic/tst_qlowenergycharacteristic.cpp @@ -0,0 +1,358 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QUuid> + +#include <QDebug> + +#include <QBluetoothDeviceDiscoveryAgent> +#include <QLowEnergyCharacteristic> +#include <QLowEnergyController> +#include <QBluetoothLocalDevice> + +QT_USE_NAMESPACE + +class tst_QLowEnergyCharacteristic : public QObject +{ + Q_OBJECT + +public: + tst_QLowEnergyCharacteristic(); + ~tst_QLowEnergyCharacteristic(); + +protected slots: + void deviceDiscovered(const QBluetoothDeviceInfo &info); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void tst_constructionDefault(); + void tst_assignCompare(); + +private: + QSet<QString> remoteLeDevices; + QLowEnergyController *globalControl; + QLowEnergyService *globalService; +}; + +tst_QLowEnergyCharacteristic::tst_QLowEnergyCharacteristic() : + globalControl(0), globalService(0) +{ + QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); +} + +tst_QLowEnergyCharacteristic::~tst_QLowEnergyCharacteristic() +{ +} + +void tst_QLowEnergyCharacteristic::initTestCase() +{ + if (QBluetoothLocalDevice::allDevices().isEmpty()) { + qWarning("No remote device discovered."); + return; + } + + // start Bluetooth if not started + QBluetoothLocalDevice device; + device.powerOn(); + + // find an arbitrary low energy device in vincinity + // find an arbitrary service with characteristic + QBluetoothDeviceDiscoveryAgent *devAgent = new QBluetoothDeviceDiscoveryAgent(this); + connect(devAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), + this, SLOT(deviceDiscovered(QBluetoothDeviceInfo))); + + QSignalSpy errorSpy(devAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); + QVERIFY(errorSpy.isValid()); + QVERIFY(errorSpy.isEmpty()); + + QSignalSpy spy(devAgent, SIGNAL(finished())); + QVERIFY(spy.isValid()); + QVERIFY(spy.isEmpty()); + + devAgent->start(); + QTRY_VERIFY_WITH_TIMEOUT(spy.count() > 0, 50000); + + // find first service with descriptor + QLowEnergyController *controller = 0; + foreach (const QString &remoteDevice, remoteLeDevices.toList()) { + controller = new QLowEnergyController(QBluetoothAddress(remoteDevice), this); + qDebug() << "Connecting to" << remoteDevice; + controller->connectToDevice(); + QTRY_IMPL(controller->state() != QLowEnergyController::ConnectingState, + 10000); + if (controller->state() != QLowEnergyController::ConnectedState) { + // any error and we skip + delete controller; + qDebug() << "Skipping device"; + continue; + } + + QSignalSpy discoveryFinishedSpy(controller, SIGNAL(discoveryFinished())); + controller->discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + foreach (const QBluetoothUuid &leServiceUuid, controller->services()) { + QLowEnergyService *leService = controller->createServiceObject(leServiceUuid, this); + if (!leService) + continue; + + leService->discoverDetails(); + QTRY_VERIFY_WITH_TIMEOUT( + leService->state() == QLowEnergyService::ServiceDiscovered, 10000); + + QList<QLowEnergyCharacteristic> chars = leService->characteristics(); + foreach (const QLowEnergyCharacteristic &ch, chars) { + if (!ch.descriptors().isEmpty()) { + globalService = leService; + globalControl = controller; + qWarning() << "Found service with descriptor" << remoteDevice + << globalService->serviceName() << globalService->serviceUuid(); + break; + } + } + + if (globalControl) + break; + else + delete leService; + } + + if (globalControl) + break; + + delete controller; + } + + if (!globalControl) { + qWarning() << "Test limited due to missing remote QLowEnergyDescriptor." + << "Please ensure the Bluetooth Low Energy device is advertising its services."; + } +} + +void tst_QLowEnergyCharacteristic::cleanupTestCase() +{ + if (globalControl) + globalControl->disconnectFromDevice(); +} + +void tst_QLowEnergyCharacteristic::deviceDiscovered(const QBluetoothDeviceInfo &info) +{ + if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) + remoteLeDevices.insert(info.address().toString()); +} + +void tst_QLowEnergyCharacteristic::tst_constructionDefault() +{ + QLowEnergyCharacteristic characteristic; + QVERIFY(!characteristic.isValid()); + QCOMPARE(characteristic.value(), QByteArray()); + QVERIFY(characteristic.uuid().isNull()); + QVERIFY(characteristic.handle() == 0); + QCOMPARE(characteristic.name(), QString()); + QCOMPARE(characteristic.descriptors().count(), 0); + QCOMPARE(characteristic.descriptor(QBluetoothUuid()), + QLowEnergyDescriptor()); + QCOMPARE(characteristic.descriptor(QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)), + QLowEnergyDescriptor()); + QCOMPARE(characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), + QLowEnergyDescriptor()); + QCOMPARE(characteristic.properties(), QLowEnergyCharacteristic::Unknown); + + QLowEnergyCharacteristic copyConstructed(characteristic); + QVERIFY(!copyConstructed.isValid()); + QCOMPARE(copyConstructed.value(), QByteArray()); + QVERIFY(copyConstructed.uuid().isNull()); + QVERIFY(copyConstructed.handle() == 0); + QCOMPARE(copyConstructed.name(), QString()); + QCOMPARE(copyConstructed.descriptors().count(), 0); + QCOMPARE(copyConstructed.properties(), QLowEnergyCharacteristic::Unknown); + + QVERIFY(copyConstructed == characteristic); + QVERIFY(characteristic == copyConstructed); + QVERIFY(!(copyConstructed != characteristic)); + QVERIFY(!(characteristic != copyConstructed)); + + QLowEnergyCharacteristic assigned; + + QVERIFY(assigned == characteristic); + QVERIFY(characteristic == assigned); + QVERIFY(!(assigned != characteristic)); + QVERIFY(!(characteristic != assigned)); + + assigned = characteristic; + QVERIFY(!assigned.isValid()); + QCOMPARE(assigned.value(), QByteArray()); + QVERIFY(assigned.uuid().isNull()); + QVERIFY(assigned.handle() == 0); + QCOMPARE(assigned.name(), QString()); + QCOMPARE(assigned.descriptors().count(), 0); + QCOMPARE(assigned.properties(), QLowEnergyCharacteristic::Unknown); + + QVERIFY(assigned == characteristic); + QVERIFY(characteristic == assigned); + QVERIFY(!(assigned != characteristic)); + QVERIFY(!(characteristic != assigned)); +} + +void tst_QLowEnergyCharacteristic::tst_assignCompare() +{ + if (!globalService) + QSKIP("No characteristic found."); + + QLowEnergyCharacteristic target; + QVERIFY(!target.isValid()); + QCOMPARE(target.value(), QByteArray()); + QVERIFY(target.uuid().isNull()); + QVERIFY(target.handle() == 0); + QCOMPARE(target.name(), QString()); + QCOMPARE(target.descriptors().count(), 0); + QCOMPARE(target.properties(), QLowEnergyCharacteristic::Unknown); + + int indexWithDescriptor = -1; + const QList<QLowEnergyCharacteristic> chars = globalService->characteristics(); + QVERIFY(!chars.isEmpty()); + for (int i = 0; i < chars.count(); i++) { + const QLowEnergyCharacteristic specific = + globalService->characteristic(chars[i].uuid()); + QVERIFY(specific.isValid()); + QCOMPARE(specific, chars[i]); + if (chars[i].descriptors().count() > 0) { + indexWithDescriptor = i; + break; + } + } + + if (chars.isEmpty()) + QSKIP("No suitable characteristic found despite prior indication."); + + bool noDescriptors = (indexWithDescriptor == -1); + if (noDescriptors) + indexWithDescriptor = 0; // just choose one + + // test assignment operator + target = chars[indexWithDescriptor]; + QVERIFY(target.isValid()); + QVERIFY(!target.name().isEmpty()); + QVERIFY(target.handle() > 0); + QVERIFY(!target.uuid().isNull()); + QVERIFY(target.properties() != QLowEnergyCharacteristic::Unknown); + if (target.properties() & QLowEnergyCharacteristic::Read) + QVERIFY(!target.value().isEmpty()); + if (!noDescriptors) + QVERIFY(target.descriptors().count() > 0); + + QVERIFY(target == chars[indexWithDescriptor]); + QVERIFY(chars[indexWithDescriptor] == target); + QVERIFY(!(target != chars[indexWithDescriptor])); + QVERIFY(!(chars[indexWithDescriptor] != target)); + + QCOMPARE(target.isValid(), chars[indexWithDescriptor].isValid()); + QCOMPARE(target.name(), chars[indexWithDescriptor].name()); + QCOMPARE(target.handle(), chars[indexWithDescriptor].handle()); + QCOMPARE(target.uuid(), chars[indexWithDescriptor].uuid()); + QCOMPARE(target.value(), chars[indexWithDescriptor].value()); + QCOMPARE(target.properties(), chars[indexWithDescriptor].properties()); + QCOMPARE(target.descriptors().count(), + chars[indexWithDescriptor].descriptors().count()); + for (int i = 0; i < target.descriptors().count(); i++) { + const QLowEnergyDescriptor ref = chars[indexWithDescriptor].descriptors()[i]; + QCOMPARE(target.descriptors()[i].name(), ref.name()); + QCOMPARE(target.descriptors()[i].isValid(), ref.isValid()); + QCOMPARE(target.descriptors()[i].type(), ref.type()); + QCOMPARE(target.descriptors()[i].handle(), ref.handle()); + QCOMPARE(target.descriptors()[i].uuid(), ref.uuid()); + QCOMPARE(target.descriptors()[i].value(), ref.value()); + + const QLowEnergyDescriptor ref2 = chars[indexWithDescriptor].descriptor(ref.uuid()); + QCOMPARE(ref, ref2); + } + + // test copy constructor + QLowEnergyCharacteristic copyConstructed(target); + QCOMPARE(copyConstructed.isValid(), chars[indexWithDescriptor].isValid()); + QCOMPARE(copyConstructed.name(), chars[indexWithDescriptor].name()); + QCOMPARE(copyConstructed.handle(), chars[indexWithDescriptor].handle()); + QCOMPARE(copyConstructed.uuid(), chars[indexWithDescriptor].uuid()); + QCOMPARE(copyConstructed.value(), chars[indexWithDescriptor].value()); + QCOMPARE(copyConstructed.properties(), chars[indexWithDescriptor].properties()); + QCOMPARE(copyConstructed.descriptors().count(), + chars[indexWithDescriptor].descriptors().count()); + + QVERIFY(copyConstructed == target); + QVERIFY(target == copyConstructed); + QVERIFY(!(copyConstructed != target)); + QVERIFY(!(target != copyConstructed)); + + // test invalidation + QLowEnergyCharacteristic invalid; + target = invalid; + QVERIFY(!target.isValid()); + QCOMPARE(target.value(), QByteArray()); + QVERIFY(target.uuid().isNull()); + QVERIFY(target.handle() == 0); + QCOMPARE(target.name(), QString()); + QCOMPARE(target.descriptors().count(), 0); + QCOMPARE(target.properties(), QLowEnergyCharacteristic::Unknown); + + QVERIFY(invalid == target); + QVERIFY(target == invalid); + QVERIFY(!(invalid != target)); + QVERIFY(!(target != invalid)); + + QVERIFY(!(chars[indexWithDescriptor] == target)); + QVERIFY(!(target == chars[indexWithDescriptor])); + QVERIFY(chars[indexWithDescriptor] != target); + QVERIFY(target != chars[indexWithDescriptor]); + + if (chars.count() >= 2) { + // at least two characteristics + QVERIFY(!(chars[0] == chars[1])); + QVERIFY(!(chars[1] == chars[0])); + QVERIFY(chars[0] != chars[1]); + QVERIFY(chars[1] != chars[0]); + } +} + +QTEST_MAIN(tst_QLowEnergyCharacteristic) + +#include "tst_qlowenergycharacteristic.moc" diff --git a/tests/auto/qlowenergycontroller/qlowenergycontroller.pro b/tests/auto/qlowenergycontroller/qlowenergycontroller.pro new file mode 100644 index 00000000..159f27bf --- /dev/null +++ b/tests/auto/qlowenergycontroller/qlowenergycontroller.pro @@ -0,0 +1,6 @@ +QT = core bluetooth testlib +TARGET = tst_qlowenergycontroller +CONFIG += testcase + +SOURCES += tst_qlowenergycontroller.cpp + diff --git a/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp new file mode 100644 index 00000000..7b12e8bc --- /dev/null +++ b/tests/auto/qlowenergycontroller/tst_qlowenergycontroller.cpp @@ -0,0 +1,1807 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QBluetoothLocalDevice> +#include <QBluetoothDeviceDiscoveryAgent> +#include <QBluetoothUuid> +#include <QLowEnergyController> +#include <QLowEnergyCharacteristic> + +#include <QDebug> + +/*! + This test requires a TI sensor tag with Firmware version: 1.5 (Oct 23 2013). + Since revision updates change user strings and even shift handles around + other versions than the above are unlikely to succeed. Please update the + sensor tag before continuing. + + The TI sensor can be updated using the related iOS app. The Android version + doesn't seem to update at this point in time. + */ + +QT_USE_NAMESPACE + +class tst_QLowEnergyController : public QObject +{ + Q_OBJECT + +public: + tst_QLowEnergyController(); + ~tst_QLowEnergyController(); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void tst_connect(); + void tst_concurrentDiscovery(); + void tst_defaultBehavior(); + void tst_writeCharacteristic(); + void tst_writeDescriptor(); + +private: + void verifyServiceProperties(const QLowEnergyService *info); + + QBluetoothDeviceDiscoveryAgent *devAgent; + QBluetoothAddress remoteDevice; + QList<QBluetoothUuid> foundServices; +}; + +Q_DECLARE_METATYPE(QLowEnergyCharacteristic) +Q_DECLARE_METATYPE(QLowEnergyDescriptor) +Q_DECLARE_METATYPE(QLowEnergyService::ServiceError) + +tst_QLowEnergyController::tst_QLowEnergyController() +{ + qRegisterMetaType<QLowEnergyCharacteristic>(); + qRegisterMetaType<QLowEnergyDescriptor>(); + + //QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); + const QString remote = qgetenv("BT_TEST_DEVICE"); + if (!remote.isEmpty()) { + remoteDevice = QBluetoothAddress(remote); + qWarning() << "Using remote device " << remote << " for testing. Ensure that the device is discoverable for pairing requests"; + } else { + qWarning() << "Not using any remote device for testing. Set BT_TEST_DEVICE env to run manual tests involving a remote device"; + } +} + +tst_QLowEnergyController::~tst_QLowEnergyController() +{ + +} + +void tst_QLowEnergyController::initTestCase() +{ + if (remoteDevice.isNull() + || QBluetoothLocalDevice::allDevices().isEmpty()) { + qWarning("No remote device or local adapter found."); + return; + } + + devAgent = new QBluetoothDeviceDiscoveryAgent(this); + + QSignalSpy finishedSpy(devAgent, SIGNAL(finished())); + // there should be no changes yet + QVERIFY(finishedSpy.isValid()); + QVERIFY(finishedSpy.isEmpty()); + + bool deviceFound = false; + devAgent->start(); + QTRY_VERIFY_WITH_TIMEOUT(finishedSpy.count() > 0, 30000); + foreach (const QBluetoothDeviceInfo &info, devAgent->discoveredDevices()) { + if (info.address() == remoteDevice) { + deviceFound = true; + break; + } + } + + QVERIFY2(deviceFound, "Cannot find remote device."); + + // These are the services exported by the TI SensorTag + foundServices << QBluetoothUuid(QString("00001800-0000-1000-8000-00805f9b34fb")); + foundServices << QBluetoothUuid(QString("00001801-0000-1000-8000-00805f9b34fb")); + foundServices << QBluetoothUuid(QString("0000180a-0000-1000-8000-00805f9b34fb")); + foundServices << QBluetoothUuid(QString("0000ffe0-0000-1000-8000-00805f9b34fb")); + foundServices << QBluetoothUuid(QString("f000aa00-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000aa10-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000aa20-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000aa30-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000aa40-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000aa50-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000aa60-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000ccc0-0451-4000-b000-000000000000")); + foundServices << QBluetoothUuid(QString("f000ffc0-0451-4000-b000-000000000000")); +} + +void tst_QLowEnergyController::cleanupTestCase() +{ + +} + +void tst_QLowEnergyController::tst_connect() +{ + QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); + if (localAdapters.isEmpty() || remoteDevice.isNull()) + QSKIP("No local Bluetooth or remote BTLE device found. Skipping test."); + + const QBluetoothAddress localAdapter = localAdapters.at(0).address(); + QLowEnergyController control(remoteDevice); + QSignalSpy connectedSpy(&control, SIGNAL(connected())); + QSignalSpy disconnectedSpy(&control, SIGNAL(disconnected())); + + QCOMPARE(control.localAddress(), localAdapter); + QVERIFY(!control.localAddress().isNull()); + QCOMPARE(control.remoteAddress(), remoteDevice); + QCOMPARE(control.state(), QLowEnergyController::UnconnectedState); + QCOMPARE(control.error(), QLowEnergyController::NoError); + QVERIFY(control.errorString().isEmpty()); + QCOMPARE(disconnectedSpy.count(), 0); + QCOMPARE(connectedSpy.count(), 0); + QVERIFY(control.services().isEmpty()); + + bool wasError = false; + control.connectToDevice(); + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 10000); + + QCOMPARE(disconnectedSpy.count(), 0); + if (control.error() != QLowEnergyController::NoError) { + //error during connect + QCOMPARE(connectedSpy.count(), 0); + QCOMPARE(control.state(), QLowEnergyController::UnconnectedState); + wasError = true; + } else if (control.state() == QLowEnergyController::ConnectingState) { + //timeout + QCOMPARE(connectedSpy.count(), 0); + QVERIFY(control.errorString().isEmpty()); + QCOMPARE(control.error(), QLowEnergyController::NoError); + QVERIFY(control.services().isEmpty()); + QSKIP("Connection to LE device cannot be established. Skipping test."); + return; + } else { + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + QCOMPARE(connectedSpy.count(), 1); + QCOMPARE(control.error(), QLowEnergyController::NoError); + QVERIFY(control.errorString().isEmpty()); + } + + QVERIFY(control.services().isEmpty()); + + QList<QLowEnergyService *> savedReferences; + + if (!wasError) { + QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished())); + QSignalSpy serviceFoundSpy(&control, SIGNAL(serviceDiscovered(QBluetoothUuid))); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + + QVERIFY(!serviceFoundSpy.isEmpty()); + QVERIFY(serviceFoundSpy.count() >= foundServices.count()); + QVERIFY(!serviceFoundSpy.isEmpty()); + QList<QBluetoothUuid> listing; + for (int i = 0; i < serviceFoundSpy.count(); i++) { + const QVariant v = serviceFoundSpy[i].at(0); + listing.append(v.value<QBluetoothUuid>()); + } + + foreach (const QBluetoothUuid &uuid, foundServices) { + QVERIFY2(listing.contains(uuid), + uuid.toString().toLatin1()); + + QLowEnergyService *service = control.createServiceObject(uuid); + QVERIFY2(service, uuid.toString().toLatin1()); + savedReferences.append(service); + QCOMPARE(service->type(), QLowEnergyService::PrimaryService); + QCOMPARE(service->state(), QLowEnergyService::DiscoveryRequired); + } + + // unrelated uuids don't return valid service object + // invalid service uuid + QVERIFY(!control.createServiceObject(QBluetoothUuid())); + // some random uuid + QVERIFY(!control.createServiceObject(QBluetoothUuid(QBluetoothUuid::DeviceName))); + + // initiate characteristic discovery + foreach (QLowEnergyService *service, savedReferences) { + qDebug() << "Discoverying" << service->serviceUuid(); + QSignalSpy stateSpy(service, + SIGNAL(stateChanged(QLowEnergyService::ServiceState))); + QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError))); + service->discoverDetails(); + + QTRY_VERIFY_WITH_TIMEOUT( + service->state() == QLowEnergyService::ServiceDiscovered, 10000); + + QCOMPARE(errorSpy.count(), 0); //no error + QCOMPARE(stateSpy.count(), 2); // + + verifyServiceProperties(service); + } + + // ensure that related service objects share same state + foreach (QLowEnergyService* originalService, savedReferences) { + QLowEnergyService *newService = control.createServiceObject( + originalService->serviceUuid()); + QVERIFY(newService); + QCOMPARE(newService->state(), QLowEnergyService::ServiceDiscovered); + delete newService; + } + } + + // Finish off + control.disconnectFromDevice(); + QTRY_VERIFY_WITH_TIMEOUT( + control.state() == QLowEnergyController::UnconnectedState, + 10000); + + if (wasError) { + QCOMPARE(disconnectedSpy.count(), 0); + } else { + QCOMPARE(disconnectedSpy.count(), 1); + // after disconnect all service references must be invalid + foreach (const QLowEnergyService *entry, savedReferences) { + const QBluetoothUuid &uuid = entry->serviceUuid(); + QVERIFY2(entry->state() == QLowEnergyService::InvalidService, + uuid.toString().toLatin1()); + + //after disconnect all related characteristics and descriptors are invalid + QList<QLowEnergyCharacteristic> chars = entry->characteristics(); + for (int i = 0; i < chars.count(); i++) { + QCOMPARE(chars.at(i).isValid(), false); + QList<QLowEnergyDescriptor> descriptors = chars[i].descriptors(); + for (int j = 0; j < descriptors.count(); j++) + QCOMPARE(descriptors[j].isValid(), false); + } + } + } + + qDeleteAll(savedReferences); + savedReferences.clear(); +} + +void tst_QLowEnergyController::tst_concurrentDiscovery() +{ + QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); + if (localAdapters.isEmpty() || remoteDevice.isNull()) + QSKIP("No local Bluetooth or remote BTLE device found. Skipping test."); + + // quick setup - more elaborate test is done by connectNew() + QLowEnergyController control(remoteDevice); + QCOMPARE(control.state(), QLowEnergyController::UnconnectedState); + QCOMPARE(control.error(), QLowEnergyController::NoError); + + control.connectToDevice(); + { + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 30000); + } + + if (control.state() == QLowEnergyController::ConnectingState + || control.error() != QLowEnergyController::NoError) { + // default BTLE backend forever hangs in ConnectingState + QSKIP("Cannot connect to remote device"); + } + + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + + // 2. new controller to same device fails + { + QLowEnergyController control2(remoteDevice); + control2.connectToDevice(); + { + QTRY_IMPL(control2.state() != QLowEnergyController::ConnectingState, + 30000); + } + + QVERIFY(control2.error() != QLowEnergyController::NoError); + } + + /* We are testing that we can run service discovery on the same device + * for multiple services at the same time. + * */ + + QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished())); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + + // pick MAX_SERVICES_SAME_TIME_ACCESS services + // and discover them at the same time +#define MAX_SERVICES_SAME_TIME_ACCESS 3 + QLowEnergyService *services[MAX_SERVICES_SAME_TIME_ACCESS]; + + QVERIFY(control.services().count() >= MAX_SERVICES_SAME_TIME_ACCESS); + + QList<QBluetoothUuid> uuids = control.services(); + + // initialize services + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + services[i] = control.createServiceObject(uuids.at(i), this); + QVERIFY(services[i]); + } + + // start complete discovery + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) + services[i]->discoverDetails(); + + // wait until discovery done + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + qWarning() << "Waiting for" << i << services[i]->serviceUuid(); + QTRY_VERIFY_WITH_TIMEOUT( + services[i]->state() == QLowEnergyService::ServiceDiscovered, + 30000); + } + + // verify discovered services + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + verifyServiceProperties(services[i]); + + QVERIFY(!services[i]->contains(QLowEnergyCharacteristic())); + QVERIFY(!services[i]->contains(QLowEnergyDescriptor())); + } + + control.disconnectFromDevice(); + QTRY_VERIFY_WITH_TIMEOUT(control.state() == QLowEnergyController::UnconnectedState, + 30000); + discoveryFinishedSpy.clear(); + + // redo the discovery with same controller + QLowEnergyService *services_second[MAX_SERVICES_SAME_TIME_ACCESS]; + control.connectToDevice(); + { + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 30000); + } + + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + + // get all details + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + services_second[i] = control.createServiceObject(uuids.at(i), this); + QVERIFY(services_second[i]->parent() == this); + QVERIFY(services[i]); + QVERIFY(services_second[i]->state() == QLowEnergyService::DiscoveryRequired); + services_second[i]->discoverDetails(); + } + + // wait until discovery done + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + qWarning() << "Waiting for" << i << services_second[i]->serviceUuid(); + QTRY_VERIFY_WITH_TIMEOUT( + services_second[i]->state() == QLowEnergyService::ServiceDiscovered, + 30000); + QCOMPARE(services_second[i]->serviceName(), services[i]->serviceName()); + QCOMPARE(services_second[i]->serviceUuid(), services[i]->serviceUuid()); + } + + // verify discovered services (1st and 2nd round) + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + verifyServiceProperties(services_second[i]); + //after disconnect all related characteristics and descriptors are invalid + const QList<QLowEnergyCharacteristic> chars = services[i]->characteristics(); + for (int j = 0; j < chars.count(); j++) { + QCOMPARE(chars.at(j).isValid(), false); + QVERIFY(services[i]->contains(chars[j])); + QVERIFY(!services_second[i]->contains(chars[j])); + const QList<QLowEnergyDescriptor> descriptors = chars[j].descriptors(); + for (int k = 0; k < descriptors.count(); k++) { + QCOMPARE(descriptors[k].isValid(), false); + services[i]->contains(descriptors[k]); + QVERIFY(!services_second[i]->contains(chars[j])); + } + } + + QCOMPARE(services[i]->serviceUuid(), services_second[i]->serviceUuid()); + QCOMPARE(services[i]->serviceName(), services_second[i]->serviceName()); + QCOMPARE(services[i]->type(), services_second[i]->type()); + QVERIFY(services[i]->state() == QLowEnergyService::InvalidService); + QVERIFY(services_second[i]->state() == QLowEnergyService::ServiceDiscovered); + } + + // cleanup + for (int i = 0; i<MAX_SERVICES_SAME_TIME_ACCESS; i++) { + delete services[i]; + delete services_second[i]; + } + + control.disconnectFromDevice(); +} + +void tst_QLowEnergyController::verifyServiceProperties( + const QLowEnergyService *info) +{ + if (info->serviceUuid() == + QBluetoothUuid(QString("00001800-0000-1000-8000-00805f9b34fb"))) { + qDebug() << "Verifying GAP Service"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 5); + + // Device Name + QString temp("00002a00-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x3)); + QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Read); + QCOMPARE(chars[0].value(), QByteArray::fromHex("544920424c452053656e736f7220546167")); + QVERIFY(chars[0].isValid()); + QCOMPARE(chars[0].descriptors().count(), 0); + QVERIFY(info->contains(chars[0])); + + // Appearance + temp = QString("00002a01-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x5)); + QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Read); + QCOMPARE(chars[1].value(), QByteArray::fromHex("0000")); + QVERIFY(chars[1].isValid()); + QCOMPARE(chars[1].descriptors().count(), 0); + QVERIFY(info->contains(chars[1])); + + // Peripheral Privacy Flag + temp = QString("00002a02-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x7)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("00")); + QVERIFY(chars[2].isValid()); + QCOMPARE(chars[2].descriptors().count(), 0); + QVERIFY(info->contains(chars[2])); + + // Reconnection Address + temp = QString("00002a03-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[3].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[3].handle(), QLowEnergyHandle(0x9)); + //Early firmware version had this characteristic as Read|Write and may fail + QCOMPARE(chars[3].properties(), QLowEnergyCharacteristic::Write); + if (chars[3].properties() & QLowEnergyCharacteristic::Read) + QCOMPARE(chars[3].value(), QByteArray::fromHex("000000000000")); + else + QCOMPARE(chars[3].value(), QByteArray()); + QVERIFY(chars[3].isValid()); + QCOMPARE(chars[3].descriptors().count(), 0); + QVERIFY(info->contains(chars[3])); + + // Peripheral Preferred Connection Parameters + temp = QString("00002a04-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[4].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[4].handle(), QLowEnergyHandle(0xb)); + QCOMPARE(chars[4].properties(), QLowEnergyCharacteristic::Read); + QCOMPARE(chars[4].value(), QByteArray::fromHex("5000a0000000e803")); + QVERIFY(chars[4].isValid()); + QCOMPARE(chars[4].descriptors().count(), 0); + QVERIFY(info->contains(chars[4])); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("00001801-0000-1000-8000-00805f9b34fb"))) { + qDebug() << "Verifying GATT Service"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 1); + + // Service Changed + QString temp("00002a05-0000-1000-8000-00805f9b34fb"); + //this should really be readable according to GATT Service spec + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0xe)); + QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Indicate); + QCOMPARE(chars[0].value(), QByteArray()); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 1); + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0xf)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("0000180a-0000-1000-8000-00805f9b34fb"))) { + qDebug() << "Verifying Device Information"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 9); + + // System ID + QString temp("00002a23-0000-1000-8000-00805f9b34fb"); + //this should really be readable according to GATT Service spec + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x12)); + QCOMPARE(chars[0].properties(), QLowEnergyCharacteristic::Read); + QCOMPARE(chars[0].value(), QByteArray::fromHex("6e41ab0000296abc")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + QCOMPARE(chars[0].descriptors().count(), 0); + + // Model Number + temp = QString("00002a24-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x14)); + QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Read); + QCOMPARE(chars[1].value(), QByteArray::fromHex("4e2e412e00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + QCOMPARE(chars[1].descriptors().count(), 0); + + // Serial Number + temp = QString("00002a25-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x16)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("4e2e412e00")); + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + QCOMPARE(chars[2].descriptors().count(), 0); + + // Firmware Revision + temp = QString("00002a26-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[3].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[3].handle(), QLowEnergyHandle(0x18)); + QCOMPARE(chars[3].properties(), + (QLowEnergyCharacteristic::Read)); + //FW rev. : 1.5 (Oct 23 2013) + // Other revisions will fail here + QCOMPARE(chars[3].value(), QByteArray::fromHex("312e3520284f637420323320323031332900")); + QVERIFY(chars[3].isValid()); + QVERIFY(info->contains(chars[3])); + QCOMPARE(chars[3].descriptors().count(), 0); + + // Hardware Revision + temp = QString("00002a27-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[4].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[4].handle(), QLowEnergyHandle(0x1a)); + QCOMPARE(chars[4].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[4].value(), QByteArray::fromHex("4e2e412e00")); + QVERIFY(chars[4].isValid()); + QVERIFY(info->contains(chars[4])); + QCOMPARE(chars[4].descriptors().count(), 0); + + // Software Revision + temp = QString("00002a28-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[5].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[5].handle(), QLowEnergyHandle(0x1c)); + QCOMPARE(chars[5].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[5].value(), QByteArray::fromHex("4e2e412e00")); + QVERIFY(chars[5].isValid()); + QVERIFY(info->contains(chars[5])); + QCOMPARE(chars[5].descriptors().count(), 0); + + // Manufacturer Name + temp = QString("00002a29-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[6].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[6].handle(), QLowEnergyHandle(0x1e)); + QCOMPARE(chars[6].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[6].value(), QByteArray::fromHex("546578617320496e737472756d656e747300")); + QVERIFY(chars[6].isValid()); + QVERIFY(info->contains(chars[6])); + QCOMPARE(chars[6].descriptors().count(), 0); + + // IEEE + temp = QString("00002a2a-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[7].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[7].handle(), QLowEnergyHandle(0x20)); + QCOMPARE(chars[7].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[7].value(), QByteArray::fromHex("fe006578706572696d656e74616c")); + QVERIFY(chars[7].isValid()); + QVERIFY(info->contains(chars[7])); + QCOMPARE(chars[7].descriptors().count(), 0); + + // PnP ID + temp = QString("00002a50-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[8].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[8].handle(), QLowEnergyHandle(0x22)); + QCOMPARE(chars[8].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[8].value(), QByteArray::fromHex("010d0000001001")); + QVERIFY(chars[8].isValid()); + QVERIFY(info->contains(chars[8])); + QCOMPARE(chars[8].descriptors().count(), 0); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa00-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Temperature"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QVERIFY(chars.count() >= 2); + + // Temp Data + QString temp("f000aa01-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x25)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x26)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x27)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("54656d702e2044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // Temp Config + temp = QString("f000aa02-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x29)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x2a)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("54656d702e20436f6e662e")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + + //Temp Period (introduced by later firmware versions) + if (chars.count() > 2) { + temp = QString("f000aa03-0451-4000-b000-000000000000"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x2c)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("64")); + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + + QCOMPARE(chars[2].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[2].descriptors().at(0).isValid(), true); + QCOMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x2d)); + QCOMPARE(chars[2].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[2].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[2].descriptors().at(0).value(), + QByteArray::fromHex("54656d702e20506572696f64")); + QVERIFY(info->contains(chars[2].descriptors().at(0))); + } + } else if (info->serviceUuid() == + QBluetoothUuid(QString("0000ffe0-0000-1000-8000-00805f9b34fb"))) { + qDebug() << "Verifying Simple Keys"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 1); + + // Temp Data + QString temp("0000ffe1-0000-1000-8000-00805f9b34fb"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x6b)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray()); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x6c)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x6d)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("4b6579205072657373205374617465")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa10-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Accelerometer"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 3); + + // Accel Data + QString temp("f000aa11-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x30)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x31)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x32)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("416363656c2e2044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // Accel Config + temp = QString("f000aa12-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x34)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + QCOMPARE(chars[1].descriptors().count(), 1); + + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x35)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("416363656c2e20436f6e662e")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + // Accel Period + temp = QString("f000aa13-0451-4000-b000-000000000000"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x37)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("64")); // don't change it or set it to 0x64 + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + + QCOMPARE(chars[2].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[2].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x38)); + QCOMPARE(chars[2].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[2].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[2].descriptors().at(0).value(), + QByteArray::fromHex("416363656c2e20506572696f64")); + QVERIFY(info->contains(chars[2].descriptors().at(0))); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa20-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Humidity"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QVERIFY(chars.count() >= 2); //new firmware has more chars + + // Humidity Data + QString temp("f000aa21-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x3b)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x3c)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x3d)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("48756d69642e2044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // Humidity Config + temp = QString("f000aa22-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x3f)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x40)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("48756d69642e20436f6e662e")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + if (chars.count() >= 3) { + // New firmware new characteristic + // Humidity Period + temp = QString("f000aa23-0451-4000-b000-000000000000"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x42)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("64")); + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + + QCOMPARE(chars[2].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[2].descriptors().at(0).isValid(), true); + QCOMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x43)); + QCOMPARE(chars[2].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[2].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[2].descriptors().at(0).value(), + QByteArray::fromHex("48756d69642e20506572696f64")); + QVERIFY(info->contains(chars[2].descriptors().at(0))); + } + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa30-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Magnetometer"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 3); + + // Magnetometer Data + QString temp("f000aa31-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x46)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x47)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x48)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("4d61676e2e2044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // Magnetometer Config + temp = QString("f000aa32-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x4a)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x4b)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("4d61676e2e20436f6e662e")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + // Magnetometer Period + temp = QString("f000aa33-0451-4000-b000-000000000000"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x4d)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("c8")); // don't change it or set it to 0xc8 + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + + QCOMPARE(chars[2].descriptors().count(), 1); + QCOMPARE(chars[2].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x4e)); + QCOMPARE(chars[2].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[2].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[2].descriptors().at(0).value(), + QByteArray::fromHex("4d61676e2e20506572696f64")); + QVERIFY(info->contains(chars[2].descriptors().at(0))); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa40-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Pressure"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QVERIFY(chars.count() >= 3); + + // Pressure Data + QString temp("f000aa41-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x51)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("00000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x52)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x53)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("4261726f6d2e2044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // Pressure Config + temp = QString("f000aa42-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x55)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x56)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("4261726f6d2e20436f6e662e")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + //calibration and period characteristic are swapped, ensure we don't depend on their order + QLowEnergyCharacteristic calibration, period; + foreach (const QLowEnergyCharacteristic &ch, chars) { + //find calibration characteristic + if (ch.uuid() == QBluetoothUuid(QString("f000aa43-0451-4000-b000-000000000000"))) + calibration = ch; + else if (ch.uuid() == QBluetoothUuid(QString("f000aa44-0451-4000-b000-000000000000"))) + period = ch; + } + + if (calibration.isValid()) { + // Pressure Calibration + temp = QString("f000aa43-0451-4000-b000-000000000000"); + QCOMPARE(calibration.uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(calibration.handle(), QLowEnergyHandle(0x5b)); + QCOMPARE(calibration.properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(calibration.value(), QByteArray::fromHex("00000000000000000000000000000000")); // don't change it + QVERIFY(calibration.isValid()); + QVERIFY(info->contains(calibration)); + + QCOMPARE(calibration.descriptors().count(), 2); + //descriptor checks + QCOMPARE(calibration.descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(calibration.descriptors().at(0).handle(), QLowEnergyHandle(0x5c)); + QCOMPARE(calibration.descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(calibration.descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(calibration.descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(calibration.descriptors().at(0))); + + QCOMPARE(calibration.descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(calibration.descriptors().at(1).handle(), QLowEnergyHandle(0x5d)); + QCOMPARE(calibration.descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(calibration.descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(calibration.descriptors().at(1).value(), + QByteArray::fromHex("4261726f6d2e2043616c6962722e")); + QVERIFY(info->contains(calibration.descriptors().at(1))); + } + + if (period.isValid()) { + // Period Calibration + temp = QString("f000aa44-0451-4000-b000-000000000000"); + QCOMPARE(period.uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(period.handle(), QLowEnergyHandle(0x58)); + QCOMPARE(period.properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(period.value(), QByteArray::fromHex("64")); + QVERIFY(period.isValid()); + QVERIFY(info->contains(period)); + + QCOMPARE(period.descriptors().count(), 1); + //descriptor checks + QCOMPARE(period.descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(period.descriptors().at(0).handle(), QLowEnergyHandle(0x59)); + QCOMPARE(period.descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(period.descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(period.descriptors().at(0).value(), + QByteArray::fromHex("4261726f6d2e20506572696f64")); + QVERIFY(info->contains(period.descriptors().at(0))); + } + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa50-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Gyroscope"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QVERIFY(chars.count() >= 2); + + // Gyroscope Data + QString temp("f000aa51-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x60)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Notify)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x61)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0000")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x62)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("4779726f2044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // Gyroscope Config + temp = QString("f000aa52-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x64)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x65)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("4779726f20436f6e662e")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + // Gyroscope Period + temp = QString("f000aa53-0451-4000-b000-000000000000"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x67)); + QCOMPARE(chars[2].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[2].value(), QByteArray::fromHex("64")); + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + + QCOMPARE(chars[2].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[2].descriptors().at(0).isValid(), true); + QCOMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x68)); + QCOMPARE(chars[2].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[2].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[2].descriptors().at(0).value(), + QByteArray::fromHex("4779726f20506572696f64")); + QVERIFY(info->contains(chars[2].descriptors().at(0))); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000aa60-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying Test Service"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 2); + + // Test Data + QString temp("f000aa61-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x70)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("3f00")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 1); + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x71)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(0).value(), + QByteArray::fromHex("546573742044617461")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + // Test Config + temp = QString("f000aa62-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x73)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Read|QLowEnergyCharacteristic::Write)); + QCOMPARE(chars[1].value(), QByteArray::fromHex("00")); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + //descriptor checks + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x74)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("5465737420436f6e666967")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000ccc0-0451-4000-b000-000000000000"))) { + qDebug() << "Connection Control Service"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 3); + + //first characteristic + QString temp("f000ccc1-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x77)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Read)); + QCOMPARE(chars[0].value(), QByteArray::fromHex("000000000000")); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x78)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0100")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x79)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("436f6e6e2e20506172616d73")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + //second characteristic + temp = QString("f000ccc2-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x7b)); + QCOMPARE(chars[1].properties(), QLowEnergyCharacteristic::Write); + QCOMPARE(chars[1].value(), QByteArray()); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 1); + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x7c)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(0).value(), + QByteArray::fromHex("436f6e6e2e20506172616d7320526571")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + //third characteristic + temp = QString("f000ccc3-0451-4000-b000-000000000000"); + QCOMPARE(chars[2].uuid(), QBluetoothUuid(temp)); + QCOMPARE(chars[2].handle(), QLowEnergyHandle(0x7e)); + QCOMPARE(chars[2].properties(), QLowEnergyCharacteristic::Write); + QCOMPARE(chars[2].value(), QByteArray()); + QVERIFY(chars[2].isValid()); + QVERIFY(info->contains(chars[2])); + + QCOMPARE(chars[2].descriptors().count(), 1); + QCOMPARE(chars[2].descriptors().at(0).isValid(), true); + QCOMPARE(chars[2].descriptors().at(0).handle(), QLowEnergyHandle(0x7f)); + QCOMPARE(chars[2].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[2].descriptors().at(0).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[2].descriptors().at(0).value(), + QByteArray::fromHex("446973636f6e6e65637420526571")); + QVERIFY(info->contains(chars[2].descriptors().at(0))); + } else if (info->serviceUuid() == + QBluetoothUuid(QString("f000ffc0-0451-4000-b000-000000000000"))) { + qDebug() << "Verifying OID Service"; + QList<QLowEnergyCharacteristic> chars = info->characteristics(); + QCOMPARE(chars.count(), 2); + + // first characteristic + QString temp("f000ffc1-0451-4000-b000-000000000000"); + QCOMPARE(chars[0].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[0].handle(), QLowEnergyHandle(0x82)); + QCOMPARE(chars[0].properties(), + (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse)); + QCOMPARE(chars[0].value(), QByteArray()); + QVERIFY(chars[0].isValid()); + QVERIFY(info->contains(chars[0])); + + QCOMPARE(chars[0].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[0].descriptors().at(0).isValid(), true); + QCOMPARE(chars[0].descriptors().at(0).handle(), QLowEnergyHandle(0x83)); + QCOMPARE(chars[0].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[0].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(0).value(), QByteArray::fromHex("0100")); + QVERIFY(info->contains(chars[0].descriptors().at(0))); + + QCOMPARE(chars[0].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[0].descriptors().at(1).handle(), QLowEnergyHandle(0x84)); + QCOMPARE(chars[0].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[0].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[0].descriptors().at(1).value(), + QByteArray::fromHex("496d67204964656e74696679")); + QVERIFY(info->contains(chars[0].descriptors().at(1))); + + // second characteristic + temp = QString("f000ffc2-0451-4000-b000-000000000000"); + QCOMPARE(chars[1].uuid(), QBluetoothUuid(temp)); + // value different in other revisions and test may fail + QCOMPARE(chars[1].handle(), QLowEnergyHandle(0x86)); + QCOMPARE(chars[1].properties(), + (QLowEnergyCharacteristic::Notify|QLowEnergyCharacteristic::Write|QLowEnergyCharacteristic::WriteNoResponse)); + QCOMPARE(chars[1].value(), QByteArray()); + QVERIFY(chars[1].isValid()); + QVERIFY(info->contains(chars[1])); + + QCOMPARE(chars[1].descriptors().count(), 2); + //descriptor checks + QCOMPARE(chars[1].descriptors().at(0).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).handle(), QLowEnergyHandle(0x87)); + QCOMPARE(chars[1].descriptors().at(0).uuid(), + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + QCOMPARE(chars[1].descriptors().at(0).type(), + QBluetoothUuid::ClientCharacteristicConfiguration); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(0).value(), QByteArray::fromHex("0100")); + QVERIFY(info->contains(chars[1].descriptors().at(0))); + + QCOMPARE(chars[1].descriptors().at(1).isValid(), true); + // value different in other revisions and test may fail + QCOMPARE(chars[1].descriptors().at(1).handle(), QLowEnergyHandle(0x88)); + QCOMPARE(chars[1].descriptors().at(1).uuid(), + QBluetoothUuid(QBluetoothUuid::CharacteristicUserDescription)); + QCOMPARE(chars[1].descriptors().at(1).type(), + QBluetoothUuid::CharacteristicUserDescription); + QCOMPARE(chars[1].descriptors().at(1).value(), + QByteArray::fromHex("496d6720426c6f636b")); + QVERIFY(info->contains(chars[1].descriptors().at(1))); + } else { + QFAIL(QString("Service not found" + info->serviceUuid().toString()).toUtf8().constData()); + } +} + +void tst_QLowEnergyController::tst_defaultBehavior() +{ + QList<QBluetoothAddress> foundAddresses; + foreach (const QBluetoothHostInfo &info, QBluetoothLocalDevice::allDevices()) + foundAddresses.append(info.address()); + const QBluetoothAddress randomAddress("11:22:33:44:55:66"); + + // Test automatic detection of local adapter + QLowEnergyController controlDefaultAdapter(randomAddress); + QCOMPARE(controlDefaultAdapter.remoteAddress(), randomAddress); + QCOMPARE(controlDefaultAdapter.state(), QLowEnergyController::UnconnectedState); + if (foundAddresses.isEmpty()) { + QVERIFY(controlDefaultAdapter.localAddress().isNull()); + } else { + QCOMPARE(controlDefaultAdapter.error(), QLowEnergyController::NoError); + QVERIFY(controlDefaultAdapter.errorString().isEmpty()); + QVERIFY(foundAddresses.contains(controlDefaultAdapter.localAddress())); + + // unrelated uuids don't return valid service object + // invalid service uuid + QVERIFY(!controlDefaultAdapter.createServiceObject( + QBluetoothUuid())); + // some random uuid + QVERIFY(!controlDefaultAdapter.createServiceObject( + QBluetoothUuid(QBluetoothUuid::DeviceName))); + } + + QCOMPARE(controlDefaultAdapter.services().count(), 0); + + // Test explicit local adapter + if (!foundAddresses.isEmpty()) { + QLowEnergyController controlExplicitAdapter(randomAddress, + foundAddresses[0]); + QCOMPARE(controlExplicitAdapter.remoteAddress(), randomAddress); + QCOMPARE(controlExplicitAdapter.localAddress(), foundAddresses[0]); + QCOMPARE(controlExplicitAdapter.state(), + QLowEnergyController::UnconnectedState); + QCOMPARE(controlExplicitAdapter.services().count(), 0); + + // unrelated uuids don't return valid service object + // invalid service uuid + QVERIFY(!controlExplicitAdapter.createServiceObject( + QBluetoothUuid())); + // some random uuid + QVERIFY(!controlExplicitAdapter.createServiceObject( + QBluetoothUuid(QBluetoothUuid::DeviceName))); + } +} + +void tst_QLowEnergyController::tst_writeCharacteristic() +{ + QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); + if (localAdapters.isEmpty() || remoteDevice.isNull()) + QSKIP("No local Bluetooth or remote BTLE device found. Skipping test."); + + // quick setup - more elaborate test is done by connect() + QLowEnergyController control(remoteDevice); + QCOMPARE(control.error(), QLowEnergyController::NoError); + + control.connectToDevice(); + { + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 30000); + } + + if (control.state() == QLowEnergyController::ConnectingState + || control.error() != QLowEnergyController::NoError) { + // default BTLE backend forever hangs in ConnectingState + QSKIP("Cannot connect to remote device"); + } + + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished())); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + + const QBluetoothUuid testService(QString("f000aa60-0451-4000-b000-000000000000")); + QList<QBluetoothUuid> uuids = control.services(); + QVERIFY(uuids.contains(testService)); + + QLowEnergyService *service = control.createServiceObject(testService, this); + QVERIFY(service); + service->discoverDetails(); + QTRY_VERIFY_WITH_TIMEOUT( + service->state() == QLowEnergyService::ServiceDiscovered, 30000); + + //test service described by http://processors.wiki.ti.com/index.php/SensorTag_User_Guide + const QList<QLowEnergyCharacteristic> chars = service->characteristics(); + + QLowEnergyCharacteristic dataChar; + QLowEnergyCharacteristic configChar; + for (int i = 0; i < chars.count(); i++) { + if (chars[i].uuid() == QBluetoothUuid(QString("f000aa61-0451-4000-b000-000000000000"))) + dataChar = chars[i]; + else if (chars[i].uuid() == QBluetoothUuid(QString("f000aa62-0451-4000-b000-000000000000"))) + configChar = chars[i]; + } + + QVERIFY(dataChar.isValid()); + QVERIFY(!(dataChar.properties() & ~QLowEnergyCharacteristic::Read)); // only a read char + QVERIFY(service->contains(dataChar)); + QVERIFY(configChar.isValid()); + QVERIFY(configChar.properties() & QLowEnergyCharacteristic::Write); + QVERIFY(service->contains(configChar)); + + QCOMPARE(dataChar.value(), QByteArray::fromHex("3f00")); + QVERIFY(configChar.value() == QByteArray::fromHex("00") + || configChar.value() == QByteArray::fromHex("81")); + + QSignalSpy writeSpy(service, + SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray))); + + // ******************************************* + // test writing of characteristic + // enable Blinking LED if not already enabled + if (configChar.value() != QByteArray("81")) { + service->writeCharacteristic(configChar, QByteArray::fromHex("81")); //0x81 blink LED D1 + QTRY_VERIFY_WITH_TIMEOUT(!writeSpy.isEmpty(), 10000); + QCOMPARE(configChar.value(), QByteArray::fromHex("81")); + QList<QVariant> firstSignalData = writeSpy.first(); + QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>(); + QByteArray signalValue = firstSignalData[1].toByteArray(); + + QCOMPARE(signalValue, QByteArray::fromHex("81")); + QVERIFY(signalChar == configChar); + + writeSpy.clear(); + } + + service->writeCharacteristic(configChar, QByteArray::fromHex("00")); //0x81 blink LED D1 + QTRY_VERIFY_WITH_TIMEOUT(!writeSpy.isEmpty(), 10000); + QCOMPARE(configChar.value(), QByteArray::fromHex("00")); + QList<QVariant> firstSignalData = writeSpy.first(); + QLowEnergyCharacteristic signalChar = firstSignalData[0].value<QLowEnergyCharacteristic>(); + QByteArray signalValue = firstSignalData[1].toByteArray(); + + QCOMPARE(signalValue, QByteArray::fromHex("00")); + QVERIFY(signalChar == configChar); + + // ******************************************* + // write wrong value -> error response required + QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError))); + writeSpy.clear(); + QCOMPARE(errorSpy.count(), 0); + QCOMPARE(writeSpy.count(), 0); + + // write 2 byte value to 1 byte characteristic + service->writeCharacteristic(configChar, QByteArray::fromHex("1111")); + QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000); + QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(), + QLowEnergyService::CharacteristicWriteError); + QCOMPARE(service->error(), QLowEnergyService::CharacteristicWriteError); + QCOMPARE(writeSpy.count(), 0); + QCOMPARE(configChar.value(), QByteArray::fromHex("00")); + + // ******************************************* + // write to read-only characteristic -> error + errorSpy.clear(); + QCOMPARE(errorSpy.count(), 0); + service->writeCharacteristic(dataChar, QByteArray::fromHex("ffff")); + + QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 10000); + QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(), + QLowEnergyService::OperationError); + QCOMPARE(service->error(), QLowEnergyService::OperationError); + QCOMPARE(writeSpy.count(), 0); + QCOMPARE(dataChar.value(), QByteArray::fromHex("3f00")); + + + control.disconnectFromDevice(); + + // ******************************************* + // write value while disconnected -> error + errorSpy.clear(); + QCOMPARE(errorSpy.count(), 0); + service->writeCharacteristic(configChar, QByteArray::fromHex("ffff")); + QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 2000); + QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(), + QLowEnergyService::OperationError); + QCOMPARE(service->error(), QLowEnergyService::OperationError); + QCOMPARE(writeSpy.count(), 0); + QCOMPARE(configChar.value(), QByteArray::fromHex("00")); + + // invalid characteristics still belong to their respective service + QVERIFY(service->contains(configChar)); + QVERIFY(service->contains(dataChar)); + + QVERIFY(!service->contains(QLowEnergyCharacteristic())); + + delete service; +} + +void tst_QLowEnergyController::tst_writeDescriptor() +{ + QList<QBluetoothHostInfo> localAdapters = QBluetoothLocalDevice::allDevices(); + if (localAdapters.isEmpty() || remoteDevice.isNull()) + QSKIP("No local Bluetooth or remote BTLE device found. Skipping test."); + + // quick setup - more elaborate test is done by connect() + QLowEnergyController control(remoteDevice); + control.connectToDevice(); + { + QTRY_IMPL(control.state() != QLowEnergyController::ConnectingState, + 30000); + } + + if (control.state() == QLowEnergyController::ConnectingState + || control.error() != QLowEnergyController::NoError) { + // default BTLE backend forever hangs in ConnectingState + QSKIP("Cannot connect to remote device"); + } + + QCOMPARE(control.state(), QLowEnergyController::ConnectedState); + QSignalSpy discoveryFinishedSpy(&control, SIGNAL(discoveryFinished())); + control.discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + + const QBluetoothUuid testService(QString("f000aa00-0451-4000-b000-000000000000")); + QList<QBluetoothUuid> uuids = control.services(); + QVERIFY(uuids.contains(testService)); + + QLowEnergyService *service = control.createServiceObject(testService, this); + QVERIFY(service); + service->discoverDetails(); + QTRY_VERIFY_WITH_TIMEOUT( + service->state() == QLowEnergyService::ServiceDiscovered, 30000); + + // Temperature service described by + // http://processors.wiki.ti.com/index.php/SensorTag_User_Guide + + // 1. Find temperature data characteristic + const QLowEnergyCharacteristic tempData = service->characteristic( + QBluetoothUuid(QStringLiteral("f000aa01-0451-4000-b000-000000000000"))); + const QLowEnergyCharacteristic tempConfig = service->characteristic( + QBluetoothUuid(QStringLiteral("f000aa02-0451-4000-b000-000000000000"))); + + if (!tempData.isValid()) { + delete service; + control.disconnectFromDevice(); + QSKIP("Cannot find temperature data characteristic of TI Sensor"); + } + + // 2. Find temperature data notification descriptor + const QLowEnergyDescriptor notification = tempData.descriptor( + QBluetoothUuid(QBluetoothUuid::ClientCharacteristicConfiguration)); + + if (!notification.isValid()) { + delete service; + control.disconnectFromDevice(); + QSKIP("Cannot find temperature data notification of TI Sensor"); + } + + QCOMPARE(notification.value(), QByteArray::fromHex("0000")); + service->contains(notification); + service->contains(tempData); + if (tempConfig.isValid()) { + service->contains(tempConfig); + QCOMPARE(tempConfig.value(), QByteArray::fromHex("00")); + } + + // 3. Test writing to descriptor -> activate notifications + QSignalSpy descChangedSpy(service, + SIGNAL(descriptorChanged(QLowEnergyDescriptor,QByteArray))); + QSignalSpy charChangedSpy(service, + SIGNAL(characteristicChanged(QLowEnergyCharacteristic,QByteArray))); + service->writeDescriptor(notification, QByteArray::fromHex("0100")); + // verify + QTRY_VERIFY_WITH_TIMEOUT(!descChangedSpy.isEmpty(), 3000); + QCOMPARE(notification.value(), QByteArray::fromHex("0100")); + QList<QVariant> firstSignalData = descChangedSpy.first(); + QLowEnergyDescriptor signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>(); + QByteArray signalValue = firstSignalData[1].toByteArray(); + QCOMPARE(signalValue, QByteArray::fromHex("0100")); + QVERIFY(notification == signalDesc); + descChangedSpy.clear(); + + // 4. Test reception of notifications + // activate the temperature sensor if available + if (tempConfig.isValid()) { + service->writeCharacteristic(tempConfig, QByteArray::fromHex("01")); + + // first signal is confirmation of tempConfig write + // subsequent signals are temp data updates + QTRY_VERIFY_WITH_TIMEOUT(charChangedSpy.count() >= 5, 10000); + QList<QVariant> entry; + for (int i = 0; i < charChangedSpy.count(); i++) { + entry = charChangedSpy[i]; + const QLowEnergyCharacteristic ch = entry[0].value<QLowEnergyCharacteristic>(); + const QByteArray val = entry[1].toByteArray(); + + if (i == 0) { + QCOMPARE(tempConfig, ch); + } else { + qDebug() << "Temp update: " << hex << ch.handle() << val.toHex(); + QCOMPARE(tempData, ch); + } + } + + service->writeCharacteristic(tempConfig, QByteArray::fromHex("00")); + } + + // 5. Test writing to descriptor -> deactivate notifications + service->writeDescriptor(notification, QByteArray::fromHex("0000")); + // verify + QTRY_VERIFY_WITH_TIMEOUT(!descChangedSpy.isEmpty(), 3000); + QCOMPARE(notification.value(), QByteArray::fromHex("0000")); + firstSignalData = descChangedSpy.first(); + signalDesc = firstSignalData[0].value<QLowEnergyDescriptor>(); + signalValue = firstSignalData[1].toByteArray(); + QCOMPARE(signalValue, QByteArray::fromHex("0000")); + QVERIFY(notification == signalDesc); + descChangedSpy.clear(); + + // ******************************************* + // write wrong value -> error response required + QSignalSpy errorSpy(service, SIGNAL(error(QLowEnergyService::ServiceError))); + descChangedSpy.clear(); + QCOMPARE(errorSpy.count(), 0); + QCOMPARE(descChangedSpy.count(), 0); + + // write 4 byte value to 2 byte characteristic + service->writeDescriptor(notification, QByteArray::fromHex("11112222")); + QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 30000); + QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(), + QLowEnergyService::DescriptorWriteError); + QCOMPARE(service->error(), QLowEnergyService::DescriptorWriteError); + QCOMPARE(descChangedSpy.count(), 0); + QCOMPARE(notification.value(), QByteArray::fromHex("0000")); + + control.disconnectFromDevice(); + + // ******************************************* + // write value while disconnected -> error + errorSpy.clear(); + service->writeDescriptor(notification, QByteArray::fromHex("0100")); + QTRY_VERIFY_WITH_TIMEOUT(!errorSpy.isEmpty(), 2000); + QCOMPARE(errorSpy[0].at(0).value<QLowEnergyService::ServiceError>(), + QLowEnergyService::OperationError); + QCOMPARE(service->error(), QLowEnergyService::OperationError); + QCOMPARE(descChangedSpy.count(), 0); + QCOMPARE(notification.value(), QByteArray::fromHex("0000")); + + delete service; +} + +QTEST_MAIN(tst_QLowEnergyController) + +#include "tst_qlowenergycontroller.moc" diff --git a/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro b/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro new file mode 100644 index 00000000..60b5a740 --- /dev/null +++ b/tests/auto/qlowenergydescriptor/qlowenergydescriptor.pro @@ -0,0 +1,8 @@ +SOURCES += tst_qlowenergydescriptor.cpp +TARGET = tst_qlowenergydescriptor +CONFIG += testcase + +QT = core bluetooth testlib +blackberry { + LIBS += -lbtapi +} diff --git a/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp b/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp new file mode 100644 index 00000000..6de47458 --- /dev/null +++ b/tests/auto/qlowenergydescriptor/tst_qlowenergydescriptor.cpp @@ -0,0 +1,326 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QUuid> + +#include <QDebug> + +#include <QBluetoothDeviceDiscoveryAgent> +#include <QLowEnergyDescriptor> +#include <QLowEnergyController> +#include <QBluetoothLocalDevice> + +QT_USE_NAMESPACE + +class tst_QLowEnergyDescriptor : public QObject +{ + Q_OBJECT + +public: + tst_QLowEnergyDescriptor(); + ~tst_QLowEnergyDescriptor(); + +protected slots: + void deviceDiscovered(const QBluetoothDeviceInfo &info); + +private slots: + void initTestCase(); + void cleanupTestCase(); + void tst_constructionDefault(); + void tst_assignCompare(); + +private: + QSet<QString> remoteLeDevices; + QLowEnergyController *globalControl; + QLowEnergyService *globalService; +}; + +tst_QLowEnergyDescriptor::tst_QLowEnergyDescriptor() : + globalControl(0), globalService(0) +{ + QLoggingCategory::setFilterRules(QStringLiteral("qt.bluetooth* = true")); +} + +tst_QLowEnergyDescriptor::~tst_QLowEnergyDescriptor() +{ +} + +void tst_QLowEnergyDescriptor::initTestCase() +{ + if (QBluetoothLocalDevice::allDevices().isEmpty()) { + qWarning("No remote device discovered."); + + return; + } + + // start Bluetooth if not started + QBluetoothLocalDevice device; + device.powerOn(); + + // find an arbitrary low energy device in vincinity + // find an arbitrary service with descriptor + + QBluetoothDeviceDiscoveryAgent *devAgent = new QBluetoothDeviceDiscoveryAgent(this); + connect(devAgent, SIGNAL(deviceDiscovered(QBluetoothDeviceInfo)), + this, SLOT(deviceDiscovered(QBluetoothDeviceInfo))); + + QSignalSpy errorSpy(devAgent, SIGNAL(error(QBluetoothDeviceDiscoveryAgent::Error))); + QVERIFY(errorSpy.isValid()); + QVERIFY(errorSpy.isEmpty()); + + QSignalSpy spy(devAgent, SIGNAL(finished())); + // there should be no changes yet + QVERIFY(spy.isValid()); + QVERIFY(spy.isEmpty()); + + devAgent->start(); + QTRY_VERIFY_WITH_TIMEOUT(spy.count() > 0, 50000); + + // find first service with descriptor + QLowEnergyController *controller = 0; + foreach (const QString &remoteDevice, remoteLeDevices.toList()) { + controller = new QLowEnergyController(QBluetoothAddress(remoteDevice), this); + qDebug() << "Connecting to" << remoteDevice; + controller->connectToDevice(); + QTRY_IMPL(controller->state() != QLowEnergyController::ConnectingState, + 10000); + if (controller->state() != QLowEnergyController::ConnectedState) { + // any error and we skip + delete controller; + qDebug() << "Skipping device"; + continue; + } + + QSignalSpy discoveryFinishedSpy(controller, SIGNAL(discoveryFinished())); + controller->discoverServices(); + QTRY_VERIFY_WITH_TIMEOUT(discoveryFinishedSpy.count() == 1, 10000); + foreach (const QBluetoothUuid &leServiceUuid, controller->services()) { + QLowEnergyService *leService = controller->createServiceObject(leServiceUuid, this); + if (!leService) + continue; + + leService->discoverDetails(); + QTRY_VERIFY_WITH_TIMEOUT( + leService->state() == QLowEnergyService::ServiceDiscovered, 10000); + + QList<QLowEnergyCharacteristic> chars = leService->characteristics(); + foreach (const QLowEnergyCharacteristic &ch, chars) { + if (!ch.descriptors().isEmpty()) { + globalService = leService; + globalControl = controller; + qWarning() << "Found service with descriptor" << remoteDevice + << globalService->serviceName() << globalService->serviceUuid(); + break; + } + } + + if (globalControl) + break; + else + delete leService; + } + + if (globalControl) + break; + + delete controller; + } + + if (!globalControl) { + qWarning() << "Test limited due to missing remote QLowEnergyDescriptor." + << "Please ensure the Bluetooth Low Energy device is advertising its services."; + } +} + +void tst_QLowEnergyDescriptor::cleanupTestCase() +{ + if (globalControl) + globalControl->disconnectFromDevice(); +} + +void tst_QLowEnergyDescriptor::deviceDiscovered(const QBluetoothDeviceInfo &info) +{ + if (info.coreConfigurations() & QBluetoothDeviceInfo::LowEnergyCoreConfiguration) + remoteLeDevices.insert(info.address().toString()); +} + +void tst_QLowEnergyDescriptor::tst_constructionDefault() +{ + QLowEnergyDescriptor descriptor; + QVERIFY(!descriptor.isValid()); + QCOMPARE(descriptor.value(), QByteArray()); + QVERIFY(descriptor.uuid().isNull()); + QVERIFY(descriptor.handle() == 0); + QCOMPARE(descriptor.name(), QString()); + QCOMPARE(descriptor.type(), QBluetoothUuid::UnknownDescriptorType); + + QLowEnergyDescriptor copyConstructed(descriptor); + QVERIFY(!copyConstructed.isValid()); + QCOMPARE(copyConstructed.value(), QByteArray()); + QVERIFY(copyConstructed.uuid().isNull()); + QVERIFY(copyConstructed.handle() == 0); + QCOMPARE(copyConstructed.name(), QString()); + QCOMPARE(copyConstructed.type(), QBluetoothUuid::UnknownDescriptorType); + + QVERIFY(copyConstructed == descriptor); + QVERIFY(descriptor == copyConstructed); + QVERIFY(!(copyConstructed != descriptor)); + QVERIFY(!(descriptor != copyConstructed)); + + QLowEnergyDescriptor assigned; + + QVERIFY(assigned == descriptor); + QVERIFY(descriptor == assigned); + QVERIFY(!(assigned != descriptor)); + QVERIFY(!(descriptor != assigned)); + + assigned = descriptor; + QVERIFY(!assigned.isValid()); + QCOMPARE(assigned.value(), QByteArray()); + QVERIFY(assigned.uuid().isNull()); + QVERIFY(assigned.handle() == 0); + QCOMPARE(assigned.name(), QString()); + QCOMPARE(assigned.type(), QBluetoothUuid::UnknownDescriptorType); + + QVERIFY(assigned == descriptor); + QVERIFY(descriptor == assigned); + QVERIFY(!(assigned != descriptor)); + QVERIFY(!(descriptor != assigned)); +} + + +void tst_QLowEnergyDescriptor::tst_assignCompare() +{ + //find the descriptor + if (!globalService) + QSKIP("No descriptor found."); + + QLowEnergyDescriptor target; + QVERIFY(!target.isValid()); + QCOMPARE(target.type(), QBluetoothUuid::UnknownDescriptorType); + QCOMPARE(target.name(), QString()); + QCOMPARE(target.handle(), QLowEnergyHandle(0)); + QCOMPARE(target.uuid(), QBluetoothUuid()); + QCOMPARE(target.value(), QByteArray()); + + QList<QLowEnergyDescriptor> targets; + const QList<QLowEnergyCharacteristic> chars = globalService->characteristics(); + foreach (const QLowEnergyCharacteristic &ch, chars) { + if (!ch.descriptors().isEmpty()) { + targets = ch.descriptors(); + break; + } + } + + if (targets.isEmpty()) + QSKIP("No descriptor found despite prior indication."); + + // test assignment operator + target = targets.first(); + QVERIFY(target.isValid()); + QVERIFY(target.type() != QBluetoothUuid::UnknownDescriptorType); + QVERIFY(!target.name().isEmpty()); + QVERIFY(target.handle() > 0); + QVERIFY(!target.uuid().isNull()); + QVERIFY(!target.value().isEmpty()); + + QVERIFY(target == targets.first()); + QVERIFY(targets.first() == target); + QVERIFY(!(target != targets.first())); + QVERIFY(!(targets.first() != target)); + + QCOMPARE(target.isValid(), targets.first().isValid()); + QCOMPARE(target.type(), targets.first().type()); + QCOMPARE(target.name(), targets.first().name()); + QCOMPARE(target.handle(), targets.first().handle()); + QCOMPARE(target.uuid(), targets.first().uuid()); + QCOMPARE(target.value(), targets.first().value()); + + // test copy constructor + QLowEnergyDescriptor copyConstructed(target); + QCOMPARE(copyConstructed.isValid(), targets.first().isValid()); + QCOMPARE(copyConstructed.type(), targets.first().type()); + QCOMPARE(copyConstructed.name(), targets.first().name()); + QCOMPARE(copyConstructed.handle(), targets.first().handle()); + QCOMPARE(copyConstructed.uuid(), targets.first().uuid()); + QCOMPARE(copyConstructed.value(), targets.first().value()); + + QVERIFY(copyConstructed == target); + QVERIFY(target == copyConstructed); + QVERIFY(!(copyConstructed != target)); + QVERIFY(!(target != copyConstructed)); + + // test invalidation + QLowEnergyDescriptor invalid; + target = invalid; + QVERIFY(!target.isValid()); + QCOMPARE(target.value(), QByteArray()); + QVERIFY(target.uuid().isNull()); + QVERIFY(target.handle() == 0); + QCOMPARE(target.name(), QString()); + QCOMPARE(target.type(), QBluetoothUuid::UnknownDescriptorType); + + QVERIFY(invalid == target); + QVERIFY(target == invalid); + QVERIFY(!(invalid != target)); + QVERIFY(!(target != invalid)); + + QVERIFY(!(targets.first() == target)); + QVERIFY(!(target == targets.first())); + QVERIFY(targets.first() != target); + QVERIFY(target != targets.first()); + + if (targets.count() >= 2) { + QLowEnergyDescriptor second = targets[1]; + // at least two descriptors + QVERIFY(!(targets.first() == second)); + QVERIFY(!(second == targets.first())); + QVERIFY(targets.first() != second); + QVERIFY(second != targets.first()); + } +} + +QTEST_MAIN(tst_QLowEnergyDescriptor) + +#include "tst_qlowenergydescriptor.moc" + diff --git a/tests/auto/qlowenergyserviceinfo/qlowenergyserviceinfo.pro b/tests/auto/qlowenergyserviceinfo/qlowenergyserviceinfo.pro new file mode 100644 index 00000000..1dd24a97 --- /dev/null +++ b/tests/auto/qlowenergyserviceinfo/qlowenergyserviceinfo.pro @@ -0,0 +1,9 @@ +SOURCES += tst_qlowenergyserviceinfo.cpp +TARGET = tst_qlowenergyserviceinfo +CONFIG += testcase + +QT = core bluetooth testlib +DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0 +blackberry { + LIBS += -lbtapi +} diff --git a/tests/auto/qlowenergyserviceinfo/tst_qlowenergyserviceinfo.cpp b/tests/auto/qlowenergyserviceinfo/tst_qlowenergyserviceinfo.cpp new file mode 100644 index 00000000..ae9b3bd8 --- /dev/null +++ b/tests/auto/qlowenergyserviceinfo/tst_qlowenergyserviceinfo.cpp @@ -0,0 +1,251 @@ +/*************************************************************************** +** +** Copyright (C) 2013 BlackBerry Limited all rights reserved +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtBluetooth module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtTest/QtTest> +#include <QUuid> + +#include <QDebug> + +#include <qbluetoothdeviceinfo.h> +#include <qlowenergyserviceinfo.h> +#include <qbluetoothaddress.h> +#include <qbluetoothlocaldevice.h> +#include <qbluetoothuuid.h> + +QT_USE_NAMESPACE + +Q_DECLARE_METATYPE(QUuid) +Q_DECLARE_METATYPE(QBluetoothDeviceInfo::CoreConfiguration) +Q_DECLARE_METATYPE(QLowEnergyServiceInfo) +Q_DECLARE_METATYPE(QBluetoothUuid::ServiceClassUuid) + +class tst_QLowEnergyServiceInfo : public QObject +{ + Q_OBJECT + +public: + tst_QLowEnergyServiceInfo(); + ~tst_QLowEnergyServiceInfo(); + +private slots: + void initTestCase(); + void tst_construction(); + void tst_assignment_data(); + void tst_assignment(); +}; + +tst_QLowEnergyServiceInfo::tst_QLowEnergyServiceInfo() +{ +} + +tst_QLowEnergyServiceInfo::~tst_QLowEnergyServiceInfo() +{ +} + +void tst_QLowEnergyServiceInfo::initTestCase() +{ + // start Bluetooth if not started + QBluetoothLocalDevice *device = new QBluetoothLocalDevice(); + device->powerOn(); + delete device; +} + +void tst_QLowEnergyServiceInfo::tst_construction() +{ + const QBluetoothUuid serviceUuid(QBluetoothUuid::HeartRate); + const QBluetoothUuid alternateServiceUuid(QBluetoothUuid::BatteryService); + const QBluetoothDeviceInfo deviceInfo(QBluetoothAddress("001122334455"), "Test Device", 0); + const QBluetoothDeviceInfo alternatedeviceInfo(QBluetoothAddress("554433221100"), "Test Device2", 0); + + { + QLowEnergyServiceInfo serviceInfo; + + QVERIFY(!serviceInfo.isValid()); + QCOMPARE(serviceInfo.serviceName(), QStringLiteral("Unknown Service")); + QCOMPARE(serviceInfo.serviceUuid().toString(), QBluetoothUuid().toString()); + QCOMPARE(serviceInfo.device(), QBluetoothDeviceInfo()); + } + + { + QLowEnergyServiceInfo serviceInfo(serviceUuid); + serviceInfo.setDevice(deviceInfo); + + QVERIFY(serviceInfo.isValid()); + + QCOMPARE(serviceInfo.serviceUuid().toString(), serviceUuid.toString()); + QCOMPARE(serviceInfo.device().address(), deviceInfo.address()); + + QLowEnergyServiceInfo copyInfo(serviceInfo); + + QVERIFY(copyInfo.isValid()); + + QCOMPARE(copyInfo.serviceUuid().toString(), serviceUuid.toString()); + QCOMPARE(copyInfo.device().address(), deviceInfo.address()); + + + copyInfo = QLowEnergyServiceInfo(alternateServiceUuid); + copyInfo.setDevice(alternatedeviceInfo); + QCOMPARE(copyInfo.serviceUuid(), alternateServiceUuid); + + QCOMPARE(copyInfo.device().address(), alternatedeviceInfo.address()); + + } +} + +void tst_QLowEnergyServiceInfo::tst_assignment_data() +{ + QTest::addColumn<QBluetoothAddress>("address"); + QTest::addColumn<QString>("name"); + QTest::addColumn<quint32>("classOfDevice"); + QTest::addColumn<QBluetoothUuid>("serviceClassUuid"); + QTest::addColumn<QBluetoothDeviceInfo::CoreConfiguration>("coreConfiguration"); + + // bits 12-8 Major + // bits 7-2 Minor + // bits 1-0 0 + + QTest::newRow("0x000000 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000000) + << QBluetoothUuid(QBluetoothUuid::GenericAccess) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; + QTest::newRow("0x000100 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000100) + << QBluetoothUuid(QBluetoothUuid::GenericAttribute) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; + QTest::newRow("0x000104 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000104) + << QBluetoothUuid(QBluetoothUuid::HeartRate) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; + QTest::newRow("0x000118 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000118) + << QBluetoothUuid(QBluetoothUuid::CyclingSpeedAndCadence) + << QBluetoothDeviceInfo::BaseRateAndLowEnergyCoreConfiguration; + QTest::newRow("0x000200 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000200) + << QBluetoothUuid(QBluetoothUuid::CyclingPower) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; + QTest::newRow("0x000204 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000204) + << QBluetoothUuid(QBluetoothUuid::ScanParameters) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; + QTest::newRow("0x000214 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000214) + << QBluetoothUuid(QBluetoothUuid::DeviceInformation) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; + QTest::newRow("0x000300 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000300) + << QBluetoothUuid(QBluetoothUuid::CurrentTimeService) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; + QTest::newRow("0x000320 COD") << QBluetoothAddress("000000000000") << "My Bluetooth Device" + << quint32(0x000320) + << QBluetoothUuid(QBluetoothUuid::LocationAndNavigation) + << QBluetoothDeviceInfo::LowEnergyCoreConfiguration; +} + +void tst_QLowEnergyServiceInfo::tst_assignment() +{ + QFETCH(QBluetoothAddress, address); + QFETCH(QString, name); + QFETCH(quint32, classOfDevice); + QFETCH(QBluetoothUuid, serviceClassUuid); + QFETCH(QBluetoothDeviceInfo::CoreConfiguration, coreConfiguration); + + QBluetoothDeviceInfo deviceInfo(address, name, classOfDevice); + deviceInfo.setCoreConfigurations(coreConfiguration); + QCOMPARE(deviceInfo.coreConfigurations(), coreConfiguration); + + QLowEnergyServiceInfo serviceInfo(serviceClassUuid); + serviceInfo.setDevice(deviceInfo); + QCOMPARE(serviceInfo.device(), deviceInfo); + + QVERIFY(serviceInfo.isValid()); + + { + QLowEnergyServiceInfo copyInfo = serviceInfo; + + QVERIFY(copyInfo.isValid()); + + QCOMPARE(copyInfo.device().address(), address); + QCOMPARE(copyInfo.serviceUuid(), serviceClassUuid); + QCOMPARE(copyInfo.device().coreConfigurations(), coreConfiguration); + QCOMPARE(copyInfo.device(), deviceInfo); + } + + { + QLowEnergyServiceInfo copyInfo; + + QVERIFY(!copyInfo.isValid()); + + copyInfo = serviceInfo; + + QVERIFY(copyInfo.isValid()); + + QCOMPARE(copyInfo.device().address(), address); + QCOMPARE(copyInfo.serviceUuid(), serviceClassUuid); + QCOMPARE(copyInfo.device().coreConfigurations(), coreConfiguration); + } + + { + QLowEnergyServiceInfo copyInfo1; + QLowEnergyServiceInfo copyInfo2; + + QVERIFY(!copyInfo1.isValid()); + QVERIFY(!copyInfo2.isValid()); + + copyInfo1 = copyInfo2 = serviceInfo; + + QVERIFY(copyInfo1.isValid()); + QVERIFY(copyInfo2.isValid()); + + QCOMPARE(copyInfo1.device().address(), address); + QCOMPARE(copyInfo2.device().address(), address); + QCOMPARE(copyInfo1.serviceUuid(), serviceClassUuid); + QCOMPARE(copyInfo2.serviceUuid(), serviceClassUuid); + QCOMPARE(copyInfo1.device().coreConfigurations(), coreConfiguration); + QCOMPARE(copyInfo2.device().coreConfigurations(), coreConfiguration); + QCOMPARE(copyInfo1.device(), deviceInfo); + QCOMPARE(copyInfo2.device(), deviceInfo); + } +} + +QTEST_MAIN(tst_QLowEnergyServiceInfo) + +#include "tst_qlowenergyserviceinfo.moc" diff --git a/tests/bttestui/btlocaldevice.cpp b/tests/bttestui/btlocaldevice.cpp index 3447de1a..6b7027d0 100644 --- a/tests/bttestui/btlocaldevice.cpp +++ b/tests/bttestui/btlocaldevice.cpp @@ -80,6 +80,8 @@ BtLocalDevice::BtLocalDevice(QObject *parent) : serviceAgent = new QBluetoothServiceDiscoveryAgent(this); connect(serviceAgent, SIGNAL(serviceDiscovered(QBluetoothServiceInfo)), this, SLOT(serviceDiscovered(QBluetoothServiceInfo))); + connect(serviceAgent, SIGNAL(serviceDiscovered(QLowEnergyServiceInfo)), + this, SLOT(leServiceDiscovered(QLowEnergyServiceInfo))); connect(serviceAgent, SIGNAL(finished()), this, SLOT(serviceDiscoveryFinished())); connect(serviceAgent, SIGNAL(canceled()), @@ -303,11 +305,11 @@ void BtLocalDevice::stopServiceDiscovery() void BtLocalDevice::serviceDiscovered(const QBluetoothServiceInfo &info) { - QString classIds; - foreach (const QBluetoothUuid uuid, info.serviceClassUuids()) - classIds += uuid.toString() + QLatin1Char(' '); + QStringList classIds; + foreach (const QBluetoothUuid &uuid, info.serviceClassUuids()) + classIds.append(uuid.toString()); qDebug() << "$$ Found new service" << info.device().address().toString() - << info.serviceUuid() << info.serviceName() << classIds; + << info.serviceUuid() << info.serviceName() << info.serviceDescription() << classIds; if (info.serviceUuid() == QBluetoothUuid(QString(TEST_SERVICE_UUID)) || info.serviceClassUuids().contains(QBluetoothUuid(QString(TEST_SERVICE_UUID)))) @@ -328,6 +330,12 @@ void BtLocalDevice::serviceDiscovered(const QBluetoothServiceInfo &info) } } +void BtLocalDevice::leServiceDiscovered(const QLowEnergyServiceInfo &info) +{ + qDebug() << "$$ Found new BTLE service" << info.device().address().toString() + << info.serviceUuid() << info.serviceName(); +} + void BtLocalDevice::serviceDiscoveryFinished() { qDebug() << "###### Service Discovery Finished"; diff --git a/tests/bttestui/btlocaldevice.h b/tests/bttestui/btlocaldevice.h index 01d9b0df..a0c020cc 100644 --- a/tests/bttestui/btlocaldevice.h +++ b/tests/bttestui/btlocaldevice.h @@ -90,6 +90,7 @@ public slots: void startTargettedServiceDiscovery(); void stopServiceDiscovery(); void serviceDiscovered(const QBluetoothServiceInfo &info); + void leServiceDiscovered(const QLowEnergyServiceInfo &leInfo); void serviceDiscoveryFinished(); void serviceDiscoveryCanceled(); void serviceDiscoveryError(QBluetoothServiceDiscoveryAgent::Error error); |