1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2016 The Qt Company Ltd. |
4 | ** Contact: https://www.qt.io/licensing/ |
5 | ** |
6 | ** This file is part of the QtBluetooth module of the Qt Toolkit. |
7 | ** |
8 | ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ |
9 | ** Commercial License Usage |
10 | ** Licensees holding valid commercial Qt licenses may use this file in |
11 | ** accordance with the commercial license agreement provided with the |
12 | ** Software or, alternatively, in accordance with the terms contained in |
13 | ** a written agreement between you and The Qt Company. For licensing terms |
14 | ** and conditions see https://www.qt.io/terms-conditions. For further |
15 | ** information use the contact form at https://www.qt.io/contact-us. |
16 | ** |
17 | ** GNU General Public License Usage |
18 | ** Alternatively, this file may be used under the terms of the GNU |
19 | ** General Public License version 3 as published by the Free Software |
20 | ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT |
21 | ** included in the packaging of this file. Please review the following |
22 | ** information to ensure the GNU General Public License requirements will |
23 | ** be met: https://www.gnu.org/licenses/gpl-3.0.html. |
24 | ** |
25 | ** $QT_END_LICENSE$ |
26 | ** |
27 | ****************************************************************************/ |
28 | |
29 | #include <QtBluetooth/qlowenergyadvertisingdata.h> |
30 | #include <QtBluetooth/qlowenergyadvertisingparameters.h> |
31 | #include <QtBluetooth/qlowenergyconnectionparameters.h> |
32 | #include <QtBluetooth/qlowenergycontroller.h> |
33 | #include <QtBluetooth/qlowenergycharacteristicdata.h> |
34 | #include <QtBluetooth/qlowenergydescriptordata.h> |
35 | #include <QtBluetooth/qlowenergyservicedata.h> |
36 | #include <QtCore/qcoreapplication.h> |
37 | #include <QtCore/qendian.h> |
38 | #include <QtCore/qhash.h> |
39 | #include <QtCore/qscopedpointer.h> |
40 | #include <QtCore/qsharedpointer.h> |
41 | #include <QtCore/qvector.h> |
42 | |
43 | static QByteArray deviceName() { return "Qt GATT server" ; } |
44 | |
45 | static QScopedPointer<QLowEnergyController> leController; |
46 | typedef QSharedPointer<QLowEnergyService> ServicePtr; |
47 | static QHash<QBluetoothUuid, ServicePtr> services; |
48 | static int descriptorWriteCount = 0; |
49 | static int disconnectCount = 0; |
50 | static QBluetoothAddress remoteDevice; |
51 | |
52 | void addService(const QLowEnergyServiceData &serviceData) |
53 | { |
54 | const ServicePtr service(leController->addService(service: serviceData)); |
55 | Q_ASSERT(service); |
56 | services.insert(akey: service->serviceUuid(), avalue: service); |
57 | } |
58 | |
59 | void addRunningSpeedService() |
60 | { |
61 | QLowEnergyServiceData serviceData; |
62 | serviceData.setUuid(QBluetoothUuid::RunningSpeedAndCadence); |
63 | serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); |
64 | |
65 | QLowEnergyDescriptorData desc; |
66 | desc.setUuid(QBluetoothUuid::ClientCharacteristicConfiguration); |
67 | desc.setValue(QByteArray(2, 0)); // Default: No indication, no notification. |
68 | QLowEnergyCharacteristicData charData; |
69 | charData.setUuid(QBluetoothUuid::RSCMeasurement); |
70 | charData.addDescriptor(descriptor: desc); |
71 | charData.setProperties(QLowEnergyCharacteristic::Notify); |
72 | QByteArray value(4, 0); |
73 | value[0] = 1 << 2; // "Running", no optional fields. |
74 | charData.setValue(value); |
75 | serviceData.addCharacteristic(characteristic: charData); |
76 | |
77 | charData = QLowEnergyCharacteristicData(); |
78 | charData.setUuid(QBluetoothUuid::RSCFeature); |
79 | charData.setProperties(QLowEnergyCharacteristic::Read); |
80 | value = QByteArray(2, 0); |
81 | qToLittleEndian<quint16>(src: 1 << 2, dest: reinterpret_cast<uchar *>(value.data())); |
82 | charData.setValue(value); |
83 | serviceData.addCharacteristic(characteristic: charData); |
84 | addService(serviceData); |
85 | } |
86 | |
87 | void addGenericAccessService() |
88 | { |
89 | QLowEnergyServiceData serviceData; |
90 | serviceData.setUuid(QBluetoothUuid::GenericAccess); |
91 | serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); |
92 | |
93 | QLowEnergyCharacteristicData charData; |
94 | charData.setUuid(QBluetoothUuid::DeviceName); |
95 | charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Write); |
96 | charData.setValue(deviceName()); |
97 | serviceData.addCharacteristic(characteristic: charData); |
98 | |
99 | charData = QLowEnergyCharacteristicData(); |
100 | charData.setUuid(QBluetoothUuid::Appearance); |
101 | charData.setProperties(QLowEnergyCharacteristic::Read); |
102 | QByteArray value(2, 0); |
103 | qToLittleEndian<quint16>(src: 128, dest: reinterpret_cast<uchar *>(value.data())); // Generic computer. |
104 | charData.setValue(value); |
105 | serviceData.addCharacteristic(characteristic: charData); |
106 | |
107 | serviceData.addIncludedService(service: services.value(akey: QBluetoothUuid::RunningSpeedAndCadence).data()); |
108 | addService(serviceData); |
109 | } |
110 | |
111 | void addCustomService() |
112 | { |
113 | QLowEnergyServiceData serviceData; |
114 | serviceData.setUuid(QBluetoothUuid(quint16(0x2000))); |
115 | serviceData.setType(QLowEnergyServiceData::ServiceTypePrimary); |
116 | |
117 | QLowEnergyCharacteristicData charData; |
118 | charData.setUuid(QBluetoothUuid(quint16(0x5000))); // Made up. |
119 | charData.setProperties(QLowEnergyCharacteristic::Read); |
120 | charData.setValue(QByteArray(1024, 'x')); // Long value to test "Read Blob". |
121 | serviceData.addCharacteristic(characteristic: charData); |
122 | |
123 | charData.setUuid(QBluetoothUuid(quint16(0x5001))); |
124 | charData.setProperties(QLowEnergyCharacteristic::Read); |
125 | charData.setReadConstraints(QBluetooth::AttAuthorizationRequired); // To test read failure. |
126 | serviceData.addCharacteristic(characteristic: charData); |
127 | charData.setValue("something" ); |
128 | |
129 | charData.setUuid(QBluetoothUuid(quint16(0x5002))); |
130 | charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Indicate); |
131 | charData.setReadConstraints(QBluetooth::AttAccessConstraints()); |
132 | const QLowEnergyDescriptorData desc(QBluetoothUuid::ClientCharacteristicConfiguration, |
133 | QByteArray(2, 0)); |
134 | charData.addDescriptor(descriptor: desc); |
135 | serviceData.addCharacteristic(characteristic: charData); |
136 | |
137 | charData.setUuid(QBluetoothUuid(quint16(0x5003))); |
138 | charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify); |
139 | serviceData.addCharacteristic(characteristic: charData); |
140 | |
141 | charData.setUuid(QBluetoothUuid(quint16(0x5004))); |
142 | charData.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::WriteSigned); |
143 | charData.setDescriptors(QList<QLowEnergyDescriptorData>()); |
144 | charData.setValue("initial" ); |
145 | serviceData.addCharacteristic(characteristic: charData); |
146 | |
147 | addService(serviceData); |
148 | |
149 | // service with full 128 bit custom uuids |
150 | QLowEnergyServiceData serviceData128; |
151 | serviceData128.setUuid(QBluetoothUuid(QString("c47774c7-f237-4523-8968-e4ae75431daf" ))); |
152 | serviceData128.setType(QLowEnergyServiceData::ServiceTypePrimary); |
153 | |
154 | QLowEnergyCharacteristicData charData128; |
155 | charData128.setUuid(QBluetoothUuid(QString("c0ad61b1-79e7-42f9-ace0-0a9aa0d0a4f8" ))); |
156 | charData128.setProperties(QLowEnergyCharacteristic::Read); |
157 | charData128.setValue(QByteArray(15, 'a')); |
158 | serviceData128.addCharacteristic(characteristic: charData128); |
159 | |
160 | addService(serviceData: serviceData128); |
161 | } |
162 | |
163 | void startAdvertising() |
164 | { |
165 | QLowEnergyAdvertisingParameters params; |
166 | params.setMode(QLowEnergyAdvertisingParameters::AdvInd); |
167 | QLowEnergyAdvertisingData data; |
168 | data.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityLimited); |
169 | data.setServices(services.keys()); |
170 | data.setIncludePowerLevel(true); |
171 | data.setLocalName(deviceName()); |
172 | leController->startAdvertising(parameters: params, advertisingData: data); |
173 | } |
174 | |
175 | int main(int argc, char *argv[]) |
176 | { |
177 | QCoreApplication app(argc, argv); |
178 | leController.reset(other: QLowEnergyController::createPeripheral()); |
179 | addRunningSpeedService(); |
180 | addGenericAccessService(); |
181 | addCustomService(); |
182 | startAdvertising(); |
183 | |
184 | const ServicePtr customService = services.value(akey: QBluetoothUuid(quint16(0x2000))); |
185 | Q_ASSERT(customService); |
186 | |
187 | const auto stateChangedHandler = [customService]() { |
188 | switch (leController->state()) { |
189 | case QLowEnergyController::ConnectedState: |
190 | remoteDevice = leController->remoteAddress(); |
191 | break; |
192 | case QLowEnergyController::UnconnectedState: { |
193 | if (++disconnectCount == 2) { |
194 | qApp->quit(); |
195 | break; |
196 | } |
197 | Q_ASSERT(disconnectCount == 1); |
198 | const QLowEnergyCharacteristic indicatableChar |
199 | = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5002))); |
200 | Q_ASSERT(indicatableChar.isValid()); |
201 | customService->writeCharacteristic(characteristic: indicatableChar, newValue: "indicated2" ); |
202 | Q_ASSERT(indicatableChar.value() == "indicated2" ); |
203 | const QLowEnergyCharacteristic notifiableChar |
204 | = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5003))); |
205 | Q_ASSERT(notifiableChar.isValid()); |
206 | customService->writeCharacteristic(characteristic: notifiableChar, newValue: "notified2" ); |
207 | Q_ASSERT(notifiableChar.value() == "notified2" ); |
208 | startAdvertising(); |
209 | break; |
210 | } |
211 | default: |
212 | break; |
213 | } |
214 | }; |
215 | |
216 | QObject::connect(sender: leController.data(), signal: &QLowEnergyController::stateChanged, slot: stateChangedHandler); |
217 | const auto descriptorWriteHandler = [customService]() { |
218 | if (++descriptorWriteCount != 2) |
219 | return; |
220 | const QLowEnergyCharacteristic indicatableChar |
221 | = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5002))); |
222 | Q_ASSERT(indicatableChar.isValid()); |
223 | customService->writeCharacteristic(characteristic: indicatableChar, newValue: "indicated" ); |
224 | Q_ASSERT(indicatableChar.value() == "indicated" ); |
225 | const QLowEnergyCharacteristic notifiableChar |
226 | = customService->characteristic(uuid: QBluetoothUuid(quint16(0x5003))); |
227 | Q_ASSERT(notifiableChar.isValid()); |
228 | customService->writeCharacteristic(characteristic: notifiableChar, newValue: "notified" ); |
229 | Q_ASSERT(notifiableChar.value() == "notified" ); |
230 | QLowEnergyConnectionParameters connParams; |
231 | connParams.setIntervalRange(minimum: 30, maximum: 62.5); |
232 | connParams.setLatency(5); |
233 | connParams.setSupervisionTimeout(5500); |
234 | leController->requestConnectionUpdate(parameters: connParams); |
235 | }; |
236 | QObject::connect(sender: customService.data(), signal: &QLowEnergyService::descriptorWritten, |
237 | slot: descriptorWriteHandler); |
238 | |
239 | return app.exec(); |
240 | } |
241 | |