Share via

Bluetooth devices

Eduardo Gomez 4,316 Reputation points
2025-09-20T00:51:12.07+00:00

I am making an app, that scan for Bluetooth devices

I am on Windows

I made an interface

public interface IBluetoothDeviceService {

    /// <summary>
    /// Starts discovery of nearby Bluetooth LE devices.
    /// Only devices that are currently advertising will be found.
    /// </summary>
    /// <param name="timeoutSeconds">How long to scan before stopping automatically.</param>
    /// <returns>A task that completes with the list of discovered device names.</returns>
    Task<IEnumerable<string>> DiscoverNearbyDevicesAsync(int timeoutSeconds = 5);

}


and I made a service

#if WINDOWS
using System.Collections.Concurrent;
using Windows.Devices.Bluetooth;
using Windows.Devices.Bluetooth.Advertisement;
using Windows.Devices.Enumeration;
namespace SnapLabel.Platforms.Windows;
public class BluetoothDeviceService : IBluetoothDeviceService {
    public async Task<IEnumerable<string>> DiscoverNearbyDevicesAsync(int timeoutSeconds) {
        var devices = new ConcurrentDictionary<string, byte>();
        var bleScanCompleted = new TaskCompletionSource<bool>();
        var bleWatcher = new BluetoothLEAdvertisementWatcher {
            ScanningMode = BluetoothLEScanningMode.Active
        };
        bleWatcher.Received += (s, args) => {
            _ = Task.Run(async () => {
                string name = args.Advertisement.LocalName;
                if(string.IsNullOrWhiteSpace(name)) {
                    try {
                        var selector = BluetoothDevice.GetDeviceSelectorFromBluetoothAddress(args.BluetoothAddress);
                        var info = await DeviceInformation.FindAllAsync(selector);
                        name = info.FirstOrDefault()?.Name ?? string.Empty;
                    } catch {
                        name = string.Empty;
                    }
                }
                if(!string.IsNullOrWhiteSpace(name) && !name.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) {
                    string mac = args.BluetoothAddress.ToString("X");
                    string label = $"{name} ({mac})";
                    devices.TryAdd(label, 0);
                }
            });
        };
        bleWatcher.Stopped += (s, e) => bleScanCompleted.TrySetResult(true);
        bleWatcher.Start();
        await Task.Delay(TimeSpan.FromSeconds(timeoutSeconds));
        if(bleWatcher.Status == BluetoothLEAdvertisementWatcherStatus.Started) {
            bleWatcher.Stop();
        }
        await bleScanCompleted.Task;
        // 🔍 Classic Bluetooth scan
        var classicSelector = "System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
        var properties = new[]
        {
            "System.Devices.Aep.DeviceAddress",
            "System.Devices.Aep.IsConnected"
        };
        var classicDevices = await DeviceInformation.FindAllAsync(
            classicSelector,
            properties,
            DeviceInformationKind.AssociationEndpoint);
        foreach(var device in classicDevices) {
            if(!string.IsNullOrWhiteSpace(device.Name) &&
                !device.Name.Equals("Unknown", StringComparison.OrdinalIgnoreCase)) {
                var address = device.Properties["System.Devices.Aep.DeviceAddress"]?.ToString() ?? string.Empty;
                if(!string.IsNullOrWhiteSpace(address)) {
                    string label = $"{device.Name} ({address.Replace(":", "")})";
                    devices.TryAdd(label, 0);
                }
            }
        }
        return devices.Keys;
    }
}
#endif


and I made this vm

namespace SnapLabel.ViewModels {

    public partial class InventoryPageViewModel(DatabaseService databaseService, IShellService shellService, IBluetoothDeviceService bluetoothDeviceService) : ObservableObject {

        [ObservableProperty]
        public partial bool Scanning { get; set; }

        public ObservableCollection<Product> Products { get; set; } = [];

        public ObservableCollection<string> Devices { get; set; } = [];

        public async Task GetItems() {

            var items = await databaseService.GetItemsAsync();

            Products.Clear();

            if(items.Count > 0) {

                foreach(var item in items) {
                    Products.Add(item);
                }
            }
        }

