xamarin / Essentials

Xamarin.Essentials is no longer supported. Migrate your apps to .NET MAUI, which includes Maui.Essentials.
https://aka.ms/xamarin-upgrade
Other
1.52k stars 505 forks source link

Add Bluetooth/BLE API #242

Closed craylward closed 1 year ago

craylward commented 6 years ago

@redth here, editing this issue with an API spec proposal:

public static partial class BluetoothLe
{
    public static AdapterState AdapterState {get;}
    public static EventHandler<DeviceDiscoveredEventArgs> DeviceDiscovered;
    public static Task StartDiscoveringAsync(TimeSpan discoveryTimeout, params string[] serviceGuids);
    public static Task StartDiscoveringAsync(CancellationToken token, params string[] serviceGuids);
    public static Task StopDiscoveringAsync();

    public static Task<IList<Device>> DiscoverAsync(TimeSpan discoveryTimeout, params string[] serviceGuids);

    public static async Task ConnectDeviceAsync(Device device);

    public static void DisconnectDevice(Device device);
}

public class Device
{
    public string Id { get; }

    public string Name { get; }

    public DeviceState State { get; }

    public int RssiDecibels { get; }

    public List<Service> Services { get; }

    public event EventHandler<ServiceDiscoveredEventArgs> ServiceDiscovered;
    public Task StartDiscoveringServicesAsync ();
    public Task StopDiscoveringServicesAsync ();

    public Task UpdateServicesAsync ();
}

public enum DeviceState
{
    Disconnected,
    Connecting,
    Connected
}

public class Service
{
    public string Id { get; }

    public string Name { get; }

    public List<Characteristics> Characteristics { get; }

    public event EventHandler<CharacteristicDiscoveredEventArgs> CharacteristicDiscovered;
    public Task StartDiscoveringCharacteristicsAsync ();
    public Task StopDiscoveringCharacteristicsAsync ();

    public Task UpdateCharacteristicsAsync ();
}

public class Characteristic
{
    public string Id { get; }

    public string Uuid { get; }

    public byte[] Value { get; }

    public CharacteristicPropertyType PropertyType { get; }

    public bool CanRead { get; }
    public bool CanUpdate { get; }
    public bool CanWrite { get; }

    public List<Descriptor> Descriptors { get; }

    public Task WriteAsync (byte[] data);
    public Task UpdateAsync ();

    public event EventHandler<CharactersticValueChangedEventArgs> ValueChanged;
    public Task StartMonitoringAsync ();
    public Task StopMonitoringAsync ();
}

public class Descriptor
{
    public string Id { get; }

    public string Name { get; }
}

[Flags]
public enum CharacteristicPropertyType
{
    Broadcast = 1, //0x1
    Read = 2, //0x2
    AppleWriteWithoutResponse = 4, //0x4
    WriteWithoutResponse = 8, //0x8
    Notify = 16, //0x10
    Indicate = 32, //0x20
    AuthenticatedSignedWrites = 64, //0x40
    ExtendedProperties = 128, //0x80
    NotifyEncryptionRequired = 256, //0x100
    IndicateEncryptionRequired = 512, //0x200
}

VS bug #735670

mattleibow commented 6 years ago

This is something we should try and do, as it is a very popular plugin. But... Wasn't there already a very good library that we support: https://github.com/xamarin/Monkey.BluetoothLE

This may be better served there.

craylward commented 6 years ago

This issue is addressed in #77 . Changed issue title to generic BLE API to extend beyond capabilities. Can be closed if it is determined that BLE belongs in a separate plugin.

erdmenchen commented 6 years ago

I tried various cross plattform wrappers for BLE in Xamarin Forms. All have their issues and they never work out of the box. You always have to "hack" workarounds. Maintenance is poor or not available at all since there is no large adoption if the issues are related to core BLE functionality.

So it would be a very big improvement for such a core feature on mobile devices if Xamarin.Essentials could provide an easy to use and out of the box working wrapper for BLE.

So my proposal: Include permission / feature check but also the whole BLE stack...

basdecort commented 6 years ago

With the rising popularity of IoT this would be a great addition. Unfortunately it's pretty challenging to implement this in a cross-platform manner, especially on Android. We are using this plugin: https://github.com/xabre/xamarin-bluetooth-le

As mentioned on the GitHub page, they are looking for maintainers.

nicolgit commented 6 years ago

I am using this https://github.com/aritchie/bluetoothle , quite stable and maintained (at least on android implementation the one I am using)

jamesmontemagno commented 6 years ago

@erdmenchen @basdecort @nicolgit can you please add comments on to what features you would like to see this API have and what features you actually need.

nicolgit commented 6 years ago

BLE devices interaction

basdecort commented 6 years ago

The features we would need for our current use are:

A more detailed description on services & characteristics can be found: https://www.bluetooth.com/specifications/gatt/generic-attributes-overview

I've been working on a app that uses BLE for more than a year, if I can be of any assistance please let me know.

wjvii commented 6 years ago

Add support for Bluetooth Beacons. There is some good work out there already with some recent commits although it has been a year since the last official release. Maybe there is some chance to incorporate that existing work into Xamarin Essentials?

https://github.com/andijakl/universal-beacon https://github.com/SwiftArchitect/universal-beacon

Redth commented 5 years ago

I think what we need here is for someone with this domain specific knowledge to come up with an API for a minimum viable product. We often think of the 80/20 rule with Essentials (what is the most minimal functionality which covers 80% of users).

I'd like to see an API proposed here to address the 80% here.

brandwooddixon commented 5 years ago

Whatever API is proposed should allow for un-adopted/custom service and characteristic UUIDs. This will allow for developers to implement support for BLE enabled devices that use non-standard services and characteristics whilst allowing for newly adopted services and characteristics to be supported without waiting for an update to Essentials. Yes cross-platform BLE support is difficult, but isn't that what Essentials is for? With the explosion of low-powered IoT devices that can't use wireless I'm surprised that this wasn't included from the outset.

Further to some of the points posted. Scan for device should allow for scan all devices and for devices implementing a specific service UUID or collection of service UUIDs (which would match a profile).

Redth commented 5 years ago

The question is always, is this a big enough implementation / API to warrant a separate library (we've discussed the potential of introducing things like Xamarin.Bluetooth as a separate nuget package if necessary in the future).

Again, would love some suggested API specs from someone more familiar with bluetooth and BLE. Admittedly I'm not too knowledgeable in this area (should we only consider BLE for now?).

There's certainly a few folks in the thread who seem to have enough knowledge to work on a proposal :)

Ulf-Ason commented 5 years ago

When we are creating apps for the industry, we need to get data from Classic Bluetooth (RFCOMM). Find the device Set up a connection Read/write serial data async

VirtualNomad00 commented 5 years ago

IS there any update on if and when we might get this . Current bluetooth functionalities( both classic and BLE ) are a lot of hassle to implement with a lot of platform specific tweaks. Example I have used BluetoothLE plugin . It's great but had to experiment a lot to figure out how things work and the there's always some issue with iOS like not able to get the adapter status in the first go or the notification subscriber stops working after getting the first notification etc .

maexsp commented 5 years ago

Same here. Waiting for Bluetooth! Creating industry apps with Xamarin essentially requires reliable Bluetooth support (LE and classic). Bluetooth is the only practical technology to wireless communicate with industry devices nowadays when implementing Multiplatform Apps. Scanning nearby devices, connect, checking capabilities, communicate (even if hidden mode), disconnect, etc. for Android, iOS and UWP/Win10. Thats a major functionality to implement Multiplatform Apps which can communicate with our industry devices. It seems that Cordova plugins and Flutter plugins already in place. Why Xamarin far behind with supporting Bluetooth? Please support Bluetooth discovery and connections in Xamarin soon. How is the official state about that regarding the Xamarin roadmap?

ElVinche commented 5 years ago

Hello, Have you a solution for the bluetooth and BLE detection and management ? Thanks

Redth commented 5 years ago

Original issue was updated with a proposal for the API spec. Discussion welcome.

mavaa commented 5 years ago

Just a note coming from https://github.com/xabre/xamarin-bluetooth-le:

During connection, we needed to implement some platform-specific code for Bonding a device on the Android side. iOS does the bonding automatically if needed when connecting. This is the code we used when connecting:

bool shouldBond = device is IBLEValveDevice; // We have another type of device which does not require bonding
if (shouldBond && !this.blePlatformSpecific.IsBonded(device.Device))
{
    await this.blePlatformSpecific.Bond(device.Device);
}

if (!shouldBond || this.blePlatformSpecific.IsBonded(device.Device))
{
    var cts = new CancellationTokenSource();
    cts.CancelAfter(TimeSpan.FromSeconds(10));

    await this.Adapter.ConnectToDeviceAsync(device.Device, default(ConnectParameters), cts.Token);
}
// ... Error handling

The important part is the "blePlatformSpecific" interface. On iOS IsBonded simply always returns true. On Android, this is our implementation:

public bool IsBonded(IDevice bleDevice)
{
    if (bleDevice.NativeDevice is BluetoothDevice nativeDevice)
    {
        return nativeDevice.BondState == Android.Bluetooth.Bond.Bonded;
    }

    return false;
}

public Task Bond(IDevice bleDevice)
{
    var tcs = new TaskCompletionSource<bool>();
    if (bleDevice.NativeDevice is BluetoothDevice nativeDevice)
    {
        MainActivity.CurrentActivity.RegisterReceiver(new BondingChangeReceiver(nativeDevice, () =>
        {
            tcs.SetResult(true);
        }), new IntentFilter(BluetoothDevice.ActionBondStateChanged));

        nativeDevice.CreateBond();
        return tcs.Task;
    }

    throw new ArgumentException("Could not find a BLE Native device", nameof(bleDevice));
}

Would be nice if this could be handled as part of ConnectDeviceAsync, or maybe there should be some separate calls in the API for bonding. Unless there's a use case where you explicitly want to connect without bonding I imagine this should just happen automatically?

newky2k commented 5 years ago

@marza91 I agree that this is something we should be handling automatically.

If i may, how do you handle reconnecting to a device that was previously connected(in between launches of the application for example). I was thinking that the current API could also provide a Connect method with the device Id only, and the underlying platform implementations can then resolve this into a 'Device' object.

mavaa commented 5 years ago

In my app, I use a settings plugin for storing the uuids of previously connected devices (https://github.com/jamesmontemagno/SettingsPlugin/).

Then when I do a scan, I have a boolean for autoConnect, and if it's true I check every discovered device against my known device list, and if there's a match I immediately stop scanning and try to connect to that device.

I also have a hacky way of connecting by device ID: In the app, I show the user a list of known devices. If they click one of them, I basically do the same as in the above scenario, but will compare only the ID of the selected device. I also have a much shorter scanning timeout (10 seconds), since the device will usually be discovered by then anyways. For normal scans, my timeout is 60 seconds.

CliffAgius commented 4 years ago

I am looking to use BT on a project and was wondering if this is currently being worked on? If not I am happy to jump in and get at least a basic PR together for finding devices and connecting to them.

For the very basic stuff that is listed above we should be able to get most working again with 80/20 rule and then drop back and add things like Beacons etc.

This has been open for a very long time and @jamesmontemagno seems to be playing Wack-A-Mole with other Issues being opened for the same request.

So has anyone grabbed this?

newky2k commented 4 years ago

@CliffAgius we are working on that ATM, and should have something soon.

Thanks

CliffAgius commented 4 years ago

@newky2k thanks. If you need any help like someone to test or anything let me know I am using lots of BLE IOT devices at the moment so plenty of test equipment around me.

erdmenchen commented 4 years ago

@Redth feedback for the current proposal at the initial comment:

When this is implemented, it will be a lot easier to use BLE functionality in Xamarin.Forms! I very much appreciate your work!

Edit: Also consider BLE 5 with new mesh concept, that would maybe require a complete differen API.

CARP-Kaiser commented 4 years ago

@newky2k awesome. Pretty excited to try it out. Is there a ballpark date you had in mind? I have a use case coming up pretty soon that would definitely fall under the 80.

CTLandry commented 4 years ago

This is very exciting. Like a lot of others we are currently using https://github.com/xabre/xamarin-bluetooth-le but would be quite pleased to use something officially supported by Xamarin.Essentials.

veletron2 commented 4 years ago

Ditto, would love to see built in X-Platform LE/Classic support in Xam.essentials. I am also using xabre BLE plugin, Some issues, particularly related to re-connections on Android when the device comes back into range. Also handling the variety of disconnect scenareo's intended, non-intended and the async nature in which these can occur. My peripheral is a medical device, based on Nordic NRF52832. Would be nice to see some better docs as well eg info on wind-down of connections and characteristic subscriptions depending on whether user initiated disconnection (eg at central), vs 'unintended' disconnection at peripheral due to out of range/etc. There are also oddities iOS vs Android re chosen connection criteria (connection interval actually chosen), as well as the implementations of DLE (data length extension) on Android vs iOS. These poss intrinsic to the respective OS, but would be great to see these pulled together in a X-platform 'recommendation' for the peripheral, rather than the current guesswork. Also, Calls to IDevice.UpdateConnectionInterval also fail to make a blind bit of difference currently.

ske66 commented 4 years ago

Just a note coming from https://github.com/xabre/xamarin-bluetooth-le:

During connection, we needed to implement some platform-specific code for Bonding a device on the Android side. iOS does the bonding automatically if needed when connecting. This is the code we used when connecting:

bool shouldBond = device is IBLEValveDevice; // We have another type of device which does not require bonding
if (shouldBond && !this.blePlatformSpecific.IsBonded(device.Device))
{
    await this.blePlatformSpecific.Bond(device.Device);
}

if (!shouldBond || this.blePlatformSpecific.IsBonded(device.Device))
{
    var cts = new CancellationTokenSource();
    cts.CancelAfter(TimeSpan.FromSeconds(10));

    await this.Adapter.ConnectToDeviceAsync(device.Device, default(ConnectParameters), cts.Token);
}
// ... Error handling

The important part is the "blePlatformSpecific" interface. On iOS IsBonded simply always returns true. On Android, this is our implementation:

public bool IsBonded(IDevice bleDevice)
{
    if (bleDevice.NativeDevice is BluetoothDevice nativeDevice)
    {
        return nativeDevice.BondState == Android.Bluetooth.Bond.Bonded;
    }

    return false;
}

public Task Bond(IDevice bleDevice)
{
    var tcs = new TaskCompletionSource<bool>();
    if (bleDevice.NativeDevice is BluetoothDevice nativeDevice)
    {
        MainActivity.CurrentActivity.RegisterReceiver(new BondingChangeReceiver(nativeDevice, () =>
        {
            tcs.SetResult(true);
        }), new IntentFilter(BluetoothDevice.ActionBondStateChanged));

        nativeDevice.CreateBond();
        return tcs.Task;
    }

    throw new ArgumentException("Could not find a BLE Native device", nameof(bleDevice));
}

Would be nice if this could be handled as part of ConnectDeviceAsync, or maybe there should be some separate calls in the API for bonding. Unless there's a use case where you explicitly want to connect without bonding I imagine this should just happen automatically?

Would you be able to share this interface and class somewhere? Bonding seems to be a difficult thing to master with Xabre's library and I have been struggling to find a good answer

CliffAgius commented 4 years ago

@jamesmontemagno is this actually being worked on? It's been proposed since 2018 and talked about a few times and I'm wondering if it's just been left.

Most use cases it's just connect and send send data so maybe start with an MVP to get the spec built out and then add the Beacons and Bonding etc later,

gresolio commented 4 years ago

I want to mention BluetoothLE Plugin by Allan Ritchie https://github.com/aritchie/bluetoothle Very stable, used in our production app for a long time (iOS & Android), no issues.

It has reactive API, which I really like, but it probably does not suit everyone. Currently Allan is developing Shiny framework (much more functionality, not only BLE) but I see it a little bit bloated.

If BLE is all you need, IMHO, BluetoothLE Plugin is better choice for Xamarin than Xabre's library (it works worse for our tasks and requires more troubleshooting).

It would be great, if BluetoothLE Plugin was taken as inspiration for developing a new Bluetooth API for Xamarin.Essentials.

CliffAgius commented 4 years ago

Yes I have tried both Allan's Plugin and the Xabre on projects and they are both good and work well but it would be great to see it as part of Essentials as it should be in the box after all they say they have all the API's covered and BT is a pretty big hole in that statement.

mavaa commented 4 years ago

@ske66 This should be everything you need in the interface (In your Xamarin.Forms project):

namespace MyProject.Interfaces
{
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Plugin.BLE.Abstractions.Contracts;

    public interface IBLEPlatformSpecific
    {
        Task Bond(IDevice bleDevice);

        bool IsBonded(IDevice bleDevice);
    }
}

And the Xamarin.Android project:

[assembly: Xamarin.Forms.Dependency(typeof(MyProject.Droid.Util.BLEUtil))]
namespace MyProject.Droid.Util
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading.Tasks;
    using Android.Bluetooth;
    using Android.Content;
    using Android.Locations;
    using Interfaces; // Xamarin.Forms interface namespace
    using Plugin.BLE.Abstractions.Contracts;

    public class BLEUtil : IBLEPlatformSpecific
    {
        public Task Bond(IDevice bleDevice)
        {
            var tcs = new TaskCompletionSource<bool>();
            if (bleDevice.NativeDevice is BluetoothDevice nativeDevice)
            {
                MainActivity.CurrentActivity.RegisterReceiver(new BondingChangeReceiver(nativeDevice, () =>
                {
                    tcs.SetResult(true);
                }), new IntentFilter(BluetoothDevice.ActionBondStateChanged));

                nativeDevice.CreateBond();
                return tcs.Task;
            }

            throw new ArgumentException("Could not find a BLE Native device", nameof(bleDevice));
        }

        public bool IsBonded(IDevice bleDevice)
        {
            if (bleDevice.NativeDevice is BluetoothDevice nativeDevice)
            {
                return nativeDevice.BondState == Android.Bluetooth.Bond.Bonded;
            }

            return false;
        }
    }

    [BroadcastReceiver(Enabled = false, Exported = false)]
    public class BondingChangeReceiver : BroadcastReceiver
    {
        private readonly BluetoothDevice device;
        private readonly Action bondingChangedAction;

        public BondingChangeReceiver() { }
        public BondingChangeReceiver(BluetoothDevice device, Action bondingChangedAction) : base()
        {
            this.device = device;
            this.bondingChangedAction = bondingChangedAction;
        }

        public override void OnReceive(Context context, Intent intent)
        {
            if(intent.Action == BluetoothDevice.ActionBondStateChanged && 
                intent.GetParcelableExtra(BluetoothDevice.ExtraDevice) is BluetoothDevice intentDevice)
            {
                if ((Bond)intent.GetIntExtra(BluetoothDevice.ExtraBondState, -1) != Bond.Bonding && intentDevice.Address == device.Address)
                {
                    MainActivity.CurrentActivity.UnregisterReceiver(this);
                    bondingChangedAction();
                }
            }
        }
    }
}

(You might be able to remove some of those using statements)

The IDevice interface comes from the plugin, it's the one you get from the Device Discovered event. To use this in the .Forms project, you use DependencyService.Get() (See https://docs.microsoft.com/en-us/xamarin/xamarin-forms/app-fundamentals/dependency-service/introduction for details).

If you need an iOS version, you can just return true in IsBonded() and an empty completed task in Bond(), as Apple should handle the bonding automatically

I had to remove some customer-specific stuff from the code, but I won't be able to test if it compiles or not... Some of this miiight have changed since I used it last, but it's only about a year old, so it might still work :slightly_smiling_face:

Hope that helps?

I just saw the comments about BluetoothLE, so you might be better off trying that out. I remember this still being a bit unstable if you're doing a lot of bonding/unbonding, but that might just be due to us doing things we shouldn't have while testing :sweat_smile:

ske66 commented 4 years ago

@mavaa Really helpful!

CartBlanche commented 3 years ago

@Redth, @mattleibow @jamesmontemagno Has any progress been made in making this API a reality?

jamesmontemagno commented 3 years ago

Not in Essentials, I still think that BLE is better served with one of the existing established libraries. Shiny or Plugin.BLE, which I have used and are great. Maybe long term in .NET MAUI Essentials though.

jfversluis commented 1 year ago

I think at this point it's safe to say that this won't be making it's way into Xamarin.Essentials anymore. Keep your eye on .NET MAUI for all new development! Thanks for all input and efforts here.

Happypig375 commented 1 year ago

@jfversluis If you actually cared about the issue instead of closing everything blindly, you would have searched for the equivalent issue in .NET MAUI https://github.com/dotnet/maui/issues/778 and its resolution is

We have no current plans for this, but do check out Shiny which handles BLE and will be supporting MAUI in the future! https://github.com/shinyorg/shiny