Categories
Bluetooth

12 Days of Bluetooth – #5 Coding Bluetooth Classic

All the previous posts in this series have talked about Bluetooth technology, in this post we will look at a practical example (using .NET of course!).

32feet.NET

From the initial release of the .NET Compact Framework, Microsoft included a library to work with IrDA. This used an API similar to TcpClient and TcpListener but also included functionality to enumerate devices. All of this was built on top of Windows sockets support which was already part of the OS – functionality which was also present in desktop Windows even though it had no .NET API. I set about creating a library which provided the same functionality so that it would work on both full .NET and the Compact Framework. Additionally I was looking at the Bluetooth APIs on both platforms. They had similar capabilities but completely different APIs except for the sockets support which was largely the same. 32feet.NET came about by combining all of this into one library. The name is derived from the fact that 32 feet (or 20 metres) was the maximum range of Bluetooth at the time.

The library has gone through some updates over time but still based around Client and Listener classes and Sockets doing the actual communication work. In order to support a wider range of platforms the BluetoothClient supports working with a stream which sits above the underlying Socket. There are a number of good reasons for this – on Android the incoming and outgoing streams are separate entities – the library hides this behind a single stream. On iOS there are no Sockets, in fact there is no API for Bluetooth Classic but there is a workaround of sorts (this one deserves its own blog post).

Discovery

The first time you want to connect to a device and exchange some data you will first need to discover the available devices. Since the process can take some time to complete it is a good idea to save the address of the chosen device if you are going to use it again. It will be much easier to try to connect, and handle failure, than performing a new discovery each time.

BluetoothClient currently provides a single method to perform discovery which is run synchronously (for historical reasons but this is something being worked on). The method has an optional parameter to limit the number of results returned but depending on the platform this won’t always shorten the overall time taken. The method returns a read-only collection of BluetoothDeviceInfo objects. This type contains the address, name and class of device information to uniquely identify a remote device. You can enumerate the devices using a foreach loop and read the properties to determine if they match your required device type:-

foreach (BluetoothDeviceInfo bdi in client.DiscoverDevices())
{
    // do something with the device...
    System.Diagnostics.Debug.WriteLine(bdi.DeviceName + " " + bdi.DeviceAddress);
}

Pick Me, Pick Me!

If you don’t want to reinvent the wheel yourself you can also take advantage of the native OS device picker. The BluetoothDevicePicker has been rewritten to support async and multi-platforms. Its predecessor the SelectBluetoothDeviceDialog was closely tied to WinForms. This class will present a familiar UI to the user and return either a device if chosen or null if the user cancelled:-

var picker = new BluetoothDevicePicker();
            
device = await picker.PickSingleDeviceAsync();

Make a Connection

Once you have a device, you need to know the UUID (a Guid in .NET) of the service you want to connect to. The BluetoothService class contains static Guids for all the standard defined services. You pass the address and service UUID to the Connect method. Once this method returns you can check the Connected property to determine if it was successful. From here you can call the GetStream method to return a standard .NET Stream which you can use to read from and write to. If you are writing text then you can wrap this in a StreamWriter (ensuring you use the expected encoding) and write data to the device. Since the stream used implements buffering, be sure to call Flush() to empty the buffer and send all data to the remote device. The following example is used with a generic serial printer which echoes text to the thermal print roll. There are escape codes to print images, barcodes etc but for simplicity we just send simple text.

When finished make sure you close the stream (by default a StreamWriter will close the associated stream when you close that), this in turn closes the underlying socket.

client.Connect(device.DeviceAddress, BluetoothService.SerialPort);
if (client.Connected)
{
    stream = client.GetStream();
    StreamWriter sw = new StreamWriter(stream, System.Text.Encoding.UTF8);
    sw.WriteLine("Hello world!");
    sw.Flush();
    sw.Close();
}

One of the interesting things which you may notice after a while is that disconnections are not reported immediately – a socket may continue to report that it is connected even if the remote device has powered down. The first notice you get that a connection has broken is usually an error when writing data. We will look into this in more detail in a future post.

Get Involved

If you would like to get involved there are plenty of opportunities to contribute to the code or documentation as the project expands to new device platforms and functionality. You can also sponsor the project to help move things along.

Next Time

In the next post we will look at what on earth is going on with iOS!

By Peter Foot

Microsoft Windows Development MVP