.NET and Linux Bluetooth

.NET and Linux Bluetooth

linux projects

Ever needed to perform some Bluetooth LE operations on your Linux device, whether it’s Raspberry PI, Ubuntu, or some other distribution? Yes, me too! In this article, we’ll dive right into using the Plugin.BlueZ NuGet package and show you how to get started using it. Feel free to contribute to the open source project over at the Suess Labs GitHub page, https://github.com/SuessLabs/Plugin.BlueZ.

UPDATE!

We are migrating towards the next generation package, Linux.Bluetooth NuGet package!
The GA functionality is the same and future enhancements are coming soon, such as Battery monitoring.

In this article, we’ll perform a simple Scanning for Devices based on the library’s scan sample.

Background

On Linux devices, we’ll access the Bluetooth radio using the BlueZ library over the D-Bus inter-processing communication (IPC) service. Now you can wow your friends or family at the next BBQ with this techno-lingo. So what is this? Basically, with D-Bus, you don’t directly call the Bluetooth API like you would on Windows, Android, iOS, or Mac. Instead, you use this mechanism which allows communication between multiple processes running concurrently on the same machine.

The beauty of using this NuGet package is that you can write your code on Windows and deploy it to your Linux device for debugging with ease. Check out our article on Visual Studio Linux Debugger, I use this nearly every time for my Linux projects at work.

Getting Started

In this sample, we’ll cover the simple, Scan for Devices. This example is similar to that used in the sample projects. We’ll start by making a new project in Linux

If you don’t have a Linux machine, you can also use a Virtual Machine such as Virtual Box. This is a great way to quickly test your projects.

Initializing an Adapter

First, create a simple console project and add the NuGet package, Plugin.BlueZ.

Next, add some lines of code to your main() method, followed by a helper method GetDeviceDescriptionAsync(...) which we’ll use later.

using System;
using System.Linq;
using System.Threading.Tasks;
using Plugin.BlueZ;
using Plugin.BlueZ.Extensions;

namespace Scan
{
  public class Program
  {
    static async Task Main(string[] args)
    {
      // Get the first known adapter (most PCs only have 1 BLE Adapter)
      IAdapter1 adapter = (await 
      BlueZManager.GetAdaptersAsync()).FirstOrDefault();
  }

    private static async Task<string> GetDeviceDescriptionAsync(IDevice1 device)
    {
      var deviceProperties = await device.GetAllAsync();
      return $"{deviceProperties.Alias} (Address: {deviceProperties.Address}, RSSI: {deviceProperties.RSSI})";
    }
}

Scan for Known Devices

If you’d like to first check for BLE devices already paired with your system, you can call the adapter’s GetDevicesAsync() method and iterate through it. Simply add this code at the end of your main() method.

      ...

      Console.WriteLine("Getting known devices...");
      var devices = await adapter.GetDevicesAsync();
      foreach (var device in devices)
      {
        string deviceDescription = await GetDeviceDescriptionAsync(device);
        Console.WriteLine($" - {deviceDescription}");
      }

      Console.WriteLine($"Found {devices.Count} paired device(s).");

Scan for New Devices

To begin listening for nearby BLE devices, we’re going to use the adapter’s DeviceFound event handler and start scanning with the StartDiscoveryAsync() method.

      // Scan for more devices.
      Console.WriteLine($"Scanning for {scanSeconds} seconds...");

      int newDevices = 0;
      using (await adapter.WatchDevicesAddedAsync(async device =>
      {
        newDevices++;
        // Write a message when we detect new devices during the scan.
        string deviceDescription = await GetDeviceDescriptionAsync(device);
        Console.WriteLine($"[NEW] {deviceDescription}");
      }))
      {
        await adapter.StartDiscoveryAsync();
        await Task.Delay(TimeSpan.FromSeconds(scanSeconds));
        await adapter.StopDiscoveryAsync();
      }

      Console.WriteLine($"Scan complete. {newDevices} new device(s) found.");

Run the code

Full Source

To copy paste and go using the original Scan program…

using System;
using System.Linq;
using System.Threading.Tasks;
using Plugin.BlueZ;
using Plugin.BlueZ.Extensions;

namespace Scan
{
  /// <summary>
  ///   GATT Client - Scan for devices
  ///
  /// Usage: dotnet scan.dll [SecondsToScan] [adapterName]
  /// Usage: dotnet scan.dll -h    Help menu
  ///
  /// </summary>
  /// <param name="args">(optional) SecondsToScan, (optional) AdapterName</param>
  /// <returns>Task.</returns>
  public class Program
  {
    static TimeSpan timeout = TimeSpan.FromSeconds(15);

    static async Task Main(string[] args)
    {
      if (
        args.Length < 1 || args.Length > 2 ||
        args[0].ToLowerInvariant() == "-h" ||
        !int.TryParse(args[0], out int scanSeconds))
      {
        Console.WriteLine("Usage: scan <SecondsToScan> <adapterName>");
        Console.WriteLine("Example: scan 10 hci0");
        return;
      }

      IAdapter1 adapter;
      if (args.Length > 1)
      {
        adapter = await BlueZManager.GetAdapterAsync(args[1]);
      }
      else
      {
        var adapters = await BlueZManager.GetAdaptersAsync();
        if (adapters.Count == 0)
        {
          throw new Exception("No Bluetooth adapters found.");
        }

        adapter = adapters.First();
      }

      var adapterPath = adapter.ObjectPath.ToString();
      var adapterName = adapterPath.Substring(adapterPath.LastIndexOf("/") + 1);
      Console.WriteLine($"Using Bluetooth adapter {adapterName}");
      Console.WriteLine($"Adapter's full path:    {adapterPath}");

      // Print out the devices we already know about.
      Console.WriteLine();
      Console.WriteLine("Getting known devices...");
      var devices = await adapter.GetDevicesAsync();
      foreach (var device in devices)
      {
        string deviceDescription = await GetDeviceDescriptionAsync(device);
        Console.WriteLine($" - {deviceDescription}");
      }

      Console.WriteLine($"Found {devices.Count} paired device(s).");
      Console.WriteLine();

      // Scan for more devices.
      Console.WriteLine($"Scanning for {scanSeconds} seconds...");

      int newDevices = 0;
      using (await adapter.WatchDevicesAddedAsync(async device =>
      {
        newDevices++;
        // Write a message when we detect new devices during the scan.
        string deviceDescription = await GetDeviceDescriptionAsync(device);
        Console.WriteLine($"[NEW] {deviceDescription}");
      }))
      {
        await adapter.StartDiscoveryAsync();
        await Task.Delay(TimeSpan.FromSeconds(scanSeconds));
        await adapter.StopDiscoveryAsync();
      }

      Console.WriteLine($"Scan complete. {newDevices} new device(s) found.");
    }

    private static async Task<string> GetDeviceDescriptionAsync(IDevice1 device)
    {
      var deviceProperties = await device.GetAllAsync();
      return $"{deviceProperties.Alias} (Address: {deviceProperties.Address}, RSSI: {deviceProperties.RSSI})";
    }
  }
}