Bluetooth LE plugin for Xamarin/MAUI, supporting Android, iOS, Mac, Windows
Xamarin, MAUI and MvvMCross plugin for accessing the bluetooth functionality. The plugin is loosely based on the BLE implementation of Monkey Robotics.
Important Note: With the term "vanilla" we mean the non-MvvmCross version, i.e. the pure Xamarin or MAUI plugin. You can use it without MvvmCross, if you download the vanilla package.
Platform | Version | Limitations |
---|---|---|
Xamarin.Android | 4.3 | |
Xamarin.iOS | 7.0 | |
Xamarin.Mac | 10.9 (Mavericks) | >= 2.1.0 |
Xamarin.UWP | 1709 - 10.0.16299 | >= 2.2.0 |
MAUI (Android, iOS, Mac, WinUI) | >= 3.0.0 |
package | stable | beta | downloads |
---|---|---|---|
Plugin.BLE | |||
MvvmCross.Plugin.BLE |
Vanilla
// stable
Install-Package Plugin.BLE
// or pre-release
Install-Package Plugin.BLE -Pre
MvvmCross
Install-Package MvvmCross.Plugin.BLE
// or
Install-Package MvvmCross.Plugin.BLE -Pre
Add these permissions to AndroidManifest.xml. For Marshmallow and above, please follow Requesting Runtime Permissions in Android Marshmallow and don't forget to prompt the user for the location permission.
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
Android 12 and above may require one or more of the following additional runtime permissions, depending on which features of the library you are using (see the android Bluetooth permissions documentation)
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
Add this line to your manifest if you want to declare that your app is available to BLE-capable devices only:
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>
On iOS you must add the following keys to your Info.plist
<key>UIBackgroundModes</key>
<array>
<!--for connecting to devices (client)-->
<string>bluetooth-central</string>
<!--for server configurations if needed-->
<string>bluetooth-peripheral</string>
</array>
<!--Description of the Bluetooth request message (required on iOS 10, deprecated)-->
<key>NSBluetoothPeripheralUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
<!--Description of the Bluetooth request message (required on iOS 13)-->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
On MacOS (version 11 and above) you must add the following keys to your Info.plist
:
<!--Description of the Bluetooth request message-->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>YOUR CUSTOM MESSAGE</string>
Add this line to the Package Manifest (.appxmanifest):
<DeviceCapability Name="bluetooth" />
We provide a sample Xamarin.Forms app, that is a basic bluetooth LE scanner. With this app, it's possible to
Have a look at the code and use it as starting point to learn about the plugin and play around with it.
Vanilla
var ble = CrossBluetoothLE.Current;
var adapter = CrossBluetoothLE.Current.Adapter;
MvvmCross
The MvvmCross plugin registers IBluetoothLE
and IAdapter
as lazy initialized singletons. You can resolve/inject them as any other MvvmCross service. You don't have to resolve/inject both. It depends on your use case.
var ble = Mvx.Resolve<IBluetoothLE>();
var adapter = Mvx.Resolve<IAdapter>();
or
MyViewModel(IBluetoothLE ble, IAdapter adapter)
{
this.ble = ble;
this.adapter = adapter;
}
Please make sure you have this code in your LinkerPleaseLink.cs file
public void Include(MvvmCross.Plugins.BLE.Plugin plugin)
{
plugin.Load();
}
var state = ble.State;
You can also listen for State changes. So you can react if the user turns on/off bluetooth on your smartphone.
ble.StateChanged += (s, e) =>
{
Debug.WriteLine($"The bluetooth state changed to {e.NewState}");
};
adapter.DeviceDiscovered += (s,a) => deviceList.Add(a.Device);
await adapter.StartScanningForDevicesAsync();
var scanFilterOptions = new ScanFilterOptions();
scanFilterOptions.ServiceUuids = new [] {guid1, guid2, etc}; // cross platform filter
scanFilterOptions.ManufacturerDataFilters = new [] { new ManufacturerDataFilter(1), new ManufacturerDataFilter(2) }; // android only filter
scanFilterOptions.DeviceAddresses = new [] {"80:6F:B0:43:8D:3B","80:6F:B0:25:C3:15",etc}; // android only filter
await adapter.StartScanningForDevicesAsync(scanFilterOptions);
Set adapter.ScanTimeout
to specify the maximum duration of the scan.
Set adapter.ScanMode
to specify scan mode. It must be set before calling StartScanningForDevicesAsync()
. Changing it while scanning, will not affect the current scan.
ConnectToDeviceAsync
returns a Task that finishes if the device has been connected successful. Otherwise a DeviceConnectionException
gets thrown.
try
{
await _adapter.ConnectToDeviceAsync(device);
}
catch(DeviceConnectionException e)
{
// ... could not connect to device
}
ConnectToKnownDeviceAsync
can connect to a device with a given GUID. This means that if the device GUID is known, no scan is necessary to connect to a device. This can be very useful for a fast background reconnect.
Always use a cancellation token with this method.
try
{
await _adapter.ConnectToKnownDeviceAsync(guid, cancellationToken);
}
catch(DeviceConnectionException e)
{
// ... could not connect to device
}
var services = await connectedDevice.GetServicesAsync();
or get a specific service:
var service = await connectedDevice.GetServiceAsync(Guid.Parse("ffe0ecd2-3d16-4f8d-90de-e89e7fc396a5"));
var characteristics = await service.GetCharacteristicsAsync();
or get a specific characteristic:
var characteristic = await service.GetCharacteristicAsync(Guid.Parse("d8de624e-140f-4a22-8594-e2216b84a5f2"));
var bytes = await characteristic.ReadAsync();
await characteristic.WriteAsync(bytes);
characteristic.ValueUpdated += (o, args) =>
{
var bytes = args.Characteristic.Value;
};
await characteristic.StartUpdatesAsync();
var descriptors = await characteristic.GetDescriptorsAsync();
var bytes = await descriptor.ReadAsync();
await descriptor.WriteAsync(bytes);
Returns all BLE devices connected or bonded (only Android) to the system. In order to use the device in the app you have to first call ConnectAsync.
var systemDevices = adapter.GetSystemConnectedOrPairedDevices();
foreach(var device in systemDevices)
{
await _adapter.ConnectToDeviceAsync(device);
}
The BLE API implementation (especially on Android) has the following limitations:
try
{
await _adapter.ConnectToDeviceAsync(device);
}
catch(DeviceConnectionException ex)
{
//specific
}
catch(Exception ex)
{
//generic
}
Build
Open a console, change to the folder "dotnet-bluetooth-le/.build" and run cake
.
pack the nuget
nuget pack ../Source/Plugin.BLE/Plugin.BLE.csproj
nuget pack ../Source/MvvmCross.Plugins.BLE/MvvmCross.Plugins.BLE.csproj
We usually do our development work on a branch with the name of the milestone. So please base your pull requests on the currently open development branch.