Gobbledegook
I'm a firm believer that a maintainer should be, at least in some part, a consumer of the thing they're maintaining. I built GGK for a personal project. That project's communication needs have grown considerably, beyond the point where Bluetooth LE is a viable option and I was forced to make the switch to an IP-based solution. As much as I've enjoyed building and using GGK, I no longer have a use-case for Bluetooh LE or a test-case for GGK.
Install / Use
/learn @rivermoonlight/GobbledegookREADME
Seeking a new maintainer
I'm a firm believer that a maintainer should be, at least in some part, a consumer of the thing they're maintaining. I built GGK for a personal project. That project's communication needs have grown considerably, beyond the point where Bluetooth LE is a viable option and I was forced to make the switch to an IP-based solution. As much as I've enjoyed building and using GGK, I no longer have a use-case for Bluetooh LE or a test-case for GGK.
If you are a GGK user and would like to become an advocate for its future development, please contact me.
News
Jun 24, 2019 - New license
This author has deciced that this software should be free. Furthermore, this author's choice should not limit the freedoms of other authors by restricting their choices. As a result, Gobbledegook is now licensed under the New BSD License.
Feb 1, 2019
Gobbledegook's license changed from GPL to LGPL in hopes that it would be found useful to more developers.
What is Gobbledegook?
Gobbledegook is a C/C++ standalone Linux Bluetooth LE GATT server using BlueZ over D-Bus with Bluetooth Management API support built in. That's a lot of words, so I shortened it to Gobbledegook. Then I shortened it again to GGK because let's be honest, it's a pain to type.
For the impatient folks in a hurry (or really just have to pee) skip down to the Quick-start for the impatient section at the bottom of this document.
Features
- DSL-like creation of BLE services makes creating services trivial
- Automated D-Bus object hierarchy introspection generation
- Automated D-Bus ObjectManager implementation
- Automated BlueZ GATT application registration
- Support for the Bluetooth Management API
- Timer events allow services to perform periodic updates
- Application-managed server data
- Comment blocks at the top of each source file with deep explanations of critical concepts
- Written in C++14 (gcc & clang) with a standard C public interface
- Tested on Ubuntu 16.04 on x86 and Raspberry Pi
Server description
A server description is the meat of your server. It is a collection one or more GATT Services. Each GATT Service contains a collection of one or more characteristics, which in turn may contain zero or more descriptors. The server description is declared in the Server constructor found in Server.cpp.
The following sections explain how to build a server description.
New to Bluetooth terminology?
Bluetooth Low Energy (or BLE) is the marketing term for Bluetooth 4.0. A BLE server will offer up one or more GATT Services.
GATT stands for Generic Attribute Profile (apparently the P is silent.) This is the standard that defines how BLE devices share data. Specifically, it defines the structure of services, characteristics and descriptors.
A GATT Service can be thought of as a discreet unit of functionality. Examples would be a time service that returns the current local time and timezone or a battery service that returns the current battery level and temperature. A GATT Service serves up information in the form of a collection of one or more GATT Characteristics.
A GATT Characteristic is one unit of data from a GATT Service. For example, our fictitious battery service would have two GATT Characteristics: (1) the current battery level and (2) the temperature. The battery level characteristic might be defined as a single byte value in the range of 0-100 representing the percentage of battery remaining. In addition to their data, a GATT Characteristic may also contain zero or more optional GATT Descriptors.
A GATT Descriptor contains additional information and metadata about a GATT Characteristic. They can be used to describe the characteristic's features or to control certain behaviors of the characteristic. Extending our battery service example further, the temperature characteristic could be a 16-bit value representing a temperature, while the GATT Descriptor further defines how to interpret that data (as degrees Fahrenheit, degrees Celsius or 10th of degrees Kelvin.)
Implementing services with GGK
Below is a complete custom GATT Service as defined within the GGK framework. It is a simple service that uses the asctime() function to return the current time as a string:
// Create a service
.gattServiceBegin("ascii_time", "00000001-1E3D-FAD4-74E2-97A033F1BFEE")
// Add a characteristic to the service with the 'read' flag set
.gattCharacteristicBegin("string", "00000002-1E3D-FAD4-74E2-97A033F1BFEE", {"read"})
// Handle the characteristic's "ReadValue" method
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
time_t timeVal = time(nullptr);
struct tm *pTimeStruct = localtime(&timeVal);
std::string timeString = asctime(pTimeStruct);
self.methodReturnValue(pInvocation, timeString, true);
})
.gattCharacteristicEnd()
.gattServiceEnd()
The first thing you may notice about this example is that many of the lines begin with a dot. This is because we're chaining methods together. Each method returns the appropriate type to provide context. Internally 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 the onReadValue() method or adding descriptors to the characteristic.
You may also have noticed that we're using lambdas to include our implementation inline. The code to generate the time string is wrapped up in a CHARACTERISTIC_METHOD_CALLBACK_LAMBDA which is just a convenience macro that declares the lambda properly for us. You can use the raw lambda declaration if you wish, but then you're being anti-macroist and that's just not cool, bruh. And if you don't like these new-fangled lambdas, you can just stick a good ol' function pointer in its place.
Side note
A compiled GGK library provides a public interface that is compatible with standard C, but you'll need a modern compiler to build a GGK library because the internals are written using features of c++11.
Let's take a look at a more complex example. Here's an implementation of the Bluetooth standard's Current Time Service. We'll even toss a few extras in to keep things interesting:
// Current Time Service (0x1805)
.gattServiceBegin("time", "1805")
.gattCharacteristicBegin("current", "2A2B", {"read", "notify"})
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
self.methodReturnVariant(pInvocation, ServerUtils::gvariantCurrentTime(), true);
})
.gattDescriptorBegin("description", "2901", {"read"})
.onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
{
self.methodReturnValue(pInvocation, "Current server local time", true);
})
.gattDescriptorEnd()
.onEvent(60, nullptr, CHARACTERISTIC_EVENT_CALLBACK_LAMBDA
{
self.sendChangeNotificationVariant(pConnection, ServerUtils::gvariantCurrentTime());
})
.gattCharacteristicEnd()
.gattCharacteristicBegin("local", "2A0F", {"read"})
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
self.methodReturnVariant(pInvocation, ServerUtils::gvariantLocalTime(), true);
})
.gattDescriptorBegin("description", "2901", {"read"})
.onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
{
self.methodReturnValue(pInvocation, "Local time data", true);
})
.gattDescriptorEnd()
.gattCharacteristicEnd()
.gattServiceEnd()
If you're already familiar with BLE, then hopefully the expansion to multiple characteristics and the addition of descriptors needs no further explanation. If that's true, then you're probably amazed by that. Maybe a more modest level of amazement than it's-bigger-on-the-inside amazement levels, but you should still be sure to catch your breath before trying to read further. Safety first.
Did you notice the bonus call to onEvent()? The event (a TickEvent to be specific) is not part of the Bluetooth standard. It works similar to a typical GUI timer event. In this example, we're using it to send out a change notification (a "PropertiesChanged" notification in the standard parlance). Any client that has subscribed to that characteristic will receive an updated time every 60 ticks (seconds.)
Contexts
Working in hierarchical contexts of services, characteristics, descriptors and methods simplifies the process building a server description because each context has a limited set of available tools to work with. For example, within a service context the only tools available are gattCharacteristicBegin() and gattServiceEnd(). This isn't a limitation on your flexibility; anything else would be a deviation from the specification.
TIP: A context is a scope
A GGK context is a synonym for the C term scope.
The Service context is another way of saying the
GattServiceobject scope. Similarly, Characteristics and Descriptors are scopes of theGattCharacteristicandGattDescriptorobjects.Methods differ slightly in that they are scoped to their lambdas. However, Methods also contain a
selfparameter which is a reference to the containing scope. In other words, a Method within a Descriptor will have aselfreference to theGattDescriptorobject wh
