У меня есть приложение. 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);