        [RelayCommand]
        public async Task DiscoverNearbyDevicesAsync() {
            if(Scanning) {
                return;
            }

            try {
                Scanning = true;

                var devices = await bluetoothDeviceService.DiscoverNearbyDevicesAsync();

                string message;
                if(devices != null && devices.Any()) {
                    message = string.Join("\n", devices);
                }
                else {
                    message = "No nearby devices found. Make sure your device is discoverable.";
                }

                // Display all discovered devices in a popup

                Debug.WriteLine($"Discovered Devices: {message}");
                await shellService.DisplayAlertAsync("Nearby Bluetooth Devices", message, "OK");
            } finally {
                Scanning = false;
            }
        }




        [RelayCommand]
        async Task AddItem() {

            await shellService.NavigateToAsync(nameof(NewProductPage));
        }

    }
}

The problem that has is that this is very slow, compared with the Settings app in window 11

Untitled video - Made with Clipchamp

I am using .NET 9

I am testing on Windows for now

Developer technologies | .NET | .NET Multi-platform App UI

Answer accepted by question author

Michael Le (WICLOUD CORPORATION) 11,325 Reputation points Microsoft External Staff Moderator
2025-09-22T09:09:12.12+00:00

Hello @Eduardo Gomez,

Thank you for reaching out.

I believe the issue lies here:

var btDevice = await BluetoothDevice.FromIdAsync(deviceInfo.Id);

This call doesn't just retrieve device information; it also initiates a connection to the device. For devices with security features, this can result in timeouts or multiple retry attempts.

Instead, you can configure DeviceWatcher to provide device information directly. Then, in the Added event handler, extract the device information directly from deviceInfo.Properties.

public class WindowsBluetoothService : IBluetoothService {
    public event Action<BluetoothDeviceModel>? DeviceFound;
    private DeviceWatcher? _watcher;
    private readonly HashSet<string> _seenDevices = [];
 
    public void StartScan() {
        _seenDevices.Clear();
        string selector =
            "System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
       
        _watcher = DeviceInformation.CreateWatcher(
            selector,
            [
                "System.Devices.Aep.DeviceAddress",
                "System.ItemNameDisplay",
                "System.Devices.Aep.ClassOfDevice", // Device class for icon determination
                "System.Devices.Aep.Category"
            ],
            DeviceInformationKind.AssociationEndpoint
        );
       
        _watcher.Added += (s, deviceInfo) => {
            string name = !string.IsNullOrWhiteSpace(deviceInfo.Name)
                ? deviceInfo.Name
                : "Unknown Device";
           
            if (name.EndsWith("(Bluetooth)", StringComparison.OrdinalIgnoreCase)) {
                name = name.Replace("(Bluetooth)", "").Trim();
            }
           
            if (!_seenDevices.Add(name)) {
                return;
            }
           
            string fontIcon = GetDeviceIcon(deviceInfo, name);
           
            var model = new BluetoothDeviceModel {
                Name = name,
                Address = deviceInfo.Properties.TryGetValue("System.Devices.Aep.DeviceAddress", out var address)
                    ? address?.ToString() ?? ""
                    : "",
                FontIcon = fontIcon
            };
           
            Debug.WriteLine($"[FOUND] {name} ({model.FontIcon})");
           
            MainThread.BeginInvokeOnMainThread(() => {
                DeviceFound?.Invoke(model);
            });
        };
       
        _watcher.Start();
    }
   
    private string GetDeviceIcon(DeviceInformation deviceInfo, string name) {
        if (deviceInfo.Properties.TryGetValue("System.Devices.Aep.ClassOfDevice", out var classOfDeviceObj) &&
            classOfDeviceObj is uint classOfDevice && classOfDevice > 0) {
           
            uint majorDeviceClass = (classOfDevice >> 8) & 0x1F;
            return majorDeviceClass switch {
                1 => FontsConstants.Computer,
                2 => FontsConstants.Smartphone,
                4 => FontsConstants.Headphones,
                5 => FontsConstants.Mouse,
                6 => FontsConstants.Print,
                _ => FontsConstants.Bluetooth_audio
            };
        }
 
        return FontsConstants.Bluetooth_audio;
    }
   
