Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Windows UWP bluetooth app, devices showing up when scanning even when they are powered off

I am developing a UWP app that is using bluetooth to connect to different devices.

My problem is that some devices that has been paired or previously discovered is showing up in my device list even though they are turned off or not in range.

As of my understanding the property System.Devices.Aep.IsPresent can be used to filter out cached devices that are not available at the time but I always get "True" for that property even though I know the device is not reachable.

Any ideas on how this can be solved?

Setup

string[] requestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected", "System.Devices.Aep.IsPresent", "System.Devices.Aep.ContainerId", "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.Manufacturer", "System.Devices.Aep.ModelId", "System.Devices.Aep.ProtocolId", "System.Devices.Aep.SignalStrength"};
        _deviceWatcher = DeviceInformation.CreateWatcher("{REMOVED, NOT IMPORTANT}", requestedProperties, DeviceInformationKind.AssociationEndpoint);
        _deviceWatcher.Added += DeviceAdded;
        _deviceWatcher.Updated += DeviceUpdated;
        _deviceWatcher.Removed += DeviceRemoved;
        _deviceWatcher.EnumerationCompleted += DeviceEnumerationCompleted;

Callback for when device is added

Here isPresent is always true

private void DeviceAdded(DeviceWatcher sender, DeviceInformation deviceInfo)
{
    Device device = new Device(deviceInfo);
    bool isPresent = (bool)deviceInfo.Properties.Single(p => p.Key == "System.Devices.Aep.IsPresent").Value;
    Debug.WriteLine("*** Found device " + deviceInfo.Id + " / " + device.Id + ", " + "name: " + deviceInfo.Name + " ***");
    Debug.WriteLine("RSSI = " + deviceInfo.Properties.Single(d => d.Key == "System.Devices.Aep.SignalStrength").Value);
    Debug.WriteLine("Present: " + isPresent);
    var rssi = deviceInfo.Properties.Single(d => d.Key == "System.Devices.Aep.SignalStrength").Value;
    if (rssi != null)
        device.Rssi = int.Parse(rssi.ToString());
    if (DiscoveredDevices.All(x => x.Id != device.Id) && isPresent)
    {
        DiscoveredDevices.Add(device);
        DeviceDiscovered(this, new DeviceDiscoveredEventArgs(device));
    }
}
like image 205
fabian Avatar asked Aug 08 '17 12:08

fabian


2 Answers

Look into the Microsoft Bluetooth LE Explorer source code of GattSampleContext. You need to get properties: System.Devices.Aep.IsConnected, System.Devices.Aep.Bluetooth.Le.IsConnectable and filter only devices, which are connectable. Take care, that the device may become connectable after the DeviceWatcher.Updated event is called. So you have to keep some track of unusedDevices.

E.g. my IsConnactable filter method is:

private static bool IsConnectable(DeviceInformation deviceInformation)
{
    if (string.IsNullOrEmpty(deviceInformation.Name))
        return false;
    // Let's make it connectable by default, we have error handles in case it doesn't work
    bool isConnectable = (bool?)deviceInformation.Properties["System.Devices.Aep.Bluetooth.Le.IsConnectable"] == true;
    bool isConnected = (bool?)deviceInformation.Properties["System.Devices.Aep.IsConnected"] == true;
    return isConnectable || isConnected;
}
like image 174
xmedeko Avatar answered Nov 11 '22 21:11

xmedeko


What worked for me was to have device watcher enumerate AssociationEndpointContainers, contrary to what the documentation suggests.

Specifically, I use,

string[] RequestedProperties = { "System.Devices.Aep.DeviceAddress", "System.Devices.Aep.IsConnected", "System.Devices.Aep.Bluetooth.Le.IsConnectable"};
 DeviceInformation.CreateWatcher(BluetoothLEDevice.GetDeviceSelectorFromConnectionStatus(BluetoothConnectionStatus.Disconnected), RequestedProperties, DeviceInformationKind.AssociationEndpointContainer)

I figured this out by logging all properties of each DeviceInformation and DeviceInformationUpdate provided during discovery, and found that AssociationEndpointContainer, in contrast to AssociationEndpoint, and Device (Device!), the System.Devices.Aep.Bluetooth.Le.IsConnectable property always appears and directly corresponds to whether the device is powered on, or not.

By the way, in retrospect, this makes sense. In the documentation, we see that the AssociationEndpointContainer

represents a single physical device that might have more than one AssociationEndpoint objects associated with it. For example, if a television supports two different network protocols, the AssociationEndpointContainer would be the television. It would also have two AssociationEndpoint objects to represent each protocol.

So, this is the central structure that Windows constructs for tracking connections to a device. It makes sense that it would be able to tell us whether the device can be connected to, or not.

I must say that it is confusing as heck that, in contrast, DeviceInformationKind.Device does not provide this functionality, and further, that it doesn't even represent a device, but a component of a device:

These devices are objects that represent a piece of the device functionality and optionally have drivers loaded on them. When a physical device is paired with windows, multiple Device objects are created for it. A device contains 0 or more DeviceInterface objects, is a child to one DeviceContainer object, and is related to 0 or 1 AssociationEndpoint objects.

The official documentation on this subject is atrocious. Microsoft, here is a tip, if you don't want people tripping all over your API. I know that time is precious, and people with purse-strings have difficulty finding the decency in their stone-cold hearts to pay developers to properly document their stuff, but for the love of all that is holy, if you're going to provide only examples on how to use your stuff, then provide examples that address the most common issue for beginners.

Case in point, which is a likelier goal for a beginner BLE developer? That you'd want to discover devices, even when they can't be connected to, or that you'd want to discover only devices that can be connected to.

Sheesh!

By the way, before I figured this out, I went down the route of using the AdvertisementWatcher. Specifically, I would detect advertisements, connect to the device from the advertisement to obtain its name, and then dispose of it. This is horribly slow and can take up a ton of memory to boot (indirectly, through the Device Association Service).

This method, in my experience is superior in every way; it is faster, more reliable, and less demanding on system resources.

like image 29
prokaryoticeukaryote Avatar answered Nov 11 '22 21:11

prokaryoticeukaryote