Talking Bluetooth (Low Energy) with Xamarin

With the rising popularity of IoT (Internet of Things), it becomes more common that you need to communicate with hardware. In most cases you can accomplish this with network connectivity, but you might want to consider Bluetooth Low Energy (BLE) as well. As the name suggests, BLE uses a lot less energy in comparison to classic Bluetooth. Less energy consumption means that it’s possible to use smaller (and portable) batteries what might be very useful for IoT devices. When deciding if BLE suites your needs, you should take a few things in consideration:

  • Bandwidth: Bluetooth is less suitable for transmitting large sets of data, especially BLE.
  • Costs: in comparison to network adapters, bluetooth is more affordable.
  • Range: the range of your device really depends on the bluetooth device and version that is used by the hardware and the smartphone/tablet. The environment might also impact the range. At a maximum you can reach 100 meters, but the average will be around 15 meters.
  • Power consumption: you can use BLE to save energy, but this will limit the throughput. If you are transferring small packages this might be interesting. The classic way of bluetooth communication is also less consuming than network connectivity.

Instead of writing the bluetooth code for every platform, you can choose to use a plugin that provides an abstraction layer so it’s possible to access BLE from shared code. At the time of writing, I find Bluetooth LE Plugin for Xamarin the best pick for the job. The plug-in is easy to implement and is continuously getting updates. If you want to use classic bluetooth you might want to look into some other plugins. in this post I will focus on Bluetooth LE Plugin for Xamarin.

When working with BLE, there are 3 (most important) different bluetooth abstraction layers:

  • Services: A service contains one or more characteristics. For example, you could have a service called “Heart Rate service” that includes characteristics such as “heart rate measurement.” Each service has it’s unique pre-defined 16-bit or 128-bit UUID.
  • Characteristics: A characteristic is some kind of endpoint for a specific part of the service. Just like a service, the characteristic also has a UUID. Characteristics support a range of different interactions: read, write, notify, indicate, signedWrite, writableAuxilliaries, broadcast.
  • DescriptorsDescriptors are defined attributes that describe a characteristic value. For example, a descriptor might specify a human-readable description, an acceptable range for a characteristic’s value, or a unit of measure that is specific to a characteristic’s value.

Now, let’s dive into some code samples! To give you a taste of what the plugin has in store for you, some snippets:

Scan for BLE devices (advertisements)
The Adapter class makes it possible to detect devices in the surrounding area. You can simply get an instance of the adapter and start scanning with the following code:

 var adapter = CrossBluetoothLE.Current.Adapter;
adapter.DeviceDiscovered += (s,a) =+ deviceList.Add(a.Device);
await adapter.StartScanningForDevicesAsync(); 

Connect to device
When you’ve found the device you want to connect to, you are able to initiate a connection with the device. After connecting successfully to a device you are able to get the available characteristics and start sending requests or start retrieving notifications from the device. When the bluetooth device doesn’t receive any requests for a specific period (may differ per device), it will disconnect to save battery power. After getting a device instance (with the previous sample), it’s fairly easy to setup a connection:

 try
{
    await _adapter.ConnectToDeviceAsync(device);
}
catch(DeviceConnectionException e)
{
// ... could not connect to device
}

Note: a device scan is only necessary when connecting to the device for the first time. It’s also possible to initiate a connection based on the UUID of the device.

Get services, characteristics, descriptors
As described above, a service is the first abstraction layer. When an instance of a service is resolved, the characteristics of this specific service can be requested. The same goes for a characteristic and his descriptors. On a characteristic you can request the descriptors.

The different abstraction layers can be requested in a similar way, GetAll or GetById:

// Get All services and getting a specific service
var services = await connectedDevice.GetServicesAsync();
var service = await connectedDevice.GetServiceAsync(Guid.Parse("ffe0ecd2-3d16-4f8d-90de-e89e7fc396a5"));

// Get All characteristics and getting a specific characteristic
var characteristics = await service.GetCharacteristicsAsync();
var characteristic = await service.GetCharacteristicAsync(Guid.Parse("37f97614-f7f7-4ae5-9db8-0023fb4215ca"));

// Get All descriptors and getting a specific descriptor
var descriptors = await characteristic.GetDescriptorsAsync();
var descriptor = await characteristic.GetDescriptorAsync(Guid.Parse("6f361a84-eeac-404c-ae48-e65b9cba6af8"));

Send write command
After retrieving an instance of the characteristic you’re able to interact with it. Writing bytes for example:

await characteristic.WriteAsync(bytes);

Send read command
You can also request information from a characteristic:

var bytes = await characteristic.ReadAsync();

Pretty easy, right? To get you started, you can find an example project on the Bluetooth LE Plugin GitHub page. Although this seems pretty simple,  I’ve experienced some difficulties in making it rock solid. Some tips to improve your code:

  • Run bluetooth code on main thread.
  • Don’t scan for devices and send commands simultaneously, also don’t send multiple commands simultaneously. A characteristic can only execute one request at a time.
  • Adjust ScanMode to suit your use case.
  • Don’t store instances of characteristics or services.

