Windows 10 Bluetooth DeviceInformation Пользовательский доступ к сопряжению запрещен - PullRequest
0 голосов
/ 24 января 2020

У меня есть приложение. NET Core 3.1 Windows (приложение WinForm), работающее на Windows 10 Версия 10.0.17134, сборка 17134. Я пытаюсь подключиться и выполнить сопряжение с устройствами Bluetooth, используя BluetoothLEAdvertisementWatcher. В основной форме приложения я использую CefSharp.WinForms для отображения страницы HTML.

Моя проблема в том, что при инициализации браузера с использованием _browser = new ChromiumWebBrowser(Settings.Default.IndexPageUrl); я больше не могу подключиться к устройству Bluetooth. Я получаю AccessDenied от pairRequest.Status в моем пользовательском событии сопряжения.

Вот код:

    using CefSharp;
    using CefSharp.WinForms;
    using Microsoft.Extensions.Logging;
    using System;
    using System.Collections.Concurrent;
    using System.Drawing;
    using System.Globalization;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    using Windows.Devices.Bluetooth;
    using Windows.Devices.Bluetooth.Advertisement;
    using Windows.Devices.Enumeration;

namespace myApp
{
public partial class MainForm : Form
{
    private readonly ILogger<MainForm> _logger;
    private ChromiumWebBrowser _browser;

    private static BluetoothLEAdvertisementWatcher _bleAdvertisementWatcher = null;
    private static volatile ConcurrentDictionary<ulong,BleDeviceInfo> _bleDevicesInfo;
    private readonly SemaphoreSlim _semaphoreSlim;
    /// <summary>
    /// Used to create a thread-safe boolean; do not access directly use AddingDevices instead
    /// </summary>
    private int _addingDevicesBool = 0; 

    public event Action<BleDeviceInfo> OnBleDeviceDetected;

    public MainForm(): this(Program.LogFactory.CreateLogger<MainForm>())
    { }

    public MainForm(ILogger<MainForm> logger)
    {
        _logger = logger;
        _semaphoreSlim = new SemaphoreSlim(1, 1);
        _bleDevicesInfo = new ConcurrentDictionary<ulong,BleDeviceInfo>();                       

        InitBluetoothUtils();
        InitializeBrowser(); 
    }

    #region browser setup
    private void InitializeBrowser()
    {
        try
        {
            Cef.EnableHighDPISupport();

            //When I comment out the 2 lines below I can succesfully pair with a BLE device
            _browser = new ChromiumWebBrowser(Settings.Default.IndexPageUrl);
            toolStripContainer.ContentPanel.Controls.Add(_browser);


            //TODO: Uncomment the lines below to see Chrome Dev Tools
            //#if DEBUG
            //                _browser.IsBrowserInitializedChanged += OnIsBrowserInitializedChanged;
            //#endif
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, Settings.BrowserInitError);
            throw;
        }            
    }

    private void OnIsBrowserInitializedChanged(object sender, EventArgs e)
    {
        _browser.ShowDevTools();
    }

    #endregion

    #region BLE setup

    #region properties       
    /// <summary>
    /// Thread-safe boolean property
    /// </summary>
    public bool ListeningForBleDevices
    {
        get { return (Interlocked.CompareExchange(ref _addingDevicesBool, 1, 1) == 1); }
        set
        {
            if (value) 
            {
                Interlocked.CompareExchange(ref _addingDevicesBool, 1, 0); 
            }
            else 
            {
                Interlocked.CompareExchange(ref _addingDevicesBool, 0, 1);
            }
        }
    }

    #endregion

    private void InitBluetoothUtils()   
    {            
        StartListeningForBleDevices();
    }

    public void StartListeningForBleDevices()   
    {
        ListeningForBleDevices = true;
        StartBleWatcher();
    }

    private void StartBleWatcher()
    {
        try
        {           
            ListeningForBleDevices = false; 

            if(_bleAdvertisementWatcher == null)
            {
                _bleAdvertisementWatcher = new BluetoothLEAdvertisementWatcher
                {
                    ScanningMode = BluetoothLEScanningMode.Active
                };

                // Only activate the watcher when we're recieving values >= -80
                _bleAdvertisementWatcher.SignalStrengthFilter.InRangeThresholdInDBm =  -80;

                // Stop watching if the value drops below -90 (user walked away)
                _bleAdvertisementWatcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = -90;

                // Register events
                _bleAdvertisementWatcher.Received += OnBleAdvWatcherReceived;
                _bleAdvertisementWatcher.Stopped += OnBleAdvWatcherStopped;

                // Wait 5 seconds to make sure the device is really out of range
                _bleAdvertisementWatcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
                _bleAdvertisementWatcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
            }    

            //Start Watcher
            ListeningForBleDevices = true;
            _bleAdvertisementWatcher.Start();
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, string.Empty);
            throw;
        }            
    }

    public void StopBleWatcher()
    {
        _bleAdvertisementWatcher?.Stop();
        ListeningForBleDevices = false;
    }

    private void OnBleAdvWatcherStopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args)
    {
        _logger.LogInformation("Ble Advertisement Watcher stopped");
    }

    /// <summary>
    /// This event will fire multiple times for the same BLE device
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="args"></param>
    private async void OnBleAdvWatcherReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
    {
        if(!ListeningForBleDevices)
        {
            return;
        }

        BleDeviceInfo bleDeviceInfo = null;

        try
        {
            var advLocalName = args.Advertisement.LocalName;

            if (string.IsNullOrEmpty(advLocalName) || !advLocalName.Contains("my custom prefix", StringComparison.OrdinalIgnoreCase))
            {
                return;
            }

            _semaphoreSlim.Wait();

            bleDeviceInfo = await SetBleDeviceInfo(args).ConfigureAwait(false);

            _semaphoreSlim.Release();  
        }
        catch (Exception ex)
        {
             _logger.LogError(ex, string.Empty);
            _semaphoreSlim.Release();  

            throw;
        } 

        if(bleDeviceInfo != null)
        {                
            OnBleDeviceDetected?.Invoke(bleDeviceInfo);
        }            
    }

    private async Task<BleDeviceInfo> SetBleDeviceInfo(BluetoothLEAdvertisementReceivedEventArgs args)
    {
        BleDeviceInfo bleDeviceInfo = null;

        try
        {                
            var bluetoothAddress = args.BluetoothAddress;
            var deviceExists = GetBleDevice(bluetoothAddress);

            if(deviceExists == null)
            {
                BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress);

                //TODO: Pairing will not work when ChromiumWebBrowser gets initialized
                var isPaired = await PairDevice(bleDevice).ConfigureAwait(false);

                bleDeviceInfo = new BleDeviceInfo(bleDevice, args);

                _bleDevicesInfo.AddOrUpdate(bleDeviceInfo.BluetoothAddress, bleDeviceInfo, (key, bleDeviceInfo) => bleDeviceInfo);
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, string.Empty);
            throw;
        } 

        return bleDeviceInfo;
    }

    public BleDeviceInfo GetBleDevice(ulong bluetoothAddress)
    {
        try
        {
            return _bleDevicesInfo.Values.FirstOrDefault(d => d.BluetoothAddress.Equals(bluetoothAddress));
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, string.Empty);
            throw;
        }              
    }

    public async Task<bool> PairDevice(BluetoothLEDevice bleDevice)
    {
        if (bleDevice == null)
        {
            return false;
        }

        var isPaired = false; // bleDevice.DeviceInformation.Pairing.IsPaired;

        try
        {
            var canPair = bleDevice.DeviceInformation.Pairing.CanPair;
            var deviceId = bleDevice.DeviceId;

            if (canPair)
            {
                DeviceInformationCustomPairing customPairing = bleDevice.DeviceInformation.Pairing.Custom;

                customPairing.PairingRequested += OnCustomPairingRequested;

                var pairRequest = await customPairing.PairAsync(DevicePairingKinds.ProvidePin, DevicePairingProtectionLevel.None);

                customPairing.PairingRequested -= OnCustomPairingRequested;

                var resultStatus = pairRequest.Status;
                isPaired = resultStatus == DevicePairingResultStatus.Paired;

                if (isPaired)
                {
                    //TODO: Verify this code block
                    bleDevice.Dispose();
                    Thread.Sleep(2000); //try 2 second delay.                        

                    //Reload device so that the GATT services are there. This is why we wait.           
                    bleDevice = await BluetoothLEDevice.FromIdAsync(deviceId);
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, string.Empty);
            throw;
        }

        return isPaired;
    }

    public async Task<bool> PairDevice(ulong bluetoothAddress)
    {
        BluetoothLEDevice bleDevice = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress, BluetoothAddressType.Public);
        return await PairDevice(bleDevice).ConfigureAwait(false);
    }

    private void OnCustomPairingRequested(DeviceInformationCustomPairing sender, DevicePairingRequestedEventArgs args)
    {
        try
        {
            //This method does not get called when ChromiumWebBrowser is initialized 
            //However, when _browser = new ChromiumWebBrowser is not called this method works as expected 
            var deviceName = args.DeviceInformation.Name;
            var devicePin = GetBlePin(deviceName);
            var pinDeferral = args.GetDeferral();

            args.Accept(devicePin.ToString(CultureInfo.InvariantCulture));

            pinDeferral.Complete();
        }
        catch (Exception)
        {
            throw;
        }
    }

    private int GetBlePin(string serialNumber)
    {
        return 12345;
    }

    #endregion

}

}

То, что я пробовал:

  • Вызов InitializeBrowser в отдельном потоке с использованием Task.Run(() => { InitializeBrowser(); }).ConfigureAwait(false);
  • Вызов InitBluetoothUtils в отдельном потоке с использованием Task.Run(() => { InitBluetoothUtils(); }).ConfigureAwait(false);
  • Вызов InitializeBrowser и InitBluetoothUtils в отдельных потоках
  • Пробовал использовать SynchronizationContext, получая текущий контекст в конструкторе MainForm, а затем вызывая PairDevice(bleDevice), используя этот контекст

Каждый раз, когда я получаю AccessDenied. Единственное, что работает успешно, это когда я закомментирую _browser = new ChromiumWebBrowser(BASettings.Default.IndexPageUrl); и toolStripContainer.ContentPanel.Controls.Add(_browser);

...