aboutsummaryrefslogtreecommitdiff
path: root/src/Server.cpp
blob: 87b3279447ec2757d66aac55e919ff09dbb41be9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
// Copyright 2017 Paul Nettle. 
// 
// This file is part of Gobbledegook. 
// 
// Gobbledegook is free software: you can redistribute it and/or modify 
// it under the terms of the GNU General Public License as published by 
// the Free Software Foundation, either version 3 of the License, or 
// (at your option) any later version. 
// 
// Gobbledegook is distributed in the hope that it will be useful, 
// but WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
// GNU General Public License for more details. 
// 
// You should have received a copy of the GNU General Public License 
// along with Gobbledegook.  If not, see <http://www.gnu.org/licenses/>. 
 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
// 
// >> 
// >>>  INSIDE THIS FILE 
// >> 
// 
// This is the money file. This is your server description and complete implementation. If you want to add or remove a Bluetooth 
// service, alter its behavior, add or remove characteristics or descriptors (and more), then this is your new home. 
// 
// >> 
// >>>  DISCUSSION 
// >> 
// 
// The use of the term 'server', as it is used here, refers a collection of BlueZ services, characteristics & Descripors (plus 
// a little more.) 
// 
// Our server needs to be described in two ways. Why two? Well, think about it like this: We're communicating with Bluetooth 
// clients through BlueZ, and we're communicating with BlueZ through D-Bus. In essence, BlueZ and D-Bus are acting as tunnels, one 
// inside the other. 
// 
// Here are those two descriptions in a bit more detail: 
// 
// 1. We need to describe ourselves as a citizen on D-Bus: The objects we implement, interfaces we provide, methods we handle, etc. 
// 
//    To accomplish this, we need to build an XML description (called an 'Introspection' for the curious readers) of our DBus 
//    object hierarchy. The code for the XML generation starts in DBusObject.cpp (see `generateIntrospectionXML`) and carries on 
//    throughout the other DBus* files (and even a few Gatt* files). 
// 
// 2. We also need to describe ourselves as a Bluetooth citizen: The services we provide, our characteristics and descriptors. 
// 
//    To accomplish this, BlueZ requires us to implement a standard D-Bus interface ('org.freedesktop.DBus.ObjectManager'). This 
//    interface includes a D-Bus method 'GetManagedObjects', which is just a standardized way for somebody (say... BlueZ) to ask a 
//    D-Bus entity (say... this server) to enumerate itself. This is how BlueZ figures out what services we offer. BlueZ will 
//    essentially forward this information to Bluetooth clients. 
// 
// Although these two descriptions work at different levels, the two need to be kept in sync. In addition, we will also need to act 
// on the messages we receive from our Bluetooth clients (through BlueZ, through D-Bus.) This means that we'll have yet another 
// synchronization issue to resolve, which is to ensure that whatever has been asked of us, makes its way to the correct code in 
// our description so we do the right thing. 
// 
// I don't know about you, but when dealing with data and the concepts "multiple" and "kept in sync" come into play, my spidey 
// sense starts to tingle. The best way to ensure sychronization is to remove the need to keep things sychronized. 
// 
// The large code block below defines a description that includes all the information about our server in a way that can be easily 
// used to generate both: (1) the D-Bus object hierarchy and (2) the BlueZ services that occupy that hierarchy. In addition, we'll 
// take that a step further by including the implementation right inside the description. Everything in one place. 
// 
// >> 
// >>>  MANAGING SERVER DATA 
// >> 
// 
// The purpose of the server is to serve data. Your application is responsible for providing that data to the server via two data 
// accessors (a getter and a setter) that implemented in the form of delegates that are passed into the `ggkStart()` method. 
// 
// While the server is running, if data is updated via a write operation from the client the setter delegate will be called. If your 
// application also generates or updates data periodically, it can push those updates to the server via call to 
// `ggkNofifyUpdatedCharacteristic()` or `ggkNofifyUpdatedDescriptor()`. 
// 
// >> 
// >>>  UNDERSTANDING THE UNDERLYING FRAMEWORKS 
// >> 
// 
// The server description below attempts to provide a GATT-based interface in terms of GATT services, characteristics and 
// descriptors. Consider the following sample: 
// 
//     .gattServiceBegin("text", "00000001-1E3C-FAD4-74E2-97A033F1BFAA") 
//         .gattCharacteristicBegin("string", "00000002-1E3C-FAD4-74E2-97A033F1BFAA", {"read", "write", "notify"}) 
// 
//             .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA 
//             { 
//                 // Abbreviated for simplicity 
//                 self.methodReturnValue(pInvocation, myTextString, true); 
//             }) 
// 
//             .onWriteValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA 
//             { 
//                 // Abbreviated for simplicity 
//                 myTextString = ... 
//             }) 
// 
//             .gattDescriptorBegin("description", "2901", {"read"}) 
//                 .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA 
//                 { 
//                     self.methodReturnValue(pInvocation, "Returns a test string", true); 
//                 }) 
// 
//             .gattDescriptorEnd() 
//         .gattCharacteristicEnd() 
//     .gattServiceEnd() 
// 
// The first thing you may notice abpout the sample is that all of the lines begin with a dot. This is because we're chaining 
// methods together. Each method returns the appropriate type to provide context. For example, The `gattServiceBegin` method returns 
// a reference to a `GattService` object which provides the proper context to create a characteristic within that service. 
// Similarly, the `gattCharacteristicBegin` method returns a reference to a `GattCharacteristic` object which provides the proper 
// context for responding to requests to read the characterisic value or add descriptors to the characteristic. 
// 
// For every `*Begin` method, there is a corresponding `*End` method, which returns us to the previous context. Indentation helps us 
// keep track of where we are. 
// 
// Also note the use of the lambda macros, `CHARACTERISTIC_METHOD_CALLBACK_LAMBDA` and `DESCRIPTOR_METHOD_CALLBACK_LAMBDA`. These 
// macros simplify the process of including our implementation directly in the description. 
// 
// The first parameter to each of the `*Begin` methods is a path node name. As we build our hierarchy, we give each node a name, 
// which gets appended to it's parent's node (which in turns gets appended to its parent's node, etc.) If our root path was 
// "/com/gobbledegook", then our service would have the path "/com/gobbledegook/text" and the characteristic would have the path 
// "/com/gobbledegook/text/string", and the descriptor would have the path "/com/gobbledegook/text/string/description". These paths 
// are important as they act like an addressing mechanism similar to paths on a filesystem or in a URL. 
// 
// The second parameter to each of the `*Begin` methods is a UUID as defined by the Bluetooth standard. These UUIDs effectively 
// refer to an interface. You will see two different kinds of UUIDs: a short UUID ("2901") and a long UUID 
// ("00000002-1E3C-FAD4-74E2-97A033F1BFAA"). 
// 
// For more information on UUDSs, see GattUuid.cpp. 
// 
// In the example above, our non-standard UUIDs ("00000001-1E3C-FAD4-74E2-97A033F1BFAA") are something we generate ourselves. In the 
// case above, we have created a custom service that simply stores a mutable text string. When the client enumerates our services 
// they'll see this UUID and, assuming we've documented our interface behind this UUID for client authors, they can use our service 
// to read and write a text string maintained on our server. 
// 
// The third parameter (which only applies to dharacteristics and descriptors) are a set of flags. You will find the current set of 
// flags for characteristics and descriptors in the "BlueZ D-Bus GATT API description" at: 
// 
//     https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt 
// 
// In addition to these structural methods, there are a small handful of helper methods for performing common operations. These 
// helper methods are available within a method (such as `onReadValue`) through the use of a `self` reference. The `self` reference 
// refers to the object at which the method is invoked (either a `GattCharacteristic` object or a `GattDescriptor` object.) 
// 
//     methodReturnValue and methodReturnVariant 
//         These methods provide a means for returning values from Characteristics and Descriptors. The `-Value` form accept a set 
//         of common types (int, string, etc.) If you need to provide a custom return type, you can do so by building your own 
//         GVariant (which is a GLib construct) and using the `-Variant` form of the method. 
// 
//     sendChangeNotificationValue and sendChangeNotificationVariant 
//         These methods provide a means for notifying changes for Characteristics. The `-Value` form accept a set of common types 
//         (int, string, etc.) If you need to notify a custom return type, you can do so by building your own GVariant (which is a 
//         GLib construct) and using the `-Variant` form of the method. 
// 
// For information about GVariants (what they are and how to work with them), see the GLib documentation at: 
// 
//     https://www.freedesktop.org/software/gstreamer-sdk/data/docs/latest/glib/glib-GVariantType.html 
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
 