    public void StopScan() {
        if (_watcher != null &&
            (_watcher.Status == DeviceWatcherStatus.Started ||
             _watcher.Status == DeviceWatcherStatus.EnumerationCompleted)) {
            _watcher.Stop();
            _watcher = null;
        }
    }
}

I hope this helps.

Was this answer helpful?


1 additional answer

Sort by: Most helpful
  1. Eduardo Gomez 4,316 Reputation points
    2025-09-23T18:13:38.47+00:00

    I fix it, one crucial part is that windows search keeps searching until you press done or cancel

    using Windows.Devices.Bluetooth;
    using Windows.Devices.Enumeration;
    namespace SnapLabel.Platforms.Windows {
        /// <summary>
        /// Windows-specific implementation of IBluetoothService.
        /// Uses DeviceWatcher to discover nearby Bluetooth devices.
        /// </summary>
        public class WindowsBluetoothService : IBluetoothService {
            public event Action<BluetoothDeviceModel>? DeviceFound;
            private DeviceWatcher? _watcher;
            private bool _keepScanning;
            private readonly HashSet<string> _seenDevices = [];
            public void StartScan() {
                _keepScanning = true;
                _seenDevices.Clear();
                StartWatcher();
            }
            public void StopScan() {
                _keepScanning = false;
                if(_watcher != null &&
                    (_watcher.Status == DeviceWatcherStatus.Started ||
                     _watcher.Status == DeviceWatcherStatus.EnumerationCompleted)) {
                    _watcher.Stop();
                    _watcher = null;
                }
            }
            private void StartWatcher() {
                string selector = "System.Devices.Aep.ProtocolId:=\"{e0cbf06c-cd8b-4647-bb8a-263b43f0f974}\"";
                _watcher = DeviceInformation.CreateWatcher(
                    selector,
                    [
                        "System.Devices.Aep.DeviceAddress",
                        "System.ItemNameDisplay"
                    ],
                    DeviceInformationKind.AssociationEndpoint
                );
                _watcher.Added += async (s, deviceInfo) => {
                    string name = !string.IsNullOrWhiteSpace(deviceInfo.Name)
                        ? deviceInfo.Name
                        : deviceInfo.Properties.TryGetValue("System.Devices.Aep.DeviceAddress", out var addr)
                            ? addr?.ToString() ?? "Unknown"
                            : "Unknown";
                    if(name.EndsWith("(Bluetooth)", StringComparison.OrdinalIgnoreCase))
                        name = name.Replace("(Bluetooth)", "").Trim();
                    if(!_seenDevices.Add(name))
                        return;
                    string fontIcon = FontsConstants.Bluetooth;
                    try {
                        var btDevice = await BluetoothDevice.FromIdAsync(deviceInfo.Id);
                        if(btDevice != null) {
                            uint major = (uint)btDevice.ClassOfDevice.MajorClass;
                            fontIcon = major switch {
                                1 => FontsConstants.Computer,
                                2 => FontsConstants.Smartphone,
                                4 => FontsConstants.Headphones,
                                5 => FontsConstants.Mouse,
                                6 => FontsConstants.Print,
                                _ => FontsConstants.Bluetooth_audio
                            };
                        }
                    } catch(Exception ex) {
                        Debug.WriteLine($"[WARN] Could not get ClassOfDevice for {name}: {ex.Message}");
                    }
                    DeviceFound?.Invoke(new BluetoothDeviceModel {
                        Name = name,
                        Address = deviceInfo.Properties.TryGetValue("System.Devices.Aep.DeviceAddress", out var a)
                            ? a?.ToString() ?? ""
                            : "",
                        FontIcon = fontIcon
                    });
                };
                _watcher.EnumerationCompleted += (s, e) => {
                    Debug.WriteLine("[INFO] Enumeration completed. Watching for new devices...");
                };
                _watcher.Updated += (s, update) => {
                    // Optional: handle property changes
                };
                _watcher.Removed += (s, update) => {
                    // Optional: handle device removal
                };
                _watcher.Start();
            }
        }
    }
    
    
    

    Maybe this code could help someone

    Was this answer helpful?

    1 person found this answer helpful.

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.