C# Windows 10 Bluetooth LE can't connect to server - c#

I'm developing a c# desktop api with forms where I want to receive ACC data from a BLE server und display them in a chart.
So I'm running in a connection problem and I can't find any solution.
I can find my LE server Device with the watcher.
DevicePairingResult dpr = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.Encryption);
returns me "AlreadyPaired"
But when I do
device = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress: eventArgs.BluetoothAddress);
mGattService = device.GetGattService(MotionService_GUID);
mCharacteristic = mGattService.GetCharacteristics(ACC_Characteristic_GUID)[0];
and then
var con = device.ConnectionStatus;
I receive "Disconnected" in con.
I am bound with de device on windows( I searched for it in Windows and entered the Code) but I am not connected(based on the Status in the windows info center).
I've read in another Thread in the windows c# developer page that it should not be necessary anymore to pair the device manually.
I'm pretty shure that the rest of my code works because sometimes I can get a connection( pretty confusing for me) and see the right Data in my chart.
Right now I just want to reach a stable connection before changing other part of my code.
Anyone any idea how to solve this?
Thx medTech
Edit:
Here is part of the Code:
Scanning for BLE
private void button1_Click(object sender, EventArgs e)
{
// Create Bluetooth Listener
var watcher = new BluetoothLEAdvertisementWatcher();
watcher.ScanningMode = BluetoothLEScanningMode.Active;
// Register callback for when we see an advertisements
watcher.Received += OnAdvertisementReceivedAsync;
// Wait 5 seconds to make sure the device is really out of range
watcher.SignalStrengthFilter.OutOfRangeTimeout = TimeSpan.FromMilliseconds(5000);
watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromMilliseconds(2000);
// Starting watching for advertisements
watcher.Start();
}
Connect to Server:
private async void OnAdvertisementReceivedAsync(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs)
{
// Filter for specific Device
if (eventArgs.Advertisement.LocalName == "MYDEVICE")
{
watcher.Stop();
var MotionService_GUID = new Guid("00002000-0000-1000-8000-00805F9B34FB");
var ACC_Characteristic_GUID = new Guid("00002001-0000-1000-8000-00805F9B34FB");
device = await BluetoothLEDevice.FromBluetoothAddressAsync(bluetoothAddress: eventArgs.BluetoothAddress);
DevicePairingResult dpr = await device.DeviceInformation.Pairing.PairAsync(DevicePairingProtectionLevel.Encryption);
mGattService = device.GetGattService(MotionService_GUID);
mCharacteristic = mGattService.GetCharacteristics(ACC_Characteristic_GUID)[0];
GattDeviceServicesResult result = await device.GetGattServicesAsync();
GattCommunicationStatus status1 = await ReadFromCharacteristicAsync(mCharacteristic);
var con = device.ConnectionStatus;
while (status1 == GattCommunicationStatus.Success)
{
try
{
status1 = await ReadFromCharacteristicAsync(mCharacteristic);
}
catch
{
Console.WriteLine("ERROR");
status1 = GattCommunicationStatus.Unreachable;
}
}
}
}
Read from Characteristic:
async Task ReadFromCharacteristicAsync(GattCharacteristic mCharacteristic)
{
GattReadResult readResult = await mCharacteristic.ReadValueAsync(BluetoothCacheMode.Uncached);
if (readResult.Status == GattCommunicationStatus.Success)
{
byte[] data = new byte[readResult.Value.Length];
DataReader.FromBuffer(readResult.Value).ReadBytes(data);
if (chart1.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate { updateChart(data); });
}
return readResult.Status;
}
return readResult.Status;
}
Terminate Connection
private async Task<bool> ClearBluetoothLEDeviceAsync()
{
mCharacteristic.Service.Dispose();
mGattService.Dispose();
await device.DeviceInformation.Pairing.UnpairAsync();
device?.Dispose();
device = null;
GC.Collect();
return true;
}
SO now when I connect the first time to the Server, I only receive zeros which shows me that the there might be a authentication Error.
After that I always receive this Error:
"System.ArgumentException" in mscorlib.dll with a notification that there is noch executable Code left because all Threads are doing some asynchronous stuff.
This Error gets thrown when I try to read from the Characteristic.
I never coded in c# before so I am not shure if there is an error in my asynchronous part oder the communication part.
Thanks you

Pairing is not the same as connecting!
I really advise using the BLE-advertisementWatcher to select and connect to your device.
The reason is that many BLE-devices don't save their pairing status.
In windows device-watcher once paired, the device stays paired even if it is switched off or out of reach.
Also many times the connection status is kept, unless the device is unpaired and disposed in code or removed in windows settings.
All BLE-devices that I know of start advertising as soon as there is no connection for some time.
This time depends on the device, but most of the time within seconds.
So don't pair but just connect if the device is advertising.

Related

Connect an already paired bluetooth headphone with .NET Core on Windows 11

I'm trying to write a small streamdeck plugin to connect my paired headphones after they have been paired to another device and haven't connected to my PC automatically. I basically want to kick off the same process as clicking Connect in the Windows 11 device list.
I'm able to enumerate my devices, find the correct device based on the device ID, and identify if it is already connected, but I'm not sure how to enable the music and chat connections if it's disconnected.
Here's basically what I have
string[] requestedProperties = {
"System.Devices.Aep.IsConnected",
};
var devices = await DeviceInformation.FindAllAsync(BluetoothDevice.GetDeviceSelector(), requestedProperties);
foreach (DeviceInformation di in devices) {
if (!di.Properties.ContainsKey("System.Devices.Aep.IsConnected")) {
continue;
}
if (di.Properties[ConnectedKey] is true) {
Debug.WriteLine($"Connected Device Name {di.Name}");
Debug.WriteLine($"Connected Device Id {di.Id}");
continue;
}
if ("<myHeadphonesBluetoothId>" == di.Id) {
// Not sure where to go at this point to initiate those connections
}
}
Been googling for hours without any success, and given some attempts at the AudioPlaybackConnection, BluetoothDevice, and RfcommDeviceService classes without success.
My big issue was a misunderstanding in the architecture involved in the way that devices, services, etc work.
I was able to get a small sample app working targeting net6.0-windows10.0.22000.0
const string targetDeviceId = "Bluetooth#Bluetooth00:00:00:00:00:00-00:00:00:0:0:00";
const int serialPortShortId = 0x1101;
using var streamSocket = new StreamSocket();
await EstablishConnection();
async ValueTask EstablishConnection() {
RfcommServiceId targetServiceId = RfcommServiceId.FromShortId(serialPortShortId);
BluetoothAdapter adapter = await BluetoothAdapter.GetDefaultAsync();
if (adapter is not null)
{
Radio btRadio = await adapter.GetRadioAsync();
if (btRadio.State == RadioState.Off)
{
await btRadio.SetStateAsync(RadioState.On);
Console.WriteLine("Bluetooth adapter was off. Turned it on.");
}
}
using BluetoothDevice btDevice = await BluetoothDevice.FromIdAsync(targetDeviceId);
btDevice.ConnectionStatusChanged += (sender, args) => Console.WriteLine("Bluetooth device connected");
RfcommDeviceServicesResult services = await btDevice.GetRfcommServicesForIdAsync(targetServiceId);
RfcommDeviceService service = services.Services.Single();
if (btDevice.ConnectionStatus != BluetoothConnectionStatus.Connected && service.DeviceAccessInformation.CurrentStatus == DeviceAccessStatus.Allowed) {
try {
await streamSocket.ConnectAsync(service.ConnectionHostName, service.ConnectionServiceName,
SocketProtectionLevel.BluetoothEncryptionAllowNullAuthentication);
}
catch (COMException e) when ((uint)e.HResult == 0x8007277C) {
Console.WriteLine("Bluetooth device is not on or in range.");
}
}
}
After running this the device would connect and windows would initialize the mic and speaker devices attached to it.

Write to Bluetooth LE Characteristic as a Server in WPF or UWP

I want to write into a Bluetooth LE Characteristic. (wpf c#, but has to work with UWP also)
I'm not exactly sure how this works, because I want to write a value not as a client, but as the server.
Like in the MS Example:
https://github.com/microsoft/Windows-universal-samples/blob/main/Samples/BluetoothLE/cs/Scenario3_ServerForeground.xaml.cs
The BLE service and characteristic are created on program start. (not in the MS example, but in my program)
After creating a Characteristic
GattLocalCharacteristicResult result = await serviceProvider.Service.CreateCharacteristicAsync(Constants.ModeCharacteristicUuid, Constants.modeParameters);
modeCharacteristic = result.Characteristic;
modeCharacteristic.WriteRequested += ModeCharacteristic_WriteRequestedAsync;
I want to use this method to write into the characteristic:
private async void ModeCharacteristic_WriteRequestedAsync(GattLocalCharacteristic sender, GattWriteRequestedEventArgs args)
{
using (args.GetDeferral())
{
GattWriteRequest request = await args.GetRequestAsync();
if (request == null)
{
// No access allowed to the device. Application should indicate this to the user.
return;
}
request.Respond();
}
}
Only question to me now is how to write in the Mode-Characteristic.
For example, I simply want to write a 5 into this Characteristic.
What code do I need?
ModeCharacteristic_WriteRequestedAsync(modeCharacteristic, 5);
doesn't work.
I don't know how to use GattWriteRequestedEventArgs args or the event handler.
Write to Bluetooth LE Characteristic as a Server in WPF or UWP
You should process write operation in GattLocalCharacteristic ReadRequested, when the client send read request, you can get GattReadRequest in above event, and then call RespondWithValue to response data that written with data writer.
private async void ResultCharacteristic_ReadRequestedAsync(GattLocalCharacteristic sender, GattReadRequestedEventArgs args)
{
// BT_Code: Process a read request.
using (args.GetDeferral())
{
// Get the request information. This requires device access before an app can access the device's request.
GattReadRequest request = await args.GetRequestAsync();
if (request == null)
{
// No access allowed to the device. Application should indicate this to the user.
rootPage.NotifyUser("Access to device not allowed", NotifyType.ErrorMessage);
return;
}
var writer = new DataWriter();
writer.ByteOrder = ByteOrder.LittleEndian;
writer.WriteInt32(resultVal);
// Can get details about the request such as the size and offset, as well as monitor the state to see if it has been completed/cancelled externally.
// request.Offset
// request.Length
// request.State
// request.StateChanged += <Handler>
// Gatt code to handle the response
request.RespondWithValue(writer.DetachBuffer());
}
}
This is the code I'm now using. It works exactly like it should. First create a characteristic, then adding a subscribedClientsChanged eventhandler.
GattLocalCharacteristicResult result = await serviceProvider.Service.CreateCharacteristicAsync(Constants.ModusCharacteristicUuid, Constants.gattOperandParameters);
if (result.Error == BluetoothError.Success)
{
modeCharacteristic = result.Characteristic;
}
else
{
return false;
}
modeCharacteristic.SubscribedClientsChanged += ResultCharacteristic_SubscribedClientsChanged;
private void ResultCharacteristic_SubscribedClientsChanged(GattLocalCharacteristic sender, object args)
{
ModeNotify(5);
}
private async void ModeNotify(int computedValue)
{
var writer = new DataWriter();
writer.ByteOrder = ByteOrder.LittleEndian;
writer.WriteInt32(computedValue);
IReadOnlyList<GattClientNotificationResult> results = await modeCharacteristic.NotifyValueAsync(writer.DetachBuffer());
}

Bluetooth LE device as proximity sensor, BluetoothLEAdvertisementWatcher cyclically stops listening for advertisements

In the way of learning BLE programming using C#, I'm trying to write a winform application that should detec BLE devices and use them as proximity sensors. For this purpose I've applied the BluetoothAdvertisementWatcher class, filtering the advertisements with a provided signal strength. The problem is the detection seems to be quite crappy... Even with the device at no distance from my PC, the listener often returns a RSSI value of -127, wich stands for "no detection" as I've understood. I've tried setting the OutOfRangeTimeout to 10 seconds and, even if things get better, problems are still heavy. The major issue is the detection can flows without interruption just for about a bunch of seconds, then the debugger signals me a thread exiting with code 0, and the listener can't receive new advertisements (with the device ever aside the pc) for even 30-40 seconds, then it restarts and so it loops.
Do you know if is this how it is intended to work or if am I missing something? Is there a way, eventually, to intercept this interruption and immediately restart the listener?
This is how I initialize the watcher:
public BLEScanner(short maxDBRange, TimeSpan outOfRangeTimeout) {
this.InRange = maxDBRange;
this.OutOfRange = (short)(this.InRange + BLEScanner.BUFFER_RANGE);
this.OutOfRangeTimeout = outOfRangeTimeout;
this.watcher = new BluetoothLEAdvertisementWatcher();
this.watcher.SignalStrengthFilter.InRangeThresholdInDBm = this.InRange;
this.watcher.SignalStrengthFilter.OutOfRangeThresholdInDBm = this.OutOfRange;
this.watcher.SignalStrengthFilter.OutOfRangeTimeout = this.OutOfRangeTimeout;
this.watcher.SignalStrengthFilter.SamplingInterval = TimeSpan.FromSeconds(1);
this.watcher.ScanningMode = BluetoothLEScanningMode.Active;
}
While this is the event handler:
private async void OnAdvertisementReceived(BluetoothLEAdvertisementWatcher watcher, BluetoothLEAdvertisementReceivedEventArgs eventArgs) {
var deviceAddress = eventArgs.BluetoothAddress;
BluetoothLEDevice device = await BluetoothLEDevice.FromBluetoothAddressAsync(deviceAddress);
UpdateUiDelegate update = new UpdateUiDelegate(
(dev, args) => {
if (eventArgs.RawSignalStrengthInDBm == -127) {
this.form.spyLabel.BackColor = System.Drawing.Color.Red;
this.form.nameLabel.Text = "(none)";
this.form.addressLabel.Text = "(none)";
this.form.rssiLabel.Text = "(none)";
this.form.connectedLabel.Text = device.ConnectionStatus.ToString();
} else {
this.form.spyLabel.BackColor = System.Drawing.Color.Green;
this.form.nameLabel.Text = device.Name;
this.form.addressLabel.Text = device.BluetoothAddress.ToString();
this.form.rssiLabel.Text = eventArgs.RawSignalStrengthInDBm.ToString();
this.form.connectedLabel.Text = device.ConnectionStatus.ToString();
}
});
this.form.Invoke(update, device, eventArgs);
Console.Write(eventArgs.RawSignalStrengthInDBm + " ");
}
Any help is appreciated!

BLE estimote eddystone background task not running

I'm trying to create a background task that will be run when the system detects an eddystone advertisement broadcasted by an estimote beacon.
I already configured the beacon to send eddystone packets and I used UniversalBeaconLibrary to get these packets while the application is in the foreground (no problems here).
Now I want to get a notification while the application is not launched (using a background task reacting to bluetooth broadcasted packets). To my understanding, to avoid putting to much strain on the battery/cpu, I do need to filter these advertisements.
One of the simplest form of filtering (the one I tried to use) is by using the company id given by the Bluetooth SIG.
Here is what I tried :
public static async void Register()
{
if (BackgroundTaskRegistration.AllTasks.Count == 0)
{
var trigger = MakeTrigger();
// this is needed for Phone, not so for Windows in this case.
var allowed = await BackgroundExecutionManager.RequestAccessAsync();
if ((allowed != BackgroundAccessStatus.Denied) &&
(allowed != BackgroundAccessStatus.Unspecified))
{
BackgroundTaskBuilder builder = new BackgroundTaskBuilder
{
Name = "BLEWatcher",
TaskEntryPoint = typeof(BLEBackgroundConsumer.Consumer).FullName
};
builder.SetTrigger(trigger);
builder.Register();
}
}
}
private static BluetoothLEAdvertisementWatcherTrigger MakeTrigger()
{
var trigger = new BluetoothLEAdvertisementWatcherTrigger();
//Can add some filters here
//trigger.AdvertisementFilter.Advertisement.ManufacturerData.Add(new BluetoothLEManufacturerData()
//{
// CompanyId = 349 //Estimote
//});
//trigger.AdvertisementFilter.Advertisement.ManufacturerData.Add(new BluetoothLEManufacturerData()
//{
// CompanyId = 76 // Apple
//});
//trigger.AdvertisementFilter.Advertisement.ManufacturerData.Add(new BluetoothLEManufacturerData()
//{
// CompanyId = 224 // Google
//});
return (trigger);
}
As it is I get an exception, saying that there is not enough or too much filtering.
When uncommenting one of the trigger blocks, I got no exception but the task does not seem to launch.
**EDIT : ** I asked estimote what was the Company Id they were boradcasting when using eddystone packets. And according to them there is none.
In regard of this answer, what would be a suitable filter ?
A Bluetooth SIG company id is only used with a manufacturer Bluetooth LE advertisement, like iBeacon and AltBeacon. Eddystone uses service Bluetooth LE advertisements, which don't contain a company id. Instead, they contain a 16-bit Service UUID of 0xFEAA.
See here for more details: https://github.com/google/eddystone/blob/master/protocol-specification.md

Pair bluetooth devices to a computer with 32feet .NET Bluetooth library

If you want to know how to use 32feet.NET library to communicate with bluetooth devices, read the solution
I am currently trying to communicate via bluetooth between a computer and a self-built .NET Gadgeteer prototype.
The Gadgeteer prototype consists of the mainboard, a power supply and a bluetooth module. The module is in discoverable mode.
On the computer a custom bluetooth program based on 32feet .NET Bluetooth is running. The program detects all bluetooth devices in range and tries to pair with them. However, this is not done automatically at the moment, I have to enter a pairing code for the device.
How can I pair devices without entering the pairing code?
Devices are found, the problem is the pairing part. I experimented a lot, but didn't find a solution...
foreach (BluetoothDeviceInfo device in this.deviceList)
{
try
{
//BluetoothClient client = new BluetoothClient(this.CreateNewEndpoint(localAddress));
//BluetoothEndPoint ep = this.CreateNewEndpoint(device.DeviceAddress);
EventHandler<BluetoothWin32AuthenticationEventArgs> handler = new EventHandler<BluetoothWin32AuthenticationEventArgs>(HandleRequests);
BluetoothWin32Authentication auth = new BluetoothWin32Authentication(handler);
BluetoothSecurity.PairRequest(device.DeviceAddress, null);
}
}
This code block initiates the pairing and it works, but Windows is asking me to enter the pairing code for the device. I read about the BluetoothWin32Authentication to prevent this case but I don't get it right.
private void HandleRequests(object that, BluetoothWin32AuthenticationEventArgs e)
{
e.Confirm = true;
}
This is the code of the event handler (http://32feet.codeplex.com/wikipage?title=BluetoothWin32Authentication)
If you simply want to allow the pairing to go ahead when to SSP devices are connecting then handling the callback and setting e.Confirm=True will be enough -- but that is a little insecure...
I am confused -.- The goal is that the application and the gadgeteer module can send data in both directions without any user interference.
Is it true that I can't pair devices automatically without user interaction?
Is it true that if two device were already paired they can exchange data without user interaction?
I figured out how to solve my problems and my knowledge about Bluetooth connections is a bit bigger now. If someone else has problems with that, I provide my solution. The code examples represent the C# implementation of a bluetooth controller with the 32feet Bluetooth library.
Scanning
This means that devices in range are detected. My code:
// mac is mac address of local bluetooth device
BluetoothEndPoint localEndpoint = new BluetoothEndPoint(mac, BluetoothService.SerialPort);
// client is used to manage connections
BluetoothClient localClient = new BluetoothClient(localEndpoint);
// component is used to manage device discovery
BluetoothComponent localComponent = new BluetoothComponent(localClient);
// async methods, can be done synchronously too
localComponent.DiscoverDevicesAsync(255, true, true, true, true, null);
localComponent.DiscoverDevicesProgress += new EventHandler<DiscoverDevicesEventArgs>(component_DiscoverDevicesProgress);
localComponent.DiscoverDevicesComplete += new EventHandler<DiscoverDevicesEventArgs>(component_DiscoverDevicesComplete);
private void component_DiscoverDevicesProgress(object sender, DiscoverDevicesEventArgs e)
{
// log and save all found devices
for (int i = 0; i < e.Devices.Length; i++)
{
if (e.Devices[i].Remembered)
{
Print(e.Devices[i].DeviceName + " (" + e.Devices[i].DeviceAddress + "): Device is known");
}
else
{
Print(e.Devices[i].DeviceName + " (" + e.Devices[i].DeviceAddress + "): Device is unknown");
}
this.deviceList.Add(e.Devices[i]);
}
}
private void component_DiscoverDevicesComplete(object sender, DiscoverDevicesEventArgs e)
{
// log some stuff
}
Pairing
This means that devices get coupled with the local bluetooth device. This needs to be done once by entering a code of both sides. Can be done via code so that the user doesn't even notice that a device was added. My code for this purpose:
// get a list of all paired devices
BluetoothDeviceInfo[] paired = localClient.DiscoverDevices(255, false, true, false, false);
// check every discovered device if it is already paired
foreach (BluetoothDeviceInfo device in this.deviceList)
{
bool isPaired = false;
for (int i = 0; i < paired.Length; i++)
{
if (device.Equals(paired[i]))
{
isPaired = true;
break;
}
}
// if the device is not paired, pair it!
if (!isPaired)
{
// replace DEVICE_PIN here, synchronous method, but fast
isPaired = BluetoothSecurity.PairRequest(device.DeviceAddress, DEVICE_PIN);
if (isPaired)
{
// now it is paired
}
else
{
// pairing failed
}
}
}
Connecting
This means establishing a connection and exchanging of data. Again some code:
// check if device is paired
if (device.Authenticated)
{
// set pin of device to connect with
localClient.SetPin(DEVICE_PIN);
// async connection method
localClient.BeginConnect(device.DeviceAddress, BluetoothService.SerialPort, new AsyncCallback(Connect), device);
}
// callback
private void Connect(IAsyncResult result)
{
if (result.IsCompleted)
{
// client is connected now :)
}
}
If you keep the order scan, pair, connect, everything should work fine. To send or receive data, use the GetStream() method of the BluetoothClient. It provides a network stream that can be manipulated.
Receiving a connection
If you want another device to connect with your device you need to listen to incoming connection requests. This only works if the device have already been paired before. My code:
BluetoothListener l = new BluetoothListener(LOCAL_MAC, BluetoothService.SerialPort);
l.Start(10);
l.BeginAcceptBluetoothClient(new AsyncCallback(AcceptConnection), l);
void AcceptConnection(IAsyncResult result){
if (result.IsCompleted){
BluetoothClient remoteDevice = ((BluetoothListener)result.AsyncState).EndAcceptBluetoothClient(result);
}
}
Replace LOCAL_MAC with a valid BluetoothAddress (e.g. by using BluetoothAddress.Parse();). After the devices are connected they can exchange messages via the underlying stream. If the connection does not work there might be authentication issues, so try setting the local device pin in the listener (l.SetPin(LOCAL_MAC, MY_PASSWORD);

Categories