Related links:

8 thoughts on “Talking Bluetooth (Low Energy) with Xamarin

  1. Hello, I’m trying to use this plugin too but I find myself a bit confused on some of the things to do to make a simple app. The snippets of code seem to short and the example project is too complicated, do you know something in the middle?

    I need to make an IoT app for iOS and Android and I was able to get the bluetooth status of the device correctly.

    I could also let the OS handle the device discovery and connection but how do I send data to a device? I think characteristics are not the right thing to do it

    Like

    1. Hi Giovanni,

      Providing a sample for this library is a bit more complicated because it depends on what device you are trying to communicate with. In general there are two different ways of communicating with Bluetooth Low Energy:

      – Advertising
      Most BLE devices broadcast messages, these messages are referred to as advertising. You can receive these messages by registering an EventHandler on IAdapter.DeviceAdvertised. When you call IAdapter.StartScanningForDevicesAsync() the app will start receiving updates.

      – Connected
      Because advertising is insecure, and can be monitored by anyone, you can also setup a connection. When connected you can use the BLE services and characteristics. As far as I know there is no other way to communicate through BLE, besides characteristics. This is also described in the GATT specification: https://www.bluetooth.com/specifications/gatt/generic-attributes-overview .
      You can initiate a connection by calling IAdapter.ConnectToDeviceAsync or IAdapter.ConnectToKnownDeviceAsync. Characteristics allow you to write or read data to/from the device.

      Note: because bluetooth is energy efficient, the device might disconnect when the connection isn’t actively used. It’s also important to send commands one by one, you can’t send multiple commands at the same time. The calls to the bluetooth library also have to be done on the main thread.

      I hope this is of any help to you.

      Like

  2. Hello Basdecort,

    I am trying to connect to near by bluetooth devices using BluetoothLE.

    Below is the code,

    IList deviceList = adp.DiscoveredDevices;
    var adpapter = CrossBluetoothLE.Current.Adapter;
    adpapter.ScanMode = ScanMode.LowLatency;
    adp.ScanTimeout = 5000;
    adpapter.DeviceDiscovered += (s, a) =>
    {
    deviceList.Add(a.Device); //Here it will add multiple available devices nearby, Also the
    Device name is null
    };
    await adpapter.StartScanningForDevicesAsync();

    await adpapter.StopScanningForDevicesAsync();

    var toConnect = deviceList.FirstOrDefault(); //As there were multiple devices discovered,
    picked the first device from the list.

    if(toConnect != null)
    {
    await adpapter.ConnectToDeviceAsync(toConnect); //This throws an error always. I have
    added this in try catch as you have
    mentioned above.
    }

    Please look at my comments. Also the problem I face is, sometime the devices are visible and sometime they are not visible.

    As I have mentioned in one of my comment that the CrossBluetoothLEAdapter discovers multiple devices, how can I differentiate between the devices if there are no names for the device and also seems the MAC is not unique or not available.

    Please help.

    Thanks in advance.

    Like

  3. Hi Nilesh,

    If I understand correctly, you are having a few issues. Let’s address them one at a time:

    Device name is Null:
    
In some cases the device name might be Null. For example when the device isn’t broadcasting a name itself (referred to as unknown device).
There is also a issue regarding this on GitHub:
    https://github.com/xabre/xamarin-bluetooth-le/issues/216

    

As a workaround, you might want to filter out devices without a name:
adapter.DiscoveredDevices.Where(x => x.Name != null).Distinct().ToList();


    Connect throws an exception:
    
What kind of Exception is this throwing? Without additional information it’s hard to tell what’s going wrong.
There are a few things important to note:
    
- You should connect from the UI thread.
    
- You can also use ConnectToKnownDeviceAsync(toConnect.Id);


    – Correct permissions should be in your Manifest.

    Devices sometimes not visible:
    
You might want to increase the adapter.ScanTimeout. Depending on bluetooth range and device configuration it might take a while for devices to be detected. 


    How to differentiate devices
:
    There are different ways to differentiate devices, but it really depends on what kind of devices you are using. Without connecting to the device, values from the IDevice are available for every device: https://github.com/xabre/xamarin-bluetooth-le/blob/master/Source/Plugin.BLE.Abstractions/Contracts/IDevice.cs .
    The name or Id might be suitable for differentiating devices. If that doesn’t work for your devices, you might want to look into the AdvertisementRecords: https://github.com/xabre/xamarin-bluetooth-le/blob/master/Source/Plugin.BLE.Abstractions/AdvertisementRecord.cs



    I hope this is of any help to you.

    Like

  4. Hello Basdecort,

    Please see my reply against your comment.

    Device name is Null:
    