#include <algorithm>
 
#include "Server.h"
#include "ServerUtils.h"
#include "Utils.h"
#include "Globals.h"
#include "DBusObject.h"
#include "DBusInterface.h"
#include "GattProperty.h"
#include "GattService.h"
#include "GattUuid.h"
#include "GattCharacteristic.h"
#include "GattDescriptor.h"
#include "Logger.h"
 
namespace ggk {
 
// There's a good chance there will be a bunch of unused parameters from the lambda macros 
#if defined(__GNUC__) && defined(__clang__)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#endif
#if defined(__GNUC__) && !defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
 
// --------------------------------------------------------------------------------------------------------------------------------- 
// Globals 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Our one and only server. It's global. 
std::shared_ptr<Server> TheServer = nullptr;
 
// --------------------------------------------------------------------------------------------------------------------------------- 
// Object implementation 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Our constructor builds our entire server description 
// 
// serviceName: The name of our server (collectino of services) 
// 
//     This is used to build the path for our Bluetooth services. It also provides the base for the D-Bus owned name (see 
//     getOwnedName.) 
// 
//     This value will be stored as lower-case only. 
// 
//     Retrieve this value using the `getName()` method. 
// 
// advertisingName: The name for this controller, as advertised over LE 
// 
//     IMPORTANT: Setting the advertisingName will change the system-wide name of the device. If that's not what you want, set 
//     BOTH advertisingName and advertisingShortName to as empty string ("") to prevent setting the advertising 
//     name. 
// 
//     Retrieve this value using the `getAdvertisingName()` method. 
// 
// advertisingShortName: The short name for this controller, as advertised over LE 
// 
//     According to the spec, the short name is used in case the full name doesn't fit within Extended Inquiry Response (EIR) or 
//     Advertising Data (AD). 
// 
//     IMPORTANT: Setting the advertisingName will change the system-wide name of the device. If that's not what you want, set 
//     BOTH advertisingName and advertisingShortName to as empty string ("") to prevent setting the advertising 
//     name. 
// 
//     Retrieve this value using the `getAdvertisingShortName()` method. 
// 
Server::Server(const std::string &serviceName, const std::string &advertisingName, const std::string &advertisingShortName, 
GGKServerDataGetter getter, GGKServerDataSetter setter)
{
// Save our names 
this->serviceName = serviceName;
std::transform(this->serviceName.begin(), this->serviceName.end(), this->serviceName.begin(), ::tolower);
this->advertisingName = advertisingName;
this->advertisingShortName = advertisingShortName;
 
// Register getter & setter for server data 
dataGetter = getter;
dataSetter = setter;
 
// Adapter configuration flags - set these flags based on how you want the adapter configured 
enableBREDR = false;
enableSecureConnection = false;
enableConnectable = true;
enableAdvertising = true;
enableBondable = false;
 
// 
// Define the server 
// 
 
// Create the root D-Bus object and push it into the list 
objects.push_back(DBusObject(DBusObjectPath() + "com" + getServiceName()));
 
// We're going to build off of this object, so we need to get a reference to the instance of the object as it resides in the 
// list (and not the object that would be added to the list.) 
objects.back()
 
// Service: Device Information (0x180A) 
// 
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.device_information.xml 
.gattServiceBegin("device""180A")
 
// Characteristic: Manufacturer Name String (0x2A29) 
// 
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.manufacturer_name_string.xml 
.gattCharacteristicBegin("mfgr_name""2A29"{"read"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
self.methodReturnValue(pInvocation, "Acme Inc."true);
})
 
.gattCharacteristicEnd()
 
// Characteristic: Model Number String (0x2A24) 
// 
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.model_number_string.xml 
.gattCharacteristicBegin("model_num""2A24"{"read"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
self.methodReturnValue(pInvocation, "Marvin-PA"true);
})
 
.gattCharacteristicEnd()
 
.gattServiceEnd()
 
// Battery Service (0x180F) 
// 
// This is a fake battery service that conforms to org.bluetooth.service.battery_service. For details, see: 
// 
//     https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.battery_service.xml 
// 
// We also handle updates to the battery level from inside the server (see onUpdatedValue). There is an external method 
// (see main.cpp) that updates our battery level and posts an update using ggkPushUpdateQueue. Those updates are used 
// to notify us that our value has changed, which translates into a call to `onUpdatedValue` from the idleFunc (see 
// Init.cpp). 
.gattServiceBegin("battery""180F")
 
// Characteristic: Battery Level (0x2A19) 
// 
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.battery_level.xml 
.gattCharacteristicBegin("level""2A19"{"read""notify"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
uint8_t batteryLevel = self.getDataValue<uint8_t>("battery/level"0);
self.methodReturnValue(pInvocation, batteryLevel, true);
})
 
// Handle updates to the battery level 
// 
// Here we use the onUpdatedValue to set a callback that isn't exposed to BlueZ, but rather allows us to manage 
// updates to our value. These updates may have come from our own server or some other source. 
// 
// We can handle updates in any way we wish, but the most common use is to send a change notification. 
.onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
{
uint8_t batteryLevel = self.getDataValue<uint8_t>("battery/level"0);
self.sendChangeNotificationValue(pConnection, batteryLevel);
return true;
})
 
.gattCharacteristicEnd()
.gattServiceEnd()
 
// Current Time Service (0x1805) 
// 
// This is a time service that conforms to org.bluetooth.service.current_time. For details, see: 
// 
//    https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.service.current_time.xml 
// 
// Like the battery service, this also makes use of events. This one updates the time every tick. 
// 
// This showcases the use of events (see the call to .onEvent() below) for periodic actions. In this case, the action 
// taken is to update time every tick. This probably isn't a good idea for a production service, but it has been quite 
// useful for testing to ensure we're connected and updating. 
.gattServiceBegin("time""1805")
 
// Characteristic: Current Time (0x2A2B) 
// 
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.current_time.xml 
.gattCharacteristicBegin("current""2A2B"{"read""notify"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
self.methodReturnVariant(pInvocation, ServerUtils::gvariantCurrentTime(), true);
})
 
// Update the time every tick of the periodic timer 
// 
// We'll send an change notification to any subscribed clients with the latest value 
.onEvent(1nullptr, CHARACTERISTIC_EVENT_CALLBACK_LAMBDA
{
self.sendChangeNotificationVariant(pConnection, ServerUtils::gvariantCurrentTime());
})
 
.gattCharacteristicEnd()
 
// Characteristic: Local Time Information (0x2A0F) 
// 
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.characteristic.local_time_information.xml 
.gattCharacteristicBegin("local""2A0F"{"read"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
self.methodReturnVariant(pInvocation, ServerUtils::gvariantLocalTime(), true);
})
 
.gattCharacteristicEnd()
.gattServiceEnd()
 
// Custom read/write text string service (00000001-1E3C-FAD4-74E2-97A033F1BFAA) 
// 
// This service will return a text string value (default: 'Hello, world!'). If the text value is updated, it will notify 
// that the value has been updated and provide the new text from that point forward. 
.gattServiceBegin("text""00000001-1E3C-FAD4-74E2-97A033F1BFAA")
 
// Characteristic: String value (custom: 00000002-1E3C-FAD4-74E2-97A033F1BFAA) 
.gattCharacteristicBegin("string""00000002-1E3C-FAD4-74E2-97A033F1BFAA"{"read""write""notify"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
const char *pTextString = self.getDataPointer<const char *>("text/string""");
self.methodReturnValue(pInvocation, pTextString, true);
})
 
// Standard characteristic "WriteValue" method call 
.onWriteValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
// Update the text string value 
GVariant *pAyBuffer = g_variant_get_child_value(pParameters, 0);
self.setDataPointer("text/string", Utils::stringFromGVariantByteArray(pAyBuffer).c_str());
 
// Since all of these methods (onReadValue, onWriteValue, onUpdateValue) are all part of the same 
// Characteristic interface (which just so happens to be the same interface passed into our self 
// parameter) we can that parameter to call our own onUpdatedValue method 
self.callOnUpdatedValue(pConnection, pUserData);
})
 
// Here we use the onUpdatedValue to set a callback that isn't exposed to BlueZ, but rather allows us to manage 
// updates to our value. These updates may have come from our own server or some other source. 
// 
// We can handle updates in any way we wish, but the most common use is to send a change notification. 
.onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
{
const char *pTextString = self.getDataPointer<const char *>("text/string""");
self.sendChangeNotificationValue(pConnection, pTextString);
return true;
})
 
// GATT Descriptor: Characteristic User Description (0x2901) 
//  
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml 
.gattDescriptorBegin("description""2901"{"read"})
 
// Standard descriptor "ReadValue" method call 
.onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
{
const char *pDescription = "A mutable text string used for testing. Read and write to me, it tickles!";
self.methodReturnValue(pInvocation, pDescription, true);
})
 
.gattDescriptorEnd()
 
.gattCharacteristicEnd()
.gattServiceEnd()
 
// Custom ASCII time string service 
// 
// This service will simply return the result of asctime() of the current local time. It's a nice test service to provide 
// a new value each time it is read. 
 
// Service: ASCII Time (custom: 00000001-1E3D-FAD4-74E2-97A033F1BFEE) 
.gattServiceBegin("ascii_time""00000001-1E3D-FAD4-74E2-97A033F1BFEE")
 
// Characteristic: ASCII Time String (custom: 00000002-1E3D-FAD4-74E2-97A033F1BFEE) 
.gattCharacteristicBegin("string""00000002-1E3D-FAD4-74E2-97A033F1BFEE"{"read"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
// Get our local time string using asctime() 
time_t timeVal = time(nullptr);
struct tm *pTimeStruct = localtime(&timeVal);
std::string timeString = Utils::trim(asctime(pTimeStruct));
 
self.methodReturnValue(pInvocation, timeString, true);
})
 
// GATT Descriptor: Characteristic User Description (0x2901) 
//  
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml 
.gattDescriptorBegin("description""2901"{"read"})
 
// Standard descriptor "ReadValue" method call 
.onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
{
const char *pDescription = "Returns the local time (as reported by POSIX asctime()) each time it is read";
self.methodReturnValue(pInvocation, pDescription, true);
})
 
.gattDescriptorEnd()
 
.gattCharacteristicEnd()
.gattServiceEnd()
 
// Custom CPU information service (custom: 0000B001-1E3D-FAD4-74E2-97A033F1BFEE) 
// 
// This is a cheezy little service that reads the CPU info from /proc/cpuinfo and returns the count and model of the 
// CPU. It may not work on all platforms, but it does provide yet another example of how to do things. 
 
// Service: CPU Information (custom: 0000B001-1E3D-FAD4-74E2-97A033F1BFEE) 
.gattServiceBegin("cpu""0000B001-1E3D-FAD4-74E2-97A033F1BFEE")
 
// Characteristic: CPU Count (custom: 0000B002-1E3D-FAD4-74E2-97A033F1BFEE) 
.gattCharacteristicBegin("count""0000B002-1E3D-FAD4-74E2-97A033F1BFEE"{"read"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
int16_t cpuCount = 0;
ServerUtils::getCpuInfo(cpuCount);
self.methodReturnValue(pInvocation, cpuCount, true);
})
 
// GATT Descriptor: Characteristic User Description (0x2901) 
//  
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml 
.gattDescriptorBegin("description""2901"{"read"})
 
// Standard descriptor "ReadValue" method call 
.onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
{
const char *pDescription = "This might represent the number of CPUs in the system";
self.methodReturnValue(pInvocation, pDescription, true);
})
 
.gattDescriptorEnd()
 
.gattCharacteristicEnd()
 
// Characteristic: CPU Model (custom: 0000B003-1E3D-FAD4-74E2-97A033F1BFEE) 
.gattCharacteristicBegin("model""0000B003-1E3D-FAD4-74E2-97A033F1BFEE"{"read"})
 
// Standard characteristic "ReadValue" method call 
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
int16_t cpuCount = 0;
self.methodReturnValue(pInvocation, ServerUtils::getCpuInfo(cpuCount), true);
})
 
// GATT Descriptor: Characteristic User Description (0x2901) 
//  
// See: https://www.bluetooth.com/specifications/gatt/viewer?attributeXmlFile=org.bluetooth.descriptor.gatt.characteristic_user_description.xml 
.gattDescriptorBegin("description""2901"{"read"})
 
// Standard descriptor "ReadValue" method call 
.onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
{
const char *pDescription = "Possibly the model of the CPU in the system";
self.methodReturnValue(pInvocation, pDescription, true);
})
 
.gattDescriptorEnd()
 
.gattCharacteristicEnd()
.gattServiceEnd(); // << -- NOTE THE SEMICOLON 
 
//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
//                                                ____ _____ ___  _____ 
//                                               / ___|_   _/ _ \|  _  | 
//                                               \___ \ | || | | | |_) | 
//                                                ___) || || |_| |  __/ 
//                                               |____/ |_| \___/|_| 
// 
// You probably shouldn't mess with stuff beyond this point. It is required to meet BlueZ's requirements for a GATT Service. 
// 
// >> 
// >>  WHAT IT IS 
// >> 
// 
// From the BlueZ D-Bus GATT API description (https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt): 
// 
//     "To make service registration simple, BlueZ requires that all objects that belong to a GATT service be grouped under a 
//     D-Bus Object Manager that solely manages the objects of that service. Hence, the standard DBus.ObjectManager interface 
//     must be available on the root service path." 
// 
// The code below does exactly that. Notice that we're doing much of the same work that our Server description does except that 
// instead of defining our own interfaces, we're following a pre-defined standard. 
// 
// The object types and method names used in the code below may look unfamiliar compared to what you're used to seeing in the 
// Server desecription. That's because the server description uses higher level types that define a more GATT-oriented framework 
// to build your GATT services. That higher level functionality was built using a set of lower-level D-Bus-oriented framework, 
// which is used in the code below. 
//  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  - 
 
// Create the root object and push it into the list. We're going to build off of this object, so we need to get a reference 
// to the instance of the object as it resides in the list (and not the object that would be added to the list.) 
// 
// This is a non-published object (as specified by the 'false' parameter in the DBusObject constructor.) This way, we can 
// include this within our server hieararchy (i.e., within the `objects` list) but it won't be exposed by BlueZ as a Bluetooth 
// service to clietns. 
objects.push_back(DBusObject(DBusObjectPath(), false));
 
// Get a reference to the new object as it resides in the list 
DBusObject &objectManager = objects.back();
 
// Create an interface of the standard type 'org.freedesktop.DBus.ObjectManager' 
// 
// See: https://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager 
auto omInterface = std::make_shared<DBusInterface>(objectManager, "org.freedesktop.DBus.ObjectManager");
 
// Add the interface to the object manager 
objectManager.addInterface(omInterface);
 
// Finally, we setup the interface. We do this by adding the `GetManagedObjects` method as specified by D-Bus for the 
// 'org.freedesktop.DBus.ObjectManager' interface. 
const char *pInArgs[] = { nullptr };
const char *pOutArgs = "a{oa{sa{sv}}}";
omInterface->addMethod("GetManagedObjects", pInArgs, pOutArgs, INTERFACE_METHOD_CALLBACK_LAMBDA
{
ServerUtils::getManagedObjects(pInvocation);
});
}
 
// --------------------------------------------------------------------------------------------------------------------------------- 
// Utilitarian 
// --------------------------------------------------------------------------------------------------------------------------------- 
 
// Find a D-Bus interface within the given D-Bus object 
// 
// If the interface was found, it is returned, otherwise nullptr is returned 
std::shared_ptr<const DBusInterface> Server::findInterface(const DBusObjectPath &objectPath, const std::string &interfaceName) const
{
for (const DBusObject &object : objects)
{
std::shared_ptr<const DBusInterface> pInterface = object.findInterface(objectPath, interfaceName);
if (pInterface != nullptr)
{
return pInterface;
}
}
 
return nullptr;
}
 
// Find and call a D-Bus method within the given D-Bus object on the given D-Bus interface 
// 
// If the method was called, this method returns true, otherwise false. There is no result from the method call itself. 
bool Server::callMethod(const DBusObjectPath &objectPath, const std::string &interfaceName, const std::string &methodName, GDBusConnection *pConnection, GVariant *pParameters, GDBusMethodInvocation *pInvocation, gpointer pUserData) const
{
for (const DBusObject &object : objects)
{
if (object.callMethod(objectPath, interfaceName, methodName, pConnection, pParameters, pInvocation, pUserData))
{
return true;
}
}
 
return false;
}
 
// Find a GATT Property within the given D-Bus object on the given D-Bus interface 
// 
// If the property was found, it is returned, otherwise nullptr is returned 
const GattProperty *Server::findProperty(const DBusObjectPath &objectPath, const std::string &interfaceName, const std::string &propertyName) const
{
std::shared_ptr<const DBusInterface> pInterface = findInterface(objectPath, interfaceName);
 
// Try each of the GattInterface types that support properties? 
if (std::shared_ptr<const GattInterface> pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattInterface))
{
return pGattInterface->findProperty(propertyName);
}
else if (std::shared_ptr<const GattService> pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattService))
{
return pGattInterface->findProperty(propertyName);
}
else if (std::shared_ptr<const GattCharacteristic> pGattInterface = TRY_GET_CONST_INTERFACE_OF_TYPE(pInterface, GattCharacteristic))
{
return pGattInterface->findProperty(propertyName);
}
 
return nullptr;
}
 
}// namespace ggk