Skip to content

Commit

Permalink
refactoring API and other fixes and improvements towards a 1.0 release
Browse files Browse the repository at this point in the history
  • Loading branch information
nexussays committed Nov 17, 2017
1 parent 8df92f1 commit 42101b4
Show file tree
Hide file tree
Showing 47 changed files with 2,913 additions and 932 deletions.
7 changes: 0 additions & 7 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
copyright: Malachi Griffie
contact : @nexussays
website : http://nexussays.com
source : https://github.com/nexussays/ble.net

---

Mozilla Public License, version 2.0

1. Definitions
Expand Down
38 changes: 19 additions & 19 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,24 @@

# ble.net ![Build status](https://img.shields.io/vso/build/nexussays/ebc6aafa-2931-41dc-b030-7f1eff5a28e5/7.svg?style=flat-square) [![NuGet](https://img.shields.io/nuget/v/ble.net.svg?style=flat-square)](https://www.nuget.org/packages/ble.net) [![MPLv2 License](https://img.shields.io/badge/license-MPLv2-blue.svg?style=flat-square)](https://www.mozilla.org/MPL/2.0/) [![API docs](https://img.shields.io/badge/apidocs-DotNetApis-blue.svg?style=flat-square)](http://dotnetapis.com/pkg/ble.net)

`ble.net` is a Bluetooth Low Energy (aka BLE, aka Bluetooth LE, aka Bluetooth Smart) PCL to enable simple development of BLE clients on Android, iOS, and Windows 10.
`ble.net` is a Bluetooth Low Energy (aka BLE, aka Bluetooth LE, aka Bluetooth Smart) cross-platform library to enable simple development of BLE clients on Android, iOS, and UWP/Windows.

It provides a consistent API across all platforms and hides many of the horrible API decisions of each respective platform. You can make multiple simultaneous BLE requests on Android without worrying that some calls will silently fail; you can simply `await` all your calls without dealing with some kludgy disjoint event-based system; if you know which characteristics and services you wish to interact with then you don't have to query down into the attribute heirarchy and retain instance variables for these characteristics and services.
It provides a consistent API across all supported platforms and hides many of the poor API decisions of each respective platform.

> Note: Currently UWP only supports listening for broadcasts/advertisements, connecting to devices is incredibly obtuse in the UWP APIs.
For example, you can make multiple simultaneous BLE requests on Android without worrying that some calls will silently fail. You can simply `await` all your calls without dealing with the book-keeping of an `event`-based system. If you know which characteristics and services you wish to interact with, then you can just read/write to them without having to query down into the device's attribute heirarchy and retain references to these characteristics and services. And so on...

> Note: Currently UWP only supports listening for broadcasts/advertisements, not connecting... the UWP BLE API is... proving difficult.
## Setup

### 1. Install NuGet packages

Install `ble.net` in your PCL
Install the `ble.net.api` package in your (PCL/NetStandard) shared library
```powershell
Install-Package ble.net
Install-Package ble.net.api
```

In each platform project, install the relevant package:
For each platform you are supporting, install the relevant package:

```powershell
Install-Package ble.net-android
Expand All @@ -31,7 +33,7 @@ Install-Package ble.net-uwp

### 2. Obtain a reference to `BluetoothLowEnergyAdapter`

Each platform project has a static method `BluetoothLowEnergyAdapter.ObtainDefaultAdapter()`. Obtain this reference and then provide it to your application code using whatever dependency injector or manual reference passing you are using in your project.
Each platform project has a static method `BluetoothLowEnergyAdapter.ObtainDefaultAdapter()` with various overloads. Obtain this reference and then provide it to your application code using your dependency injector, or manual reference passing, or a singleton, or whatever strategy you are using in your project.

Examples:
* [Android Xamarin.Forms](src/ble.net.sampleapp-android/MyApplication.cs#L98)
Expand All @@ -40,19 +42,17 @@ Examples:

#### Android-specific setup

If you want the adapter enable/disable functions to work, in your main `Activity`:
If you want `IBluetoothLowEnergyAdapter.State.DisableAdapter()` and `EnableAdapter()` to work, in your main `Activity`:
```csharp
protected override void OnCreate( Bundle bundle )
{
// ...
BluetoothLowEnergyAdapter.InitActivity( this );

// ...
}
```

If you want `IBluetoothLowEnergyAdapter.OnStateChanged` to work, in your calling `Activity`:
If you want `IBluetoothLowEnergyAdapter.State.Subscribe()` to work, in your calling `Activity`:
```csharp
protected sealed override void OnActivityResult( Int32 requestCode, Result resultCode, Intent data )
{
Expand Down Expand Up @@ -92,10 +92,13 @@ await adapter.ScanForBroadcasts(

You can also use a scan filter which will ensure that your callback only receives peripherals that pass the filter.

For the common case of ignoring duplicate advertisements (i.e., repeated advertisements from the same device), there is a static `ScanFilter.UniqueBroadcastsOnly` you can use as the scan filter.

Or write your own custom filter:
```csharp
// create the filter using an object initalizer...
await adapter.ScanForBroadcasts(
new ScanFilter.Factory()
new ScanFilter()
{
AdvertisedDeviceName = "foo",
AdvertisedManufacturerCompanyId = 76,
Expand All @@ -105,22 +108,20 @@ await adapter.ScanForBroadcasts(
p => { /* do stuff with found peripheral */ } );
// ...or create the filter using a fluent builder pattern
await adapter.ScanForBroadcasts(
new ScanFilter.Factory()
new ScanFilter()
.SetAdvertisedDeviceName( "foo" )
.SetAdvertisedManufacturerCompanyId( 76 )
.AddAdvertisedService( guid )
.SetIgnoreRepeatBroadcasts( true ),
p => { /* do stuff with found peripheral */ } );
```

For the common case of ignoring duplicate advertisements (i.e., repeated advertisements from the same device), there is a static `ScanFilter.UniqueBroadcastsOnly` you can use as the scan filter.

### Connect to a BLE device

```csharp
// If the connection isn't established before CancellationToken or timeout is triggered, it will be stopped.
var connection = await adapter.ConnectToDevice( peripheral, TimeSpan.FromSeconds( 15 ));
if(connection.IsSuccessful())
if(connection.IsSuccessful()) // syntax sugar for: connection.ConnectionResult == ConnectionResult.Success
{
var gattServer = connection.GattServer;
// do things with gattServer here... (see further examples...)
Expand Down Expand Up @@ -207,8 +208,7 @@ catch(GattException ex)
Debug.WriteLine( ex.ToString() );
}

// ... later ...
// done listening for notifications
// ... later, once done listening for notifications ...
notifier.Dispose();
```

Expand All @@ -232,7 +232,7 @@ catch(GattException ex)

### Do a bunch of things

> If you've used the native BLE APIs on Android or iOS, imagine the code you would have to write to achieve the same functionality as the following example.
> (If you've used the native BLE APIs on Android or iOS, imagine the code you would have to write to achieve the same functionality as the following example.)
```csharp
try
Expand Down
51 changes: 51 additions & 0 deletions src/ble.net/BleDeviceConnection.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright M. Griffie <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System;
using System.ComponentModel;

namespace nexus.protocols.ble
{
/// <summary>
/// The results of a connection attempt to a remote BLE device
/// </summary>
public struct BleDeviceConnection
{
/// <summary>
/// </summary>
public BleDeviceConnection( ConnectionResult connectionResult, IBleGattServerConnection gattServer )
{
ConnectionResult = connectionResult;
GattServer = gattServer;
}

/// <summary>
/// The result of the connection attempt to a BLE device
/// </summary>
public ConnectionResult ConnectionResult { get; }

/// <summary>
/// The remote GATT server or null, if the connection was unsuccessful
/// </summary>
public IBleGattServerConnection GattServer { get; }
}

/// <summary>
/// Extension methods for <see cref="BleDeviceConnection" />
/// </summary>
[EditorBrowsable( EditorBrowsableState.Never )]
public static class BleDeviceConnectionExtensions
{
/// <summary>
/// True if this <see cref="BleDeviceConnection" /> resulted in <see cref="ConnectionResult.Success" />
/// <remarks>Syntax sugar for <c>device.ConnectionResult == ConnectionResult.Success</c></remarks>
/// </summary>
public static Boolean IsSuccessful( this BleDeviceConnection device )
{
return device.ConnectionResult == ConnectionResult.Success;
}
}
}
114 changes: 7 additions & 107 deletions src/ble.net/BluetoothLowEnergyUtils.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
// Copyright Malachi Griffie
// Copyright M. Griffie <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.IO;
using nexus.core;
using nexus.protocols.ble.advertisement;
using nexus.protocols.ble.connection;
using nexus.core.resharper;

namespace nexus.protocols.ble
{
Expand Down Expand Up @@ -89,39 +84,6 @@ public static Guid AddressToGuid( Byte[] address )
} );
}

/// <summary>
/// Return true if <see cref="CharacteristicProperty.Indicate" /> is set on this characteristic
/// </summary>
public static Boolean CanIndicate( this CharacteristicProperty properties )
{
return (properties & CharacteristicProperty.Indicate) != 0;
}

/// <summary>
/// Return true if <see cref="CharacteristicProperty.Notify" /> is set on this characteristic
/// </summary>
public static Boolean CanNotify( this CharacteristicProperty properties )
{
return (properties & CharacteristicProperty.Notify) != 0;
}

/// <summary>
/// Return true if <see cref="CharacteristicProperty.Read" /> is set on this characteristic
/// </summary>
public static Boolean CanRead( this CharacteristicProperty properties )
{
return (properties & CharacteristicProperty.Read) != 0;
}

/// <summary>
/// Return true if <see cref="CharacteristicProperty.Write" /> or <see cref="CharacteristicProperty.WriteNoResponse" /> are
/// set on this characteristic
/// </summary>
public static Boolean CanWrite( this CharacteristicProperty properties )
{
return (properties & (CharacteristicProperty.Write | CharacteristicProperty.WriteNoResponse)) != 0;
}

/// <summary>
/// Create a <see cref="Guid" /> from a Bluetooth Special Interest Group adopted key
/// </summary>
Expand All @@ -146,10 +108,12 @@ public static Guid CreateGuidFromAdoptedKey( this UInt16 adoptedKey )
/// <exception cref="ArgumentNullException">If the provided value is null</exception>
/// <exception cref="ArgumentException">If the provided value is not 4 characters in length</exception>
/// <exception cref="FormatException">If the provided value cannot be parsed to a Guid</exception>
public static Guid CreateGuidFromAdoptedKey( this String adoptedKey )
public static Guid CreateGuidFromAdoptedKey( [NotNull] this String adoptedKey )
{
Contract.Requires<ArgumentNullException>( adoptedKey != null );
Debug.Assert( adoptedKey != null, "adoptedKey != null" );
if(adoptedKey == null)
{
throw new ArgumentNullException( nameof(adoptedKey) );
}
if(adoptedKey.Length != 4)
{
throw new ArgumentException(
Expand Down Expand Up @@ -180,69 +144,5 @@ public static Boolean IsReservedKey( this Guid id )
bytes[3] = 0;
return s_adoptedKeyBase.Equals( id );
}

/// <summary>
/// True if this <see cref="BleDeviceConnection" /> resulted in <see cref="ConnectionResult.Success" />
/// </summary>
public static Boolean IsSuccessful( this BleDeviceConnection device )
{
return device.ConnectionResult == ConnectionResult.Success;
}

/// <summary>
/// Listen for NOTIFY events on this characteristic.
/// </summary>
public static IDisposable NotifyCharacteristicValue( this IBleGattServer server, Guid service,
Guid characteristic, Action<Tuple<Guid, Byte[]>> onNotify,
Action<Exception> onError = null )
{
Contract.Requires<ArgumentNullException>( server != null );
// ReSharper disable once PossibleNullReferenceException
return server.NotifyCharacteristicValue( service, characteristic, Observer.Create( onNotify, null, onError ) );
}

/// <summary>
/// Listen for NOTIFY events on this characteristic.
/// </summary>
public static IDisposable NotifyCharacteristicValue( this IBleGattServer server, Guid service,
Guid characteristic, Action<Byte[]> onNotify,
Action<Exception> onError = null )
{
Contract.Requires<ArgumentNullException>( server != null );
// ReSharper disable once PossibleNullReferenceException
return server.NotifyCharacteristicValue(
service,
characteristic,
Observer.Create( ( Tuple<Guid, Byte[]> tuple ) => onNotify( tuple.Item2 ), null, onError ) );
}

/// <summary>
/// Parse <c>advD</c> payload data from advertising packet. You should never need to call this from client code, platform
/// libraries should handle it.
/// </summary>
public static IEnumerable<AdvertisingDataItem> ParseAdvertisingPayloadData( Byte[] advD )
{
var records = new List<AdvertisingDataItem>();
var index = 0;
while(index < advD?.Length)
{
var length = advD[index];
index++;
if(length > 0)
{
if(!(advD.Length > index + length))
{
throw new InvalidDataException(
"Advertising data specifies length {0} but only has {1} bytes remaining"
.F( length, advD.Length ) );
}
var type = advD[index];
var data = advD.Slice( index + 1, index + length );
index += length;
records.Add( new AdvertisingDataItem( (AdvertisingDataType)type, data ) );
}
}
return records;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright Malachi Griffie
// Copyright M. Griffie <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace nexus.protocols.ble.connection
namespace nexus.protocols.ble
{
/// <summary>
/// The progress of a remote connection attempt
Expand All @@ -14,7 +14,7 @@ public enum ConnectionProgress
/// <summary>
/// SearchingForDevice
/// </summary>
SearchingForDevice = -1,
SearchingForDevice = -2,
/// <summary>
/// Disconnected
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright Malachi Griffie
// Copyright M. Griffie <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace nexus.protocols.ble.connection
namespace nexus.protocols.ble
{
/// <summary>
/// The result of a remote connection attempt
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// Copyright Malachi Griffie
// Copyright M. Griffie <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

namespace nexus.protocols.ble.connection
namespace nexus.protocols.ble
{
/// <summary>
/// The state of a remote connection
Expand Down
4 changes: 2 additions & 2 deletions src/ble.net/EnabledDisabledState.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright Malachi Griffie
// Copyright M. Griffie <[email protected]>
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
Expand Down Expand Up @@ -28,7 +28,7 @@ public enum EnabledDisabledState
/// </summary>
Enabling,
/// <summary>
/// The entity is aeabled and ready for use
/// The entity is enabled and ready for use
/// </summary>
Enabled
}
Expand Down
Loading

0 comments on commit 42101b4

Please sign in to comment.