Bluetooth Low Energy with C#, BlueZ 5 and Mono

mono   ble   bluetooth   blend-micro   dbus   bluez   gatt  

The major change from Mr. Gibbs 2015 to 2016 is the rewrite of all the bluetooth calls to support BlueZ 5. It sure seems like a lot of work just to be on the latest version number, and indeed, there are some important functionality related reasons. Chief among them are that Bluetooth Low Energy (BLE) didn't really work in BlueZ prior to very recently with version 5.

Having built up a dbus-sharp fork, and defined the BlueZ Interfaces that supported my needs for talking to the pebble, I was hopeful that BLE integration would be greatly simplified.

blend micro

For the purposes of this post, I'm going to outline how I got it working with a a RedBearLabs Blend Micro, but I believe this process should translate pretty easily to any GATT based device. The Adafruit Feather BLE should work with the same code, though I have not tested this. This should go without saying, but you will also need a reasonably modern Bluetooth adapter that supports BLE. The built in Bluetooth on my 2009 macbook does not qualify, but even inexpensive USB adapters should work just fine.

BLE devices communicate using a protocol that is simpler than the myriad of different protocols that traditional Bluetooth devices use. They use a protocol called GATT (Generic Attribute Profile). The basic idea is that a device exposes a hierarchy of services, characteristics, and descriptors, and an interested host can connect and read, write, or "subscribe" to any of these values.

In order to use GATT from BlueZ on most linux distributions you need to start bluetoothd with experimental features enabled. To do this in ubuntu, for example:


sudo stop bluetooth
sudo bluetoothd -E
Once running, use a dbus viewer (I use d-feet) to look at the system bus, under org.bluez.
experimental enabled
Once bluez is back up and running, you should see the GattManager1 and LEAdvertisingManager1 interfaces appear in your adapter path on the bus. To find your devices you can use the normal Bluetooth UI or console tools, or simply navigate to your Bluetooth interface in d-feet and start discovery.
discovery
Once discovery finds your BLE device, you should see paths appear for the device, each service, each characteristic, and each descriptor.
paths
For the Blend Micro the GATT service we're interested in is a "UART" service that simply allows us to read and write bytes to a characteristic. The GATT hierarchy looks like this.
UART Service: 713d0000-503e-4c75-ba94-3148f18d941e
    Vendor Name Characteristic: 713D0001-503E-4C75-BA94-3148F18D941E
    Read Characteristic: 713D0002-503E-4C75-BA94-3148F18D941E
    Write Characteristic: 713D0003-503E-4C75-BA94-3148F18D941E
    Ack Characteristic: 713D0004-503E-4C75-BA94-3148F18D941E
    Version Characteristic: 713D0005-503E-4C75-BA94-3148F18D941E

Note how individual items each have a UUID, there are no collisions, and the characteristics all "descend" from the service.

You can interact with your BLE device straight from d-feet. YMMV, but I found that many properties will not return values unless you first invoke "Connect" on the BLE device. Once connected, however, you should be able to read most properties and read GATT characteristic values.


"Well that's great, but I came here to do it from C#", you say. Fair enough. For the purpose of this post I'm going to assume we know both the local bluetooth interface name, the address of the device we want to connect to, and how to initialize a dbus connection. If you do not know these things I have covered in another post how to initialize the connection and instantiate and ObjectManager.

Once we have our dbus connection, getting a reference to our GATT device and connecting is pretty straightforward using the helper in Mono.BlueZ


var devicePath = BlueZPath.Device("hci1", "F6:58:7F:09:5D:E6");
var device = _connection.System.GetObject(BlueZPath.Service, devicePath);
device.Connect();

Once connected, we need to find the service and characteristic we're interested in, in this case the read characteristic.


var servicePaths = device.GattServices;
//for the purpose of the blend there's only 1, but this isn't a rule
var servicePath = servicePaths.Single();
var service = _connection.System.GetObject(BlueZPath.Service, servicePath);
var charPaths = service.Characteristics;
Characteristic1 readChar;
Properties properties;
foreach (var charPath in charPaths)
{
    var vChar = _connection.System.GetObject(BlueZPath.Service, charPath);
    if (vChar.UUID.ToUpper() == "713D0002-503E-4C75-BA94-3148F18D941E")
    {
        readChar = vChar;
        properties = _connection.System.GetObject(BlueZPath.Service, charPath);
        break;
    }
}

Now that we have our characteristic, we could simply just read the value over and over watching for changes, but this really isn't in keeping with the intent of BLE (which, of course, is to use as little energy as possible). Instead, we can tell the device that we simply want to be notified if the value changes.


readChar.StartNotify();

But where do these notifications go? BlueZ uses another common dbus interface, the Properties interface, to send these notifications. Using .Net events we can simply create event handlers.


properties.PropertiesChanged += new PropertiesChangedHandler(
new Action,string[]>((@interface,changed,invalidated)=>{
    if(changed!=null)
    {
        foreach(var prop in changed.Keys)
        {
            if(changed[prop] is byte[])
            {
                //if the characteristic is here, our value will be at (byte[])changed[prop])
            }
        }
    }
    if(invalidated!=null)
    {
        foreach(var prop in invalidated)
        {
            //if the characterstic is here, it means that it was changed but the value wasn't sent, we need to invoke the property to read it
        }
    }
}));

I have a couple working implementations of this process, the first is the BlendMicroAnemometer plugin for MrGibbs, and the other is the Blend Micro Bootstrapper that I included in Mono.BlueZ


comments powered by Disqus
Comments powered by Disqus