fabian
fabian

Reputation: 63

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));
    }
}

Upvotes: 6

Views: 3959

Answers (3)

prokaryoticeukaryote
prokaryoticeukaryote

Reputation: 151

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.

Upvotes: 4

sam msft
sam msft

Reputation: 597

It is a little hard to specifically answer your question because you can use Bluetooth in several ways to connect to a device. For instance, some devices need to be paired to be usable. Some devices can't be discovered unless the Bluetooth stack is issuing an enquiry.

There are a few things to do though to get this pointed in the right direction.

  1. The first thing you want to do is use an AQS selector string to constrain what you are discovering. The selector should also constrain the protocol because you don't want the PC discovering WiFiDirect devices and network devices. Post filtering is not good for performance.
  2. Next look at the Bluetooth samples. They have many get selector type methods for different scenarios that you can use to pass into create watcher. Either one of those will do what you want, or you can look at their AQS string to get an idea of what your filter should look like.

In terms of properties you can use for your AQS selector, I am not sure if is present will be effective, but here are some other properties you might consider for constructing your selector/filter. Like I said, the scenario makes a big difference; like a headset must be paired, whereas some LE device can't be paired and are just connectable.

System.Devices.Aep.Bluetooth.IssueInquiry
System.Devices.Aep.Bluetooth.LastSeenTime
System.Devices.Aep.Bluetooth.Le.IsConnectable
System.Devices.Aep.IsPaired
System.Devices.Aep.CanPair
System.Devices.Aep.IsConnected
System.Devices.Aep.IsPresent
System.Devices.Aep.ProtocolId

A selector could look like this to enumerate Bluetooth BR devices that are paired:

System.Devices.Aep.ProtocolId:="{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}" AND System.Devices.Aep.IsPaired:=System.StructuredQueryType.Boolean#True";

If you have a more specific device scenario, I can elaborate with a more specific answer.

Upvotes: 0

xmedeko
xmedeko

Reputation: 7820

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;
}

Upvotes: 5

Related Questions