In some cases the device name might be Null. For example when the device isn’t broadcasting a name itself (referred to as unknown device).
There is also a issue regarding this on GitHub:
    https://github.com/xabre/xamarin-bluetooth-le/issues/216

    

As a workaround, you might want to filter out devices without a name:
adapter.DiscoveredDevices.Where(x => x.Name != null).Distinct().ToList();


    Nilesh =>I have tried doing this, but as I mentioned the name is null and at my end all the device names are null. I am trying with some other Hardwares and with mobiles(OnePlus2,MotoG).

    Connect throws an exception:
    
What kind of Exception is this throwing? Without additional information it’s hard to tell what’s going wrong.
There are a few things important to note:
    
- You should connect from the UI thread.
    
- You can also use ConnectToKnownDeviceAsync(toConnect.Id);


    – Correct permissions should be in your Manifest.

    Nilesh =>
    For this let me add my code over here:
    Also the exception which is thrown is : GattCallback error: 133

    Code:
    Class MainActivity
    {
    private IAdapter _adapter;
    private IBluetoothLE _ble;
    private IList _connectedDevices;

    public MainActivity()
    {
    this._ble = CrossBluetoothLE.Current;
    this._adapter = CrossBluetoothLE.Current.Adapter;
    }
    protected override void OnCreate(Bundle bundle)
    {
    base.OnCreate(bundle);

    this.SetContentView(Resource.Layout.Main);

    var btStartScan = this.FindViewById(Resource.Id.btStartScan);
    btStartScan.Click += this.StartScan;
    var btConnect = this.FindViewById(Resource.Id.btConnect);
    btConnect.Click += this.Connect;
    }
    public async void StartScan(object sender, EventArgs ea)
    {
    this._adapter = CrossBluetoothLE.Current.Adapter;
    this._adapter.ScanMode = ScanMode.LowLatency;
    this._adapter.ScanTimeout = 5000;
    await this._adapter.StartScanningForDevicesAsync();
    this._connectedDevices = this._adapter.DiscoveredDevices;
    }
    public async void Connect(object sender, EventArgs ea)
    {
    try
    {
    await this._adapter.StopScanningForDevicesAsync();
    if(this._connectedDevices != null)
    {
    foreach (IDevice device in this._connectedDevices)
    {
    try
    {
    await adp.ConnectToDeviceAsync(device); // It is breaking here.
    //await this._adapter.ConnectToKnownDeviceAsync(Guid.Parse(“00000000-0000-0000-0000-3ed0763bb43d”));// Have use this, but there was no success.
    // To connect to known device, I found GUID by this – device.Id (Please let me know if this correct).
    break; //wanted to connect to anyone device for testing purpose
    }
    catch (DeviceConnectionException e)
    {
    //Exception
    }
    }
    }
    }
    }
    }
    }

    Permission in Manifest:

    Devices sometimes not visible:
    
You might want to increase the adapter.ScanTimeout. Depending on bluetooth range and device configuration it might take a while for devices to be detected. 


    Nilesh =>Have tried this.

    How to differentiate devices
:
    There are different ways to differentiate devices, but it really depends on what kind of devices you are using. Without connecting to the device, values from the IDevice are available for every device: https://github.com/xabre/xamarin-bluetooth-le/blob/master/Source/Plugin.BLE.Abstractions/Contracts/IDevice.cs .
    The name or Id might be suitable for differentiating devices. If that doesn’t work for your devices, you might want to look into the AdvertisementRecords: https://github.com/xabre/xamarin-bluetooth-le/blob/master/Source/Plugin.BLE.Abstractions/AdvertisementRecord.cs



    Nilesh => I will try this

    Thanks,
    Nilesh

    Like

    1. Hi Nilesh,

      Device name is Null:
      Instead of using _adapter.DiscoveredDevices to find your devices, you can register an event handler on _adapter.DeviceAdvertised. This will notify you every time a device is detected.

      Connect throws an exception:
      The GattCallback error you are mentioning is very broad. Often this indicates that you are not running on the main thread. Wrapping the Connect code in Activity.RunOnUiThread() might fix this issue. You also might want to pass the additional parameter “forceBleTransport: true” to the Connect method.

      Hopefully this helps.

      Bas

      Like

  5. Hello Nilesh,

    Thank you so much for your post, I find it very useful!

    From your experience, is it possible for one mobile device to be connected simultaneously to two different Blue Tooth devices (Data & Audio for both BT devices), for ex., to play file A to one BT and play file B the other BT at the same time?

    Cheers.
    Wheelie

    Like

    1. Hello Wheelie,

      Thanks for your reply! I haven’t tried connecting to multiple BLE devices simultaneously, but I think you should be able to implement this. I think it will be difficult to start playing on the two device at the exact same time, because you depend on the bluetooth transport. Based on the range and/or device type the messages may be delivered faster/slower. But I don’t know if that is a requirement for your scenario.

      I’m curious to know if you’ve managed to implement this!

      Like

Leave a comment