Giter Club home page Giter Club logo

gobbledegook's Introduction

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 GattService object scope. Similarly, Characteristics and Descriptors are scopes of the GattCharacteristic and GattDescriptor objects.

Methods differ slightly in that they are scoped to their lambdas. However, Methods also contain a self parameter which is a reference to the containing scope. In other words, a Method within a Descriptor will have a self reference to the GattDescriptor object where that method is declared. More on this in the Lambda reference section.

Service cheat sheet

GATT Services are chained together to form a complete server description. So let's focus on how a GATT Service is built.

Below is a template for a GATT Service with all available components. Function parameters are shown by name and indentation denotes context.

Note that a service may contain more than one characteristic, in which case they would follow each other sequentially within the service description. The same holds true for Descriptors, except that a characteristic may have no descriptors. In addition, each of the four methods found in the characteristics and descriptors (.onEvent(), .onReadValue(), .onWriteValue() and .onUpdateValue()) would only be present if needed.

.gattServiceBegin(name, uuid)
    .gattCharacteristicBegin(name, uuid, flags[])
        .onEvent(tickFrequency, userData, CHARACTERISTIC_EVENT_CALLBACK_LAMBDA
        {
            [...your code here...]
        })
        .onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
        {
            [...your code here...]
        })
        .onWriteValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
        {
            [...your code here...]
        })
        .onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
        {
            [...your code here...]
        })
        .gattDescriptorBegin(name, uuid, flags[])
            .onEvent(tickFrequency, userData, DESCRIPTOR_EVENT_CALLBACK_LAMBDA
            {
                [...your code here...]
            })
            .onReadValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
            {
                [...your code here...]
            })
            .onWriteValue(DESCRIPTOR_METHOD_CALLBACK_LAMBDA
            {
                [...your code here...]
            })
            .onUpdatedValue(DESCRIPTOR_UPDATED_VALUE_CALLBACK_LAMBDA
            {
                [...your code here...]
            })
        .gattDescriptorEnd()
    .gattCharacteristicEnd()
.gattServiceEnd()

Method reference

The following methods are available within the context of either a characteristic or descriptor.


onReadValue(callback_or_lambda)

Register a lambda or callback that is called whenever a Bluetooth client reads the value of a characteristic or descriptor. It is tied to the ReadValue method described in the BlueZ D-Bus GATT API.


onWriteValue(callback_or_lambda)

Register a lambda or callback that is called whenever a Bluetooth client writes to the value of a characteristic or descriptor. It is tied to the WriteValue method described in the BlueZ D-Bus GATT API.


onEvent(int tickFrequency, void *pUserData, callback_or_lambda)

Register a lambda or callback that is called after tickFrequency ticks of the periodic timer. Tick events work similar to timer events found in modern GUIs.

Events can be used to update server data, send notifications or perform any other general periodic work. This is a convenience method of GGK and is not part of the Bluetooth standard or BlueZ D-Bus GATT API.


onUpdatedValue(callback_or_lambda)

Register a lambda or callback that is called when data is updated internally by the application.

As an application generates or updates its own data, it may notify the server of those updates using the public interface methods ggkNofifyUpdatedCharacteristic() and ggkNofifyUpdatedDescriptor(). The server will then call the appropriate onUpdateValue lambda or callback for the characteristic or descriptor receiving the update. This is a convenience method of GGK and is not part of the Bluetooth standard or BlueZ D-Bus GATT API.

Aside from the application performing data updates, a characteristic or descriptor may modify its own data from within a lambda and trigger this call. For details, see self.callOnUpdatedValue() method in the Lambda reference section below.

Lambda reference

Within the context of a lambda there is a self parameter that references the parent context (the characteristic or descriptor under which the lambda is registered.)

The following methods are available to through that self parameter.


T self.getDataValue(const char *name, const T default)

Gets a named value from the server data (see the section Server data for details on how this data is managed.) If name is not found, default is returned. This is a templated function to allow non-pointer data of any type to be retrieved. For pointer data, see self.getDataPointer().


T self.getDataPointer(const char *name, const T default)

Gets a named pointer from the server data (see the section Server data for details on how this data is managed.) If name is not found, default is returned. This is a templated function to allow pointer data of any type to be retrieved. Note that T is a pointer type. For non-pointer values, see self.getDataValue().


bool self.setDataValue(const char *name, const T value)

Sets the named value in the server data (see the section Server data for details on how this data is managed.) This is a templated function to allow non-pointer data of any type to be set. For pointer data, see self.setDataPointer().


bool self.setDataPointer(const char *name, const T pointer)

Sets the named pointer in the server data (see the section Server data for details on how this data is managed.) This is a templated function to allow pointer data of any type to be set. Note that T is a pointer type. For non-pointer values, see self.setDataValue().


bool self.callOnUpdatedValue(self.pConnection, void *pUserData)

Call the parent context's onUpdatedValue() method.

This is a convenience method for situations where a lambda modifies the data of its parent characteristic or descriptor and wishes to notify it's own parent of the change. This simplifies reuse of code by placing all data update management code in the onUpdatedValue() registered lambda.


void self.methodReturnValue(self.pInvocation, T value, bool wrapInTuple = false)

Send a value to a Bluetooth client in response to a method call (such as ReadValue).

Unlike normal return values from methods within a program, return values in response to Bluetooth method calls must be explicitly sent.

This is a templated function to allow common data types to be automatically placed into a GVariant array of bytes ("ay") with the option of wrapping the response in a tuple ("(ay)"). For a generic GVariant * version of this method, see methodReturnVariant().

For information on GVariants, see the GLib reference manual.


void self.methodReturnVariant(self.pInvocation, GVariant *pVariant, bool wrapInTuple = false)

Send a GVariant to a Bluetooth client in response to a method call (such as ReadValue).

Unlike normal return values from methods within a program, return values in response to Bluetooth method calls must be explicitly sent.

If wrapInTuple is set to true, the pVariant is automatically wrapped in a tuple before sending. A convenience function is available for responding with common data types (see methodReturnValue()).

For information on GVariants, see the GLib reference manual.


void self.sendChangeNotificationVariant(self.pBusConnection, GVariant *pNewValue)

Sends a change notification (in BlueZ parlance, a "PropertiesChanged" signal) with an updated GVariant * value to subscribers to this characteristic.

This is a generalized method that accepts a GVariant *. A templated version is available that supports common types called sendChangeNotificationValue().

For information on GVariants, see the GLib reference manual.

NOTE: This method is only available to characteristics.


void self.sendChangeNotificationValue(self.pBusConnection, T value)

Sends a change notification (in BlueZ parlance, a "PropertiesChanged" signal) to subscribers to this characteristic.

This is a helper method that accepts common types. For custom types, there is a form that accepts a GVariant *, called sendChangeNotificationVariant().

NOTE: This method is only available to characteristics.

Server Data

Server data is maintained by the application. When the application starts the GGK server, it calls ggkStart() with two delegates: a data getter and a data setter. These methods are used by the server to retrieve and store server data.

For details on these delegates and their usage, see the comment blocks in Gobbledegook.h under the section heading SERVER DATA.

A brief look under the hood

When we build a server description, what we're really doing is building a hierarchical structure of D-Bus objects that conforms to BlueZ's standards for GATT services. The *Begin() and *End() calls are the building blocks for this hierarchy.

GGK uses this server description to build an XML introspection used to register the object hierarchy with D-Bus. You can view the generated XML for your services by launching the standalone application with the -d parameter.

GGK also uses the server description to implement the D-Bus ObjectManager interface which is used to describe our services. When we eventually register our application with BlueZ, BlueZ will use our ObjectManager interface to enumerate our services. GGK manages this for us automatically.

System configuration tips

The following sections may help users with their system configurations.

BlueZ Version

My distribution didn't have the latest version of BlueZ with proper support for their D-Bus API (it was still considered experimental up until v5.42.) I had to download and build it for myself. I grabbed BlueZ 5.45.

Here's how I built it - this will probably work for most or all Ubuntu users:

./configure --prefix=/usr --libexecdir=/usr/lib \
--sysconfdir=/etc --localstatedir=/var --enable-test \
--enable-manpages --enable-testing --enable-library \
--enable-maintainer-mode --enable-experimental \
--enable-deprecated
make
service bluetooth stop
sudo make install           # Helpful for development
systemctl daemon-reload     # Helpful for development
service bluetooth start     # Helpful for development

If you're on a Raspberry Pi, you should probably also run sudo rpi-update followed by a reboot.

Configuring BlueZ

The configuration file is located at /etc/bluetooth/main.conf. However, you should be fine with defaults here because this server can configure the settings as needed.

Enable BlueZ debug logs

Edit the file /lib/systemd/system/bluetooth.service and set the ExecStart line to look like:

ExecStart=/usr/lib/bluetooth/bluetoothd -d --noplugin=hostname

-d - it's a good idea to leave this enabled while you are setting up your services in GGK (you can remove it later). This will allow you to tail -f /var/log/syslog | grep bluetoothd to get some helpful debug information.

--noplugin=hostname - this prevents BlueZ from running the 'hostname' plugin, which will rename your device to whatever its hostname is (ignoring your configurations.)

Once you've done this, run the following commands to reload these settings and restart the service:

sudo systemctl daemon-reload
sudo service bluetooth stop
sudo service bluetooth start

Enabling D-Bus Permissions

In order for our application to communicate over D-Bus, we'll need to ask D-Bus for an owned name, which will in effect be our address on D-Bus. D-Bus must be configured to grant us permissions to do this. We'll grant these permissions to user root.

You'll need to locate the D-Bus permissions on your box. Likely, you'll find a set of files for this in the directory /etc/dbus-1/system.d. Create the file /etc/dbus-1/system.d/gobbledegook.conf and give it the contents:

<!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
<busconfig>
  <policy user="root">
    <allow own="com.gobbledegook"/>
    <allow send_destination="com.gobbledegook"/>
    <allow send_destination="org.bluez"/>
  </policy>
  <policy at_console="true">
    <allow own="com.gobbledegook"/>
    <allow send_destination="com.gobbledegook"/>
    <allow send_destination="org.bluez"/>
  </policy>
  <policy context="default">
    <deny send_destination="com.gobbledegook"/>
  </policy>
</busconfig>

Note the com.gobbledegook entries in your new gobbledegook.conf file. This must match the service name (the first parameter sent to ggkStart() in standalone.cpp). If you change the service name from gobbledegook to clownface in that call to ggkStart(), then you'll need to edit the gobbledegook.conf file and change all occurrances of com.gobbledegook to com.clownface.

Enabling Bluetooth

You don't need to do anything. this server will automatically power on the adapter, enable LE with advertisement.

However, if you want to do this manually, here are a few helpful commands you might try:

sudo btmgmt -i 0 power off
sudo btmgmt -i 0 le on
sudo btmgmt -i 0 connectable on
sudo btmgmt -i 0 advertising on
sudo btmgmt -i 0 power on

Build & Launch

GGK uses the standard autotools build process:

./configure && make

This will build libggk.a then compile standalone.cpp into a program that links with libggk.a. There is no make install as there is nothing to install.

Then run with:

sudo src/standalone -d

GGK requires super-user privileges when run due to privileges required for D-Bus and HCI sockets. A system can be configured to allow a user to run a GGK server without sudo, but that's beyond the scope of this document.

During development, I tend to run these three commands, each in their own terminal:

sudo tail -f /var/log/syslog | grep bluetoothd
sudo dbus-monitor --system
sudo ./src/standalone -d

With no parameters, standalone will output only service level output (starting stopping errors, etc.) Additional output parameters are:

`-q`        Quiet - errors only
`-v`        Verbose - include info log levels
`-d`        Debug - include debug log levels

Testing your server

If you don't already have some kind of test harness, you'll probably want something. I've had luck with a free Android app called nRF Connect.

Integration into your own app

Think of Gobbledegook as a template BLE library. You're expected to modify Server.cpp and replace the example services with your own. Once you've customized your services, the rest is easy.

Just link against libggk.a and include include/Gobbledegook.h to access the public API in your app. You may notice that the public interface isn't documented here. Instead, it is documented in include/Gobbledegook.h.

You can use standalone.cpp as a reference on how to get things setup in your code.

Other handy references

If you decide to dig into the codebase, you'll want to be familiar with D-Bus. If you've never messed with D-Bus, this short Introduction to D-Bus will help a lot. GGK uses GLib's GIO for all D-Bus work, which is fully documented in the GIO Reference Manual. And finally, for the real nitty-gritty on D-Bus be sure to visit the D-Bus Specification.

The BlueZ D-Bus APIs are fully documented. They're brief, but complete. Pay special attention to the BlueZ D-Bus GATT API description, which describes how GATT services are implemented over D-Bus.

The GLib Reference Manual covers many topics used in GGK, most notably the use of GVariant objects for passing data around over D-Bus. Pay special attention to the GVariant Format Strings page.

Reference output

The following is the output from a reference tool used to connect to the standalone server running with the sample services. The output shows data read from the server over BLE. Note that the hieararchy may not match 1:1 with that of the samples, since some features are automatically provided by BlueZ. In addition, the reference tool periodically writes updates to the Text string service (hence the appended text, "(updated: 3 times)".)

Connected to Gobbledook

* Service: Device Information
  > Characteristic: Manufacturer Name String
    + Value (String): 'Acme Inc.'
    + Flags: {read}
  > Characteristic: Model Number String
    + Value (String): 'Marvin-PA'
    + Flags: {read}

* Service: (Example) CPU Info
  > Characteristic: CPU Count
    + Value (UInt16): 4
    + Flags: {read}
      - Descriptor: Characteristic user description
        - Value (String): 'This might represent the number of CPUs in the system'
  > Characteristic: CPU Model
    + Value (String): 'ARMv7 Processor rev 4 (v7l)'
    + Flags: {read}
      - Descriptor: Characteristic user description
        - Value (String): 'Possibly the model of the CPU in the system'

* Service: Battery Information
  > Characteristic: Battery Level
    + Value (Percent): 75%
    + Flags: {read, notify}
      - Descriptor: Client characteristic configuration
        - Value (Flags): Notifications enabled[false], Indications enabled[false]

* Service: (Example) asctime()
  > Characteristic: Ascii Time
    + Value (String): 'Thu Aug 24 18:43:10 2017'
    + Flags: {read}
      - Descriptor: Characteristic user description
        - Value (String): 'Returns the local time (as reported by POSIX asctime()) each time it is read'

* Service: (Example) Text string
  > Characteristic: String value
    + Value (String): 'Hello, world (updated: 3 times)'
    + Flags: {read, write, notify}
      - Descriptor: Client characteristic configuration
        - Value (Flags): Notifications enabled[false], Indications enabled[false]
      - Descriptor: Characteristic user description
        - Value (String): 'A mutable text string used for testing. Read and write to me, it tickles!'

* Service: Current Time
  > Characteristic: Current Time
    + Value (DateTime): Thu, 2017/08/24 18:43:29.00
    + Flags: {read, notify}
      - Descriptor: Client characteristic configuration
        - Value (Flags): Notifications enabled[false], Indications enabled[false]
  > Characteristic: Local Time Information
    + Value (LocTime): utcOffset: -5.0 hours, DST (+1h)
    + Flags: {read}

Quick-start for the impatient

Build with ./configure && make. This will build libggk.a then compile standalone.cpp into a program that links with libggk.a.

Run sudo ./src/standalone -d. This will require your machine to be setup correctly with BlueZ and working BLE hardware. Don't forget the sudo; you'll need it unless you configure the appropriate permissions for your user to access to D-Bus and the HCI socket. Options are: -e (errors-only output), -v (verbose output), -d (debug output)

Coding your own BLE services is actually very easy once you become a little familiar with things. Think of Gobbledegook as a template BLE library. You're expected to modify Server.cpp and replace the example services with your own. Do yourself a favor and scan through the Implementing services with GGK section. It's your quickest path to understanding the foundation.

Integrating into your app is easy: just link against libggk.a and include include/Gobbledegook.h where needed. Use standalone.cpp as a template for getting things setup in your code.

gobbledegook's People

Contributors

nettlep avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

gobbledegook's Issues

Pairing/Bonding planned?

Hi! Thanks for the great code!! Do you have any plans to integrate pairing/bonding some time?
Best regards

Support for org.bluez.LEAdvertisingManager1 ?

Hi, I wanted to advertise a service UUID.

Your library is great but it seems service UUID advertisement is not supported (or I couldn't get it to work).

I'm not familiar with D-Bus interface but it seems org.bluez.LEAdvertisingManager1 is needed. Also it looks like the XML must include

Any hints on how it could be added to your library?

Thanks,

!!ERROR: Unsupported response event type: 0x0011 (Authentication Failed Event)

Same error as #10 (comment)
Connection an iOS device works fine for a while, an error appear looping

!!ERROR: Unsupported response event type: 0x0011 (Authentication Failed Event)

You mentioned that it doesn't support pairing #9 (comment)
configuring Bluetooth to use pairable on pairs the device and the message goes away.

Is there a way to resolve this without pairing the device?
This error can be reproduced with any generic BLE discovery app in the app store.

is the error uuid referring to the
HIDP | 0x0011 | Human Interface Device Profile (HID)
https://www.bluetooth.com/specifications/assigned-numbers/service-discovery
and so do we need to setup a authorize characteristic?
`from https://git.kernel.org/pub/scm/bluetooth/bluez.git/plain/doc/gatt-api.txt

Another workaround is to ignore the errors, as the server and the client works fine with them.

Managment API not needed anymore

Firstly what a well written and nicely documented project.

Secondly, the last time this project was active was 7 years ago. I'm not sure what has changed w.r.t. bluez and its dbus implementation but I am guessing most of the stuff in Mgmt.cpp wasn't there. That's no longer true so although supporting the Management API still works, a lot of those API's can be found in the org.bluez.Adapter1 interface exposed in the /org/bluez/hc0 object

Making GGK work on Gentoo64?

Hi. I've been able to build GGK on gentoo64 (sakaki's distro for Raspberry Pi.)

Now I'm trying "sudo src/standalone -d", but it fails:

   INFO: Starting GGK server 'Gobbledegook'
 STATUS: ** SERVER RUN STATE CHANGED: Uninitialized -> Initializing
  DEBUG: Acquiring bus connection
  DEBUG: Creating GLib main loop
-Trace-: Starting GLib main loop
  DEBUG: Acquiring owned name: 'com.gobbledegook'
  DEBUG: Getting BlueZ ObjectManager
  DEBUG: Finding BlueZ GattManager1 interface
!!ERROR: Unable to find the adapter
WARNING:   + Will retry the failed operation in about 2 seconds

Any hints about what could go wrong? Thanks!

to send large amount of data

hi,

I'd like to send large amount of data, but I don't know how to maximise the speed to send to iOS...but I don't know what the problem with my code :(
could you take a look at?

.gattCharacteristicBegin("toPhone", "----", {"read", "notify"})
.onReadValue(CHARACTERISTIC_METHOD_CALLBACK_LAMBDA
{
int16_t cpuCount = 4;
self.methodReturnValue(pInvocation, cpuCount, true);
})

      .onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
      {
        const std::vector<guint8> Default;
        const std::vector<guint8>* pDataVec = self.getDataPointer<const std::vector<guint8>*>("Data/toPhone",&Default);

          size_t pos = 0;
          size_t left = pDataVec->size();
          const size_t MaxNumtoSend = 100;
          while( left > 0 )
          {
              const size_t toSend = left > MaxNumtoSend ? MaxNumtoSend : left ;
              const std::vector<uint8_t> chunk(pDataVec->data() + pos , pDataVec->data() + pos + toSend);
              self.sendChangeNotificationValue(pConnection,chunk);
              pos += toSend;
              left -= toSend;
          }
        self.clearDataPointer("Data/toPhone");
        return true;
      })
  .gattCharacteristicEnd()

thank you very much

Is it possible to set Advertised service UUID?

Hello. I am really happy to discover this marvelous ble tool!

Currently, I want to set an Advertised service UUID for my specified ble application before connection for a central scanning device. While I understand I can set Advertised service UUID via using "sudo hcitool -i hci0 cmd 0x08 0x0008" command, but whenever I set the gobbledegook server up, Advertising packet gets initialized.

Is it a way to set it with gobbledegook?

Advertising Interval

nRF Connect is showing my ggk server as having a 1281ms adv. interval. Is there some way of increasing the speed of the advertising interval? Most other devices are around 40ms.

Looks like there is a hcitool command that may be applicable.

Why limit the length of shortname to 10?

Thanks for your work, I'm using your program to create my service, so far it works fine, but I can't customize my BT name, my BT name is "Smart Disk 7778", and then I found that the name was intercepted by the program , becomes "Smart Disk".

standalone.cpp & Server.cpp need to separated from core library.

Seems in order to create a new Server with custom services and characteristics, I need to modify Server.cpp and build all the GPL code into my project, which I'd rather not do.

Seems like there needs to be a libggk.a built without Server.cpp included so I can include my own and link against the GPL library to correctly observe the GPL license.

Or a way to instantiate my own Server class and pass it into ggkStart() and have it call my class instead of the built in one.

Or perhaps relicense the library as BSD.

Thanks.

ATT error: 0x0e (unlikely) after write

When I try to write the "text/string" characteristic in the unmodified standalone.cpp / Server.cpp I get the error 0x0e (unlikely). When using gatt-python this error shows as 'Operation failed with ATT error: 0x0e' and with nRF Connect I get Write operation failed: BLE_GATT_STATUS_ATTERR_UNLIKELY_ERROR (0x010E).

I have tested with bluez 5.48 and 5.45 on Ubuntu 18.04 and I don't see useful debug information in the logs, the error is not mentioned neither by bluetoothd nor gobbledegook. btmon shows me the error is sent:

Bluetooth monitor ver 5.48
= Note: Linux version 4.15.0-42-generic (x86_64)                                             0.728855
= Note: Bluetooth subsystem version 2.22                                                     0.728857
= New Index: BA:27:EB:BB:B3:40 (Primary,USB,hci0)                                     [hci0] 0.728857
= Open Index: BA:27:EB:BB:B3:40                                                       [hci0] 0.728858
= Index Info: BA:27:EB:BB:B3:40 (Cambridge Silicon Radio)                             [hci0] 0.728858
@ MGMT Open: standalone (privileged) version 1.14                                   {0x0003} 0.728859
@ MGMT Open: bluetoothd (privileged) version 1.14                                   {0x0002} 0.728859
@ MGMT Open: bluetoothd (privileged) version 1.14                                   {0x0001} 0.728859
@ MGMT Open: btmon (privileged) version 1.14                                        {0x0004} 0.728939
> ACL Data RX: Handle 68 flags 0x02 dlen 12                                        #1 [hci0] 9.186933
      ATT: Write Request (0x12) len 7
        Handle: 0x0037
          Data: 6262626262
< ACL Data TX: Handle 68 flags 0x00 dlen 9                                        #2 [hci0] 14.192821
      ATT: Error Response (0x01) len 4
        Write Request (0x12)
        Handle: 0x0037
        Error: Unlikely Error (0x0e)
> HCI Event: Number of Completed Packets (0x13) plen 5                            #3 [hci0] 14.272507
        Num handles: 1
        Handle: 68
        Count: 1

I suppose it may be related to the write response mentioned in #6, as, when I run the example-gatt-server from bluez my test application receives a write response and all is OK.

Also when I change the characteristic to "write-without-response" it works OK - but without write responses...
.gattCharacteristicBegin("string", "00000002-1E3C-FAD4-74E2-97A033F1BFAA", {"read", "write-without-response", "notify"})

Gobbledegook and TI Sensor Tag

Hello:

Could I use your library to communicate with a TI Sensor Tag?

I currently use the tinyb library to talk to the TI Sensor Tag. However issues have popped up
and I am not getting any support. So I am looking or alternatives.

Tinyb uses dbus to talk to bluetoothd.

thank you,
west suhanic

Using Cmake with gobbledegook

Just in case if anyone is looking to use cmake with this library I was able to compile with the following file

cmake_minimum_required(VERSION 3.5)

project(GATTCHECK LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED)
find_package(realsense2 REQUIRED)

pkg_search_module(GLib REQUIRED glib-2.0)
pkg_search_module(GTK REQUIRED gtk+-3.0)
pkg_search_module(GIO REQUIRED)

include_directories(${GLib_INCLUDE_DIRS}
                    ${GTK_INCLUDE_DIRS}
                    ${GIO_INCLUDE_DIRS})

link_directories(${GLib_LIBRARY_DIRS}
                 ${GTK_LIBRARY_DIRS}
                  ${GIO_LIBRARY_DIRS})

add_definitions(${GTK_CFLAGS_OTHER})

add_executable(GATTCHECK standalone.cpp)
target_link_libraries(GATTCHECK
                      ${GATTCHECK_SOURCE_DIR}/libggk.a
                      ${GLib_LIBRARIES}
                      ${GKT_LIBRARIES}
                      ${GIO_LIBRARIES}
                      ${realsense2_LIBRARY}
                      pthread
                     )

You need to put libggk.a and Gobbledegook.h in the source directory.

Glib Lib

Anyone knows how to update the Glib library to a version higher than 2.44 on Linux?

Set Local Name Command

Sorry, I want to ask you question, because I set bluetooth name 'gobbledegook'。why this problem.
please tell me ,thank you。
WARNING: + Timed out waiting on command code 0x000F (Set Local Name Command)
!!ERROR: GGK server initialization timed out
STATUS: ** SERVER HEALTH CHANGED: Ok -> Failed initialization
STATUS: ** SERVER RUN STATE CHANGED: Initializing -> Stopping
-Trace-: HciAdapter waiting for thread termination

Example Server cannot connect to iOS device

Hello,

I'm trying to connect some hardware to a raspberry pi 3 and connect the pi via bluetooth to an iPad (6th generation) app.
So far I'm using the example code without any modification, I can compile and run the server. On my Android phone I can see and connect to the server using the nRF Connect App.
On the iPad, however, the gobbledegook server never appears, I used BLE Scanner and my own Swift software and I can find all kinds of devices but not the gobbledegook server. It never gets even listed.
It seems that apple has some stricter rules on what Bluetooth devices they accept. I found the following link which explains some of these rules:
https://community.cypress.com/docs/DOC-14906
https://developer.apple.com/accessories/Accessory-Design-Guidelines.pdf
Unfortunately I don't know much about Bluetooth. Does anyone know what timings or services have to change in order for iOS to accept the BT server?

I guess it would be nice in general if the example code would also connect to iOS

Thank you for your help!

Philipp

How to send characteristic values longer than the MTU

Hi,

I tried to create a 700-character long string and read it from a Xiaomi Android phone, but I only get 512 digits. I assume this is the agreed MTU size between the Android phone and my Ubuntu laptop running the gobbledegook standalone server.

Does the library support sending longer than the MTU size if it is indeed the cause of the trimmed value I received?

Please help Server is not sending write response

I have noticed that the server does not send a client a write response.

I have tried sending a "read characteristic command" and I receive a "read response".

But when I send a write command the server does not send a write response back to the client.

Notifications not stable?

Hello Nettlep nice software! I was able to make a gatt server in 10 minutes!
But sometimes the notification part does not work.
I downloaded the last version 6feca1d I can't see any message related to the notiication on the terminal when on the phone (using nRF connect application) I subscribe to the battery notification.
On the phone the value battery characteristic doesn't change until I press read .
The problem happens about one time every 5 ,then I kill the software with ctrl+C and, I run sudo ./src/standalone -d again and sometimes it does work.
Tested on debian9 x64 with bluetooth usb Lm506 with bluez5.46 running: bluez-5.46/src/./bluetoothd --experimental -n -d
On bluez I always see (also when there is the problem) :
src/gatt-database.c:ccc_write_cb() External CCC write received with value: 0x0001
and when I disable the notification I see:
src/gatt-database.c:ccc_write_cb() External CCC write received with value: 0x0000

Is there a callback on connect and disconnect?

I need to take some actions in my program when a device connects or disconnects to GGK. Is there a lambda / callback / whatever, to be notified when a device connects or disconnects?

Nordic UART GATT Service for GGK

My little contribution to this amazing project. Implemented the Nordic UART GATT Server Service (to be inserted in the Server.cpp) in the frame of GGK. Tested on RPi3A+, Stretch, Bluez 5.50 and using the Uart Service on the Adafruit Bluefruit APP for IOS.

`

// Nordic UART Service (6e400001-b5a3-f393-e0a9-e50e24dcca9e)
.gattServiceBegin("Nordic_UART_Service", "6e400001-b5a3-f393-e0a9-e50e24dcca9e")

	// Characteristic: String value (custom: 6e400002-b5a3-f393-e0a9-e50e24dcca9e)
	.gattCharacteristicBegin("UART_RX", "6e400002-b5a3-f393-e0a9-e50e24dcca9e", {"write-without-response","write"})

		// 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("uart_rx", 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 use that parameter to call our own onUpdatedValue method
			self.callOnUpdatedValue(pConnection, pUserData);

			// Note: Even though the WriteValue method returns void, it's important to return like this, so that a
			// dbus "method_return" is sent, otherwise the client gets an error (ATT error code 0x0e"unlikely").
			// Only "write-without-response" works without this
			self.methodReturnVariant(pInvocation, NULL);
		})

		// 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 *>("uart_rx", "");
			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 = "Nordic UART RX";
				self.methodReturnValue(pInvocation, pDescription, true);
			})

		.gattDescriptorEnd()

	.gattCharacteristicEnd()

	// Characteristic: String value (custom: 6e400003-b5a3-f393-e0a9-e50e24dcca9e)
	.gattCharacteristicBegin("UART_TX", "6e400003-b5a3-f393-e0a9-e50e24dcca9e", {"notify"})

		// Standard characteristic "notify" method call
		.onUpdatedValue(CHARACTERISTIC_UPDATED_VALUE_CALLBACK_LAMBDA
		{
			const char *pTextString = self.getDataPointer<const char *>("uart_tx", "");
			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 = "Nordic UART TX";
				self.methodReturnValue(pInvocation, pDescription, true);
			})

		.gattDescriptorEnd()

	.gattCharacteristicEnd()		
.gattServiceEnd()
`

Transmit data from the server by:

ggkNofifyUpdatedCharacteristic("/com/gobbledegook/Nordic_UART_Service/UART_TX");

Not shown the application specific handlers in the dataGetter for the 'uart_tx' and in the dataSetter for 'uart_rx', but those familiar with the GGK will understand the missing parts...

Advice for anyone using ggk in an autostart context

This is a wonderful library and works brilliantly, but I have come across one problem and I am describing a workaround here in case any other users have a similar problem. I created a BLE peripheral using a raspberry pi using the ggk library. When started from the command line it works, but it hangs when started automatically at boot using a systemd service. I added the normal dependencies (after Bluetooth.service, wantedby Bluetooth.target etc) then tried using a crontab job. Both had the same problem; After calling ggkStart() it returns an error and then the thread hangs and does not return.

I have the same error trying to run the "standalone" example from boot- (in order to remove any problems that might have been with my own implementation of ggk). Error logs show:

Feb 24 17:47:30 raspberrypi3b3 standalone[360]: !!ERROR: Failed to get an ObjectManager client: Error calling StartServiceByName for org.bluez: Failed to activate service 'org.bluez': timed out (service_start_timeout=25000ms)
Feb 24 17:47:30 raspberrypi3b3 standalone[360]: WARNING: + Will retry the failed operation in about 2 seconds
Feb 24 17:47:31 raspberrypi3b3 standalone[360]: DEBUG: Ticking retry timer
Feb 24 17:47:32 raspberrypi3b3 standalone[360]: DEBUG: Ticking retry timer
Feb 24 17:47:32 raspberrypi3b3 standalone[360]: DEBUG: Getting BlueZ ObjectManager
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: !!ERROR: GGK server initialization timed out
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: STATUS: ** SERVER HEALTH CHANGED: Ok -> Failed initialization
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: STATUS: ** SERVER RUN STATE CHANGED: Initializing -> Stopping
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: -Trace-: HciAdapter waiting for thread termination
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: -Trace-: > Event thread is not joinable
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: STATUS: ** SERVER RUN STATE CHANGED: Stopping -> Stopped
Feb 24 17:47:35 raspberrypi3b3 standalone[360]: INFO: GGK server stopped

Unfortunately this error appears to create a hanging condition. I don't understand why. The ggkStart method returns an error code, and my code executes a few more lines and then the main thread hangs. After endless head scratching and attempts to get this working, I eventually just put a delay in the contact of five seconds in the shell program that invokes it. Now it works. This is just a heads up for anyone attempting to do the same thing. Bluez appears to require some services which have not yet even started after user prompts are available on the command line. More seriously there appears to be a problem with ggk catching this error properly. I would attempt to debug the ggk code myself to fix this, but it is beyond my knowledge at the moment. If you can live with the workaround - it works well !

Adding Manufacturer specific data into Advertising data

Hi,
I am kind of new to C++ and Bluetooth. I have just started using your server. I have successfully implemented a basic NUS based chat application by modifying your server code and standalone app code. Thanks for all your effort.

But I need to add manufacturer specific data into the advertising data. I could not find any easy way until now to do this. With my limited understanding, I realized that I have to change things much deep into your codes (e.g. using add Advertising command of management-api instead of set advertising command). But the problem is that I am afraid of chaning things without having a complete grasp (which I don't have at the moment).

p.s. I found a python based example where they provide a seperate method just to set this data. But I need a C++ based solution.
Could you please help? Thanks in advance.

Not an issue

Hi,

Nice work! Been tired of using Bluez's py code so this is refreshing. Just to let you know in the 'Tested on Ubuntu 16.04 on x86 and Raspberry Pi' you could add 'Freescale imx7'

Cross complies nicely and can see all the services and characteristics using iOS BLE scanner.

Cheers.

Application as Maintainer

Hi,

while i was searching the net for a C++ library for BLE, i stumbled across your nice project here.
Another discovery was: https://github.com/weareaudiofile/bluez-dbus-cpp

Since the other lib seems to be unmaintained as well, i created a fork and integrated my changes.
I believe, it might be a good idea to merge these two C++ BLE projects.

What do you think?

Sending raw bytes instead of null terminated string.

Hi,
I am able to send null-terminated strings successfully. But when I try to send raw bytes (array of char) where there could be values equal to null in the middle of the data, I get only until the null byte and rest are not sent. I am using a dataSetter method similar to that of the example standalone code.

How can I change this behaviour? Thanks in advance.

Can i cross-compile gobbledegook for ARM

i want to cross-compile gobbledegook for ARM because i use AM3358 Processer, and it too slow to compile gobbledegook.
So can you tell me, how can i cross-compile gobbledegook for ARM, thank you very much.

'Error 14 (0xe): GATT ERR UNLIKELY' after every notification

Hello Paul,

First of all, thank you very much for this great piece of code. 👍

I hope you can help me to clear up what is going here.

I've downloaded your latest version on a CentOS 7 Linux running on a Raspberry Pi v3 B, using Bluez version 5.49. The compilation went fine and I'm able to run ./standalone -d without any problems.

Then I'm using the Android App called nRF Connect to be able to establish a BLE connection with the standalone program. This also when smooth and well.

I've expanded the Unknown Characteristic where you can send a text and it will go back to you as a notification. I wrote and sent just a test string and it indeed went back to me as a notification, but I see the error Error 14 (0xe): GATT ERR UNLIKELY in the log of the session. I used the nRF Logger Android App to obtain the complete transcription log:

(please scroll down near to the end to see the error)

nRF Connect, 2018-10-18
Gobbledego (B8:27:EB:37:2C:0D)
I	11:24:15.029	[Server] Server started
V	11:24:15.065	Heart Rate (0x180D)
- Heart Rate Measurement [N] (0x2A37)
   Client Characteristic Configuration (0x2902)
- Body Sensor Location [R] (0x2A38)
- Heart Rate Control Point [W] (0x2A39)
Unknown Service (0000aaa0-0000-1000-8000-aabbccddeeff)
- Unknown Characteristic [N R] (0000aaa1-0000-1000-8000-aabbccddeeff)
   Client Characteristic Configuration (0x2902)
   Unknown Descriptor (0000aab0-0000-1000-8000-aabbccddeeff)
   Characteristic User Description (0x2901)
   Characteristic Presentation Format (0x2904)
- Unknown Characteristic [I W WNR] (0000aaa2-0000-1000-8000-aabbccddeeff)
   Client Characteristic Configuration (0x2902)
User Data (0x181C)
- First Name [R W] (0x2A8A)
- Last Name [R W] (0x2A90)
- Gender [R W] (0x2A8C)
V	11:24:15.452	Connecting to B8:27:EB:37:2C:0D...
D	11:24:15.454	gatt = device.connectGatt(autoConnect = false, TRANSPORT_LE, preferred PHY = LE 1M)
D	11:24:16.581	[Server callback] Connection state changed with status: 0 and new state: CONNECTED (2)
I	11:24:16.581	[Server] Device with address B8:27:EB:37:2C:0D connected
D	11:24:16.595	[Callback] Connection state changed with status: 0 and new state: CONNECTED (2)
I	11:24:16.595	Connected to B8:27:EB:37:2C:0D
D	11:24:16.653	[Broadcast] Action received: android.bluetooth.device.action.ACL_CONNECTED
V	11:24:16.653	Discovering services...
D	11:24:16.653	gatt.discoverServices()
I	11:24:17.017	[Server] MTU changed to 517
I	11:24:17.416	Connection parameters updated (interval: 7.5ms, latency: 0, timeout: 5000ms)
D	11:24:17.911	[Callback] Services discovered with status: 0
I	11:24:17.911	Services discovered
V	11:24:17.951	Generic Access (0x1800)
- Device Name [R] (0x2A00)
- Appearance [R] (0x2A01)
Generic Attribute (0x1801)
- Service Changed [I] (0x2A05)
   Client Characteristic Configuration (0x2902)
Device Information (0x180A)
- Manufacturer Name String [R] (0x2A29)
- Model Number String [R] (0x2A24)
Battery Service (0x180F)
- Battery Level [N R] (0x2A19)
   Client Characteristic Configuration (0x2902)
Current Time Service (0x1805)
- Current Time [N R] (0x2A2B)
   Client Characteristic Configuration (0x2902)
- Local Time Information [R] (0x2A0F)
Unknown Service (00000001-1e3c-fad4-74e2-97a033f1bfaa)
- Unknown Characteristic [N R W] (00000002-1e3c-fad4-74e2-97a033f1bfaa)
   Client Characteristic Configuration (0x2902)
   Characteristic User Description (0x2901)
Unknown Service (00000001-1e3d-fad4-74e2-97a033f1bfee)
- Unknown Characteristic [R] (00000002-1e3d-fad4-74e2-97a033f1bfee)
   Characteristic User Description (0x2901)
Unknown Service (0000b001-1e3d-fad4-74e2-97a033f1bfee)
- Unknown Characteristic [R] (0000b002-1e3d-fad4-74e2-97a033f1bfee)
   Characteristic User Description (0x2901)
- Unknown Characteristic [R] (0000b003-1e3d-fad4-74e2-97a033f1bfee)
   Characteristic User Description (0x2901)
D	11:24:17.952	gatt.setCharacteristicNotification(00002a05-0000-1000-8000-00805f9b34fb, true)
D	11:24:17.956	gatt.setCharacteristicNotification(00002a19-0000-1000-8000-00805f9b34fb, true)
D	11:24:17.959	gatt.setCharacteristicNotification(00002a2b-0000-1000-8000-00805f9b34fb, true)
D	11:24:17.962	gatt.setCharacteristicNotification(00000002-1e3c-fad4-74e2-97a033f1bfaa, true)
I	11:24:17.995	Connection parameters updated (interval: 48.75ms, latency: 0, timeout: 5000ms)
V	11:24:31.105	Enabling notifications for 00000002-1e3c-fad4-74e2-97a033f1bfaa
D	11:24:31.105	gatt.setCharacteristicNotification(00000002-1e3c-fad4-74e2-97a033f1bfaa, true)
D	11:24:31.107	gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x0100)
I	11:24:31.207	Data written to descr. 00002902-0000-1000-8000-00805f9b34fb, value: (0x) 01-00
A	11:24:31.207	"Notifications enabled" sent
V	11:24:31.224	Notifications enabled for 00000002-1e3c-fad4-74e2-97a033f1bfaa
V	11:24:33.845	Disabling notifications for 00000002-1e3c-fad4-74e2-97a033f1bfaa
D	11:24:33.845	gatt.setCharacteristicNotification(00000002-1e3c-fad4-74e2-97a033f1bfaa, false)
D	11:24:33.847	gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x0000)
I	11:24:33.935	Data written to descr. 00002902-0000-1000-8000-00805f9b34fb, value: (0x) 00-00
A	11:24:33.935	"Notifications and indications disabled" sent
V	11:24:33.953	Notifications and indications disabled for 00000002-1e3c-fad4-74e2-97a033f1bfaa
V	11:24:49.761	Reading characteristic 00000002-1e3c-fad4-74e2-97a033f1bfaa
D	11:24:49.761	gatt.readCharacteristic(00000002-1e3c-fad4-74e2-97a033f1bfaa)
I	11:24:49.831	Read Response received from 00000002-1e3c-fad4-74e2-97a033f1bfaa, value: (0x) 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64-21, "Hello, world!"
A	11:24:49.831	"(0x) 48-65-6C-6C-6F-2C-20-77-6F-72-6C-64-21, "Hello, world!"" received
V	11:24:55.030	Enabling notifications for 00000002-1e3c-fad4-74e2-97a033f1bfaa
D	11:24:55.030	gatt.setCharacteristicNotification(00000002-1e3c-fad4-74e2-97a033f1bfaa, true)
D	11:24:55.032	gatt.writeDescriptor(00002902-0000-1000-8000-00805f9b34fb, value=0x0100)
I	11:24:55.092	Data written to descr. 00002902-0000-1000-8000-00805f9b34fb, value: (0x) 01-00
A	11:24:55.092	"Notifications enabled" sent
V	11:24:55.112	Notifications enabled for 00000002-1e3c-fad4-74e2-97a033f1bfaa
V	11:25:10.813	Writing request to characteristic 00000002-1e3c-fad4-74e2-97a033f1bfaa
D	11:25:10.814	gatt.writeCharacteristic(00000002-1e3c-fad4-74e2-97a033f1bfaa, value=0x6A75737420612074657374)
I	11:25:10.891	Notification received from 00000002-1e3c-fad4-74e2-97a033f1bfaa, value: (0x) 6A-75-73-74-20-61-20-74-65-73-74, "just a test"
A	11:25:10.891	"(0x) 6A-75-73-74-20-61-20-74-65-73-74, "just a test"" received
E	11:25:15.861	Error 14 (0xe): GATT ERR UNLIKELY
V	11:25:22.535	[Server] Cancelling server connection...
D	11:25:22.535	server.cancelConnection(device)
V	11:25:22.562	Disconnecting...
D	11:25:22.562	gatt.disconnect()
D	11:25:22.565	[Callback] Connection state changed with status: 0 and new state: DISCONNECTED (0)
I	11:25:22.592	Disconnected
D	11:25:22.610	gatt.close()
D	11:25:22.651	wait(200)

Please, let me know if I missed something important during the compilation process.
Thanks.

Dependency for building on Raspberry Pi Stretch

The documentation on this is great! But, you might want to consider having a section on dependencies. I needed to install automake and libbluetooth-dev before it would build on Raspbian Stretch. The ./configure script noticed that I didn't have automake, but I didn't realize that I needed libbluetooth-dev until it started the compile.

Also, you mention that you build it on 5.45. It seems to work fine on the default install of 5.43 that comes with Raspbian Stretch. (with --expiremental enabled) Is there anything that I should be aware of with that older version?

Library compatible to BlueZ 5.37?

Hi,
first of all: Looks like a great library.

I programmed a BLE Library myself for a project but only the central device part is working.
Now I wanted to build a NUS (Nordic UART Service) example (basically BLE-Chat) with a peripheral device using your library and mine for the central device.
It would be really nice to see such an example.
My was implemented against BlueZ 5.37 (with experimental flag, but 5.37 was the preinstalled version) and now it is not clear if the goobledegook library will work with 5.37.

So my fist question is now: Does your library only work with BlueZ 5.45 or is the D-Bus API version 5.45 different to the version 5.37?

g_auto() undefined

Hi,

I've just forked and am trying to build the lib and app.
I am on a Pi, using:
pi@pi ~/projects/gobbledegook $ g++ --version
g++ (Raspbian 4.9.2-10) 4.9.2
Copyright (C) 2014 Free Software Foundation, Inc.
This is free software; see the source for copying conditions. There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I am getting a syntax error concerning g_auto():

Utils.cpp: In static member function ‘static GVariant* ggk::Utils::gvariantFromStringArray(const char*, va_list)’:
Utils.cpp:245:24: error: expected primary-expression before ‘)’ token
g_auto(GVariantBuilder) builder;
Utils.cpp:245:24: error: ‘g_auto’ was not declared in this scope

Since I am completely new to all this, and by sheer brut force, it passes if I remove all references to g_auto and simply use:

GVariantBuilder builder;

I wanted your guidance please, in this regards. I am sure it's a problem on my side, and would like your help in resolving it, if possible.

If it's an oversight (which I doubt) and we need to get rid of g_auto(), then I'll open a PR.

Thanks!
Itamar

unable to make gkk on RPi w. Stretch and bluez 5.50

I have all the latest (as pr 15-nov-2018) dis-upgrades and updates on an RPi3 w. Stretch and have installed Bluez-5.50. The make of gkk gives the output shown below.
Any help would be appreciated.

make all-recursive
make[1]: Entering directory '/home/pi/gobbledegook'
Making all in src
make[2]: Entering directory '/home/pi/gobbledegook/src'
g++ -fPIC -Wall -Wextra -std=c++11 -g -O2 -o standalone standalone-standalone.o libggk.a -lgobject-2.0 -lgio-2.0 -lglib-2.0 -lpthread
libggk.a(libggk_a-DBusObject.o): In function ggk::DBusObject::generateIntrospectionXML(int) const': /home/pi/gobbledegook/src/DBusObject.cpp:258: undefined reference to ggk::Logger::debug(std::string const&)'
libggk.a(libggk_a-DBusObject.o): In function construct<ggk::GattService, ggk::DBusObject&, char const (&)[23]>': /usr/include/c++/4.9/ext/new_allocator.h:120: undefined reference to ggk::GattService::GattService(ggk::DBusObject&, std::string const&)'
libggk.a(libggk_a-DBusObject.o): In function ggk::GattService& ggk::GattInterface::addProperty<ggk::GattService>(std::string const&, ggk::GattUuid const&, _GVariant* (*)(_GDBusConnection*, char const*, char const*, char const*, char const*, _GError**, void*), int (*)(_GDBusConnection*, char const*, char const*, char const*, char const*, _GVariant*, _GError**, void*))': /home/pi/gobbledegook/src/GattInterface.h:100: undefined reference to ggk::GattProperty::GattProperty(std::string const&, _GVariant*, _GVariant* ()(_GDBusConnection, char const*, char const*, char const*, char const*, _GError**, void*), int ()(_GDBusConnection, char const*, char const*, char const*, char const*, _GVariant*, _GError**, void*))'
libggk.a(libggk_a-DBusObject.o): In function ggk::GattService& ggk::GattInterface::addProperty<ggk::GattService>(std::string const&, bool, _GVariant* (*)(_GDBusConnection*, char const*, char const*, char const*, char const*, _GError**, void*), int (*)(_GDBusConnection*, char const*, char const*, char const*, char const*, _GVariant*, _GError**, void*))': /home/pi/gobbledegook/src/GattInterface.h:142: undefined reference to ggk::GattProperty::GattProperty(std::string const&, _GVariant*, _GVariant* ()(_GDBusConnection, char const*, char const*, char const*, char const*, _GError**, void*), int ()(_GDBusConnection, char const*, char const*, char const*, char const*, _GVariant*, _GError**, void*))'
libggk.a(libggk_a-DBusObject.o):(.data.rel.ro._ZTVN3ggk11GattServiceE[_ZTVN3ggk11GattServiceE]+0x1c): undefined reference to ggk::GattInterface::generateIntrospectionXML(int) const' libggk.a(libggk_a-Init.o): In function ggk::registerObjects()':
/home/pi/gobbledegook/src/Init.cpp:642: undefined reference to ggk::DBusObject::generateIntrospectionXML[abi:cxx11](int) const' libggk.a(libggk_a-Server.o): In function ggk::Server::findInterface(ggk::DBusObjectPath const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&) const':
/home/pi/gobbledegook/src/Server.cpp:607: undefined reference to ggk::DBusObject::findInterface(ggk::DBusObjectPath const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ggk::DBusObjectPath const&) const' libggk.a(libggk_a-Server.o): In function ggk::Server::callMethod(ggk::DBusObjectPath const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GDBusConnection*, _GVariant*, _GDBusMethodInvocation*, void*) const':
/home/pi/gobbledegook/src/Server.cpp:624: undefined reference to ggk::DBusObject::callMethod(ggk::DBusObjectPath const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GDBusConnection*, _GVariant*, _GDBusMethodInvocation*, void*, ggk::DBusObjectPath const&) const' libggk.a(libggk_a-Server.o): In function ggk::Server::Server(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, void const* ()(char const), int ()(char const, void const*))':
/home/pi/gobbledegook/src/Server.cpp:263: undefined reference to ggk::DBusObject::gattServiceBegin(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ggk::GattUuid const&)' /home/pi/gobbledegook/src/Server.cpp:303: undefined reference to ggk::DBusObject::gattServiceBegin(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, ggk::GattUuid const&)'
/home/pi/gobbledegook/src/Server.cpp:344: undefined reference to ggk::DBusObject::gattServiceBegin(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ggk::GattUuid const&)' /home/pi/gobbledegook/src/Server.cpp:385: undefined reference to ggk::DBusObject::gattServiceBegin(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, ggk::GattUuid const&)'
/home/pi/gobbledegook/src/Server.cpp:444: undefined reference to ggk::DBusObject::gattServiceBegin(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, ggk::GattUuid const&)' libggk.a(libggk_a-Server.o):/home/pi/gobbledegook/src/Server.cpp:483: more undefined references to ggk::DBusObject::gattServiceBegin(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, ggk::GattUuid const&)' follow
libggk.a(libggk_a-Server.o): In function void __gnu_cxx::new_allocator<ggk::DBusInterface>::construct<ggk::DBusInterface, ggk::DBusObject&, char const (&) [35]>(ggk::DBusInterface*, ggk::DBusObject&, char const (&) [35])': /usr/include/c++/6/ext/new_allocator.h:120: undefined reference to ggk::DBusInterface::DBusInterface(ggk::DBusObject&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)'
libggk.a(libggk_a-Server.o): In function ggk::Server::Server(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, void const* (*)(char const*), int (*)(char const*, void const*))': /home/pi/gobbledegook/src/Server.cpp:593: undefined reference to ggk::DBusInterface::addMethod(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, char const**, char const*, void ()(ggk::DBusInterface const&, _GDBusConnection, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GVariant*, _GDBusMethodInvocation*, void*))'
libggk.a(libggk_a-ServerUtils.o): In function ggk::addManagedObjectsNode(ggk::DBusObject const&, ggk::DBusObjectPath const&, _GVariantBuilder*)': /home/pi/gobbledegook/src/ServerUtils.cpp:76: undefined reference to ggk::DBusObject::getInterfacesabi:cxx11 const'
/home/pi/gobbledegook/src/ServerUtils.cpp:82: undefined reference to ggk::DBusObject::getInterfaces[abi:cxx11]() const' /home/pi/gobbledegook/src/ServerUtils.cpp:90: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
/home/pi/gobbledegook/src/ServerUtils.cpp:109: undefined reference to ggk::DBusInterface::getName[abi:cxx11]() const' /home/pi/gobbledegook/src/ServerUtils.cpp:186: undefined reference to ggk::DBusObject::getChildrenabi:cxx11 const'
/home/pi/gobbledegook/src/ServerUtils.cpp:118: undefined reference to ggk::DBusInterface::getName[abi:cxx11]() const' /home/pi/gobbledegook/src/ServerUtils.cpp:137: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
/home/pi/gobbledegook/src/ServerUtils.cpp:146: undefined reference to ggk::DBusInterface::getName[abi:cxx11]() const' /home/pi/gobbledegook/src/ServerUtils.cpp:165: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
libggk.a(libggk_a-GattCharacteristic.o): In function ggk::GattCharacteristic::onReadValue(void (*)(ggk::GattCharacteristic const&, _GDBusConnection*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GVariant*, _GDBusMethodInvocation*, void*))': /home/pi/gobbledegook/src/GattCharacteristic.cpp:115: undefined reference to ggk::DBusInterface::addMethod(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, char const**, char const*, void ()(ggk::DBusInterface const&, _GDBusConnection, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GVariant*, _GDBusMethodInvocation*, void*))'
libggk.a(libggk_a-GattCharacteristic.o): In function ggk::GattCharacteristic::onWriteValue(void (*)(ggk::GattCharacteristic const&, _GDBusConnection*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GVariant*, _GDBusMethodInvocation*, void*))': /home/pi/gobbledegook/src/GattCharacteristic.cpp:131: undefined reference to ggk::DBusInterface::addMethod(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, char const**, char const*, void ()(ggk::DBusInterface const&, _GDBusConnection, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GVariant*, _GDBusMethodInvocation*, void*))'
libggk.a(libggk_a-GattCharacteristic.o): In function ggk::GattCharacteristic::sendChangeNotificationVariant(_GDBusConnection*, _GVariant*) const': /home/pi/gobbledegook/src/GattCharacteristic.cpp:221: undefined reference to ggk::DBusObject::emitSignal(_GDBusConnection*, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GVariant*)'
libggk.a(libggk_a-GattCharacteristic.o): In function ggk::GattCharacteristic::callMethod(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GDBusConnection*, _GVariant*, _GDBusMethodInvocation*, void*) const': /home/pi/gobbledegook/src/GattCharacteristic.cpp:74: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
libggk.a(libggk_a-GattDescriptor.o): In function ggk::GattDescriptor::onReadValue(void (*)(ggk::GattDescriptor const&, _GDBusConnection*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GVariant*, _GDBusMethodInvocation*, void*))': /home/pi/gobbledegook/src/GattDescriptor.cpp:116: undefined reference to ggk::DBusInterface::addMethod(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, char const**, char const*, void ()(ggk::DBusInterface const&, _GDBusConnection, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GVariant*, _GDBusMethodInvocation*, void*))'
libggk.a(libggk_a-GattDescriptor.o): In function ggk::GattDescriptor::onWriteValue(void (*)(ggk::GattDescriptor const&, _GDBusConnection*, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GVariant*, _GDBusMethodInvocation*, void*))': /home/pi/gobbledegook/src/GattDescriptor.cpp:132: undefined reference to ggk::DBusInterface::addMethod(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, char const**, char const*, void ()(ggk::DBusInterface const&, _GDBusConnection, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GVariant*, _GDBusMethodInvocation*, void*))'
libggk.a(libggk_a-GattDescriptor.o): In function ggk::GattDescriptor::callMethod(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, _GDBusConnection*, _GVariant*, _GDBusMethodInvocation*, void*) const': /home/pi/gobbledegook/src/GattDescriptor.cpp:75: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
libggk.a(libggk_a-GattInterface.o): In function ggk::GattInterface::GattInterface(ggk::DBusObject&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)': /home/pi/gobbledegook/src/GattInterface.cpp:45: undefined reference to ggk::DBusInterface::DBusInterface(ggk::DBusObject&, std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&)'
libggk.a(libggk_a-GattInterface.o): In function ggk::GattInterface::generateIntrospectionXML[abi:cxx11](int) const': /home/pi/gobbledegook/src/GattInterface.cpp:107: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
/home/pi/gobbledegook/src/GattInterface.cpp:112: undefined reference to ggk::DBusMethod::generateIntrospectionXML[abi:cxx11](int) const' /home/pi/gobbledegook/src/GattInterface.cpp:103: undefined reference to ggk::DBusInterface::getNameabi:cxx11 const'
libggk.a(libggk_a-GattInterface.o):(.data.rel.ro+0x20): undefined reference to `ggk::DBusInterface::callMethod(std::__cxx11::basic_string<char, std::char_traits, std::allocator > const&, _GDBusConnection*, _GVariant*, _GDBusMethodInvocation*, void*) const'
collect2: error: ld returned 1 exit status
Makefile:394: recipe for target 'standalone' failed
make[2]: *** [standalone] Error 1
make[2]: Leaving directory '/home/pi/gobbledegook/src'
Makefile:368: recipe for target 'all-recursive' failed
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory '/home/pi/gobbledegook'
Makefile:309: recipe for target 'all' failed
make: *** [all] Error 2

Send "ggkNofifyUpdatedCharacteristic" while still operating on a different Characteristic.

How can I make the ff. perform in the correct order within characteristicA's dataSetter?

  1. Write on characteristicA
  2. Notify characteristicB w/ new value "1"
  3. Perform some internal logic in standalone
  4. Notify characteristicB w/ new value "0"

Currently I get the ff. order for the values I receive on characteristicB:

  1. Write on characteristicA
  2. Perform some internal logic in standalone
  3. Notify characteristicB w/ new value "1"
  4. Notify characteristicB w/ new value "0"

Initial investigation shows that "ggkNofifyUpdatedCharacteristic" adds the notification entries into a queue and only pops the items in the queue after I perform the internal logic and I exit the dataSetter function. Is it possible to make the first scenario work in GGK?

Discovering GATT Services: Error 22 (0x16) terminate local host

I'm currently running the standalone binary without modification (BlueZ 5.48).

nRF Connect is able to discover the device via its scanner. However, the nRF Connect fails during "Discovering services..." which leads to a disconnect. The error message is "Error 22 (0x16): GATT CONN TERMINATE LOCAL HOST".

The standalone server produces no log event during the nRF service discovery failure

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.