Skip to content
This repository has been archived by the owner on Jun 19, 2024. It is now read-only.

Refactor Android implementation #44

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -21,29 +21,8 @@ public ServiceList (IAdapter adapter, IDevice device)
this.device = device;
this.services = new ObservableCollection<IService> ();
listView.ItemsSource = services;

// when device is connected
adapter.DeviceConnected += (s, e) => {
device = e.Device; // do we need to overwrite this?

// when services are discovered
device.ServicesDiscovered += (object se, EventArgs ea) => {
Debug.WriteLine("device.ServicesDiscovered");
//services = (List<IService>)device.Services;
if (services.Count == 0)
Device.BeginInvokeOnMainThread(() => {
foreach (var service in device.Services) {
services.Add(service);
}
});
};
// start looking for services
device.DiscoverServices ();

};
// TODO: add to IAdapter first
//adapter.DeviceFailedToConnect += (sender, else) => {};

adapter.DeviceConnected += this.OnDeviceConnected;
adapter.DeviceDisconnected += this.OnDeviceDisconnected;
DisconnectButton.Activated += (sender, e) => {
adapter.DisconnectDevice (device);
Navigation.PopToRootAsync(); // disconnect means start over
Expand Down Expand Up @@ -71,6 +50,44 @@ public void OnItemSelected (object sender, SelectedItemChangedEventArgs e) {

((ListView)sender).SelectedItem = null; // clear selection
}

public void OnDeviceConnected(object sender, DeviceConnectionEventArgs args)
{
if (args.Device == this.device) {
this.adapter.DeviceConnected -= this.OnDeviceConnected;
this.device.ServicesDiscovered += this.ServicesDiscovered;
this.device.DiscoverServices ();
}
}

public void OnDeviceDisconnected(object sender, DeviceConnectionEventArgs args)
{
if (args.Device == this.device) {
this.adapter.DeviceDisconnected -= this.OnDeviceDisconnected;
// For robustness reason, its possible to be disconnected from device
// right after adding the OnConnected delgate in constructor.
// We do not want DeviceConnected delegate become a hidden bomb.
this.adapter.DeviceConnected -= this.OnDeviceConnected;
// For robustness reason, its possible we go from device
// connected to device disconnected without going through
// service discovered. We do not want the service discover
// delegate become a hidden bomb.
this.device.ServicesDiscovered -= this.ServicesDiscovered;
}
}

public void ServicesDiscovered(object sender, EventArgs args)
{
this.device.ServicesDiscovered -= this.ServicesDiscovered;
Debug.WriteLine("device.ServicesDiscovered");
if (services.Count == 0) {
Device.BeginInvokeOnMainThread (() => {
foreach (var service in device.Services) {
services.Add (service);
}
});
}
}
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using Android.Bluetooth;
using System.Threading.Tasks;
using Java.Util;

namespace Robotics.Mobile.Core.Bluetooth.LE
{
Expand All @@ -19,7 +20,6 @@ public class Adapter : Java.Lang.Object, BluetoothAdapter.ILeScanCallback, IAdap
// class members
protected BluetoothManager _manager;
protected BluetoothAdapter _adapter;
protected GattCallback _gattCallback;

public bool IsScanning {
get { return this._isScanning; }
Expand All @@ -44,38 +44,30 @@ public Adapter ()
// get a reference to the bluetooth system service
this._manager = (BluetoothManager) appContext.GetSystemService("bluetooth");
this._adapter = this._manager.Adapter;

this._gattCallback = new GattCallback (this);

this._gattCallback.DeviceConnected += (object sender, DeviceConnectionEventArgs e) => {
this._connectedDevices.Add ( e.Device);
this.DeviceConnected (this, e);
};

this._gattCallback.DeviceDisconnected += (object sender, DeviceConnectionEventArgs e) => {
// TODO: remove the disconnected device from the _connectedDevices list
// i don't think this will actually work, because i'm created a new underlying device here.
//if(this._connectedDevices.Contains(
this.DeviceDisconnected (this, e);
};
}

//TODO: scan for specific service type eg. HeartRateMonitor
public async void StartScanningForDevices (Guid serviceUuid)
{
StartScanningForDevices ();
// throw new NotImplementedException ("Not implemented on Android yet, look at _adapter.StartLeScan() overload");
}
public async void StartScanningForDevices ()
public async void StartScanningForDevices ()
{
this.StartScanningForDevices(Guid.Empty);
}

public async void StartScanningForDevices (Guid serviceUuid)
{
Console.WriteLine ("Adapter: Starting a scan for devices.");

// clear out the list
this._discoveredDevices = new List<IDevice> ();

UUID[] serviceUuids = null;
if (serviceUuid != Guid.Empty)
{
serviceUuids = new UUID[1];
serviceUuids[0] = UUID.FromString(serviceUuid.ToString());
}

// start scanning
this._isScanning = true;
this._adapter.StartLeScan (this);
this._adapter.StartLeScan (serviceUuids, this);

// in 10 seconds, stop the scan
await Task.Delay (10000);
Expand Down Expand Up @@ -125,9 +117,31 @@ protected bool DeviceExistsInDiscoveredList(BluetoothDevice device)

public void ConnectToDevice (IDevice device)
{
// returns the BluetoothGatt, which is the API for BLE stuff
// TERRIBLE API design on the part of google here.
((BluetoothDevice)device.NativeDevice).ConnectGatt (Android.App.Application.Context, true, this._gattCallback);
var androidBleDevice = (Device)device;
if (androidBleDevice._gatt == null) {
var gattCallback = new GattCallback (androidBleDevice);
gattCallback.DeviceConnected += (object sender, DeviceConnectionEventArgs e) => {
this._connectedDevices.Add (e.Device);
this.DeviceConnected (this, e);
};

gattCallback.DeviceDisconnected += (object sender, DeviceConnectionEventArgs e) => {
this._connectedDevices.Remove (e.Device);
this.DeviceDisconnected (this, e);
};

androidBleDevice.GattCallback = gattCallback;
androidBleDevice._gatt = ((BluetoothDevice)device.NativeDevice).ConnectGatt (Android.App.Application.Context, false, gattCallback);
var success = androidBleDevice._gatt.Connect ();
Console.WriteLine(string.Format("Initial connection attempt is {0}", success));
} else {
switch (androidBleDevice.State) {
case DeviceState.Disconnected:
androidBleDevice.Disconnect ();
this.ConnectToDevice (androidBleDevice);
break;
}
}
}

public void DisconnectDevice (IDevice device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,20 @@ namespace Robotics.Mobile.Core.Bluetooth.LE
public class Characteristic : ICharacteristic
{
public event EventHandler<CharacteristicReadEventArgs> ValueUpdated = delegate {};

public event EventHandler<CharacteristicWrittenEventArgs> ValueWritten = delegate {};

protected BluetoothGattCharacteristic _nativeCharacteristic;
/// <summary>
/// we have to keep a reference to this because Android's api is weird and requires
/// the GattServer in order to do nearly anything, including enumerating services
/// </summary>
protected BluetoothGatt _gatt;
/// <summary>
/// we also track this because of gogole's weird API. the gatt callback is where
/// we'll get notified when services are enumerated
/// </summary>
protected GattCallback _gattCallback;


public Characteristic (BluetoothGattCharacteristic nativeCharacteristic, BluetoothGatt gatt, GattCallback gattCallback)
protected Device _device;


public Characteristic (BluetoothGattCharacteristic nativeCharacteristic, Device device)
{
this._nativeCharacteristic = nativeCharacteristic;
this._gatt = gatt;
this._gattCallback = gattCallback;
this._device = device;

if (this._gattCallback != null) {
if (this._device.GattCallback != null) {
// wire up the characteristic value updating on the gattcallback
this._gattCallback.CharacteristicValueUpdated += (object sender, CharacteristicReadEventArgs e) => {
this._device.GattCallback.CharacteristicValueUpdated += (object sender, CharacteristicReadEventArgs e) => {
// it may be other characteristics, so we need to test
if(e.Characteristic.ID == this.ID) {
// update our underlying characteristic (this one will have a value)
Expand Down Expand Up @@ -104,7 +94,6 @@ public object NativeCharacteristic {
//NOTE: why this requires Apple, we have no idea. BLE stands for Mystery.
public bool CanWrite {get{return (this.Properties & CharacteristicPropertyType.WriteWithoutResponse | CharacteristicPropertyType.AppleWriteWithoutResponse) != 0; }}

// HACK: UNTESTED - this API has only been tested on iOS
public void Write (byte[] data)
{
if (!CanWrite) {
Expand All @@ -113,11 +102,16 @@ public void Write (byte[] data)

var c = _nativeCharacteristic;
c.SetValue (data);
this._gatt.WriteCharacteristic (c);
this._device.GattCallback.CharacteristicValueWritten += this.OnWritten;
this._device._gatt.WriteCharacteristic (c);
Console.WriteLine(".....Write");
}


public void OnWritten(object sender, CharacteristicWrittenEventArgs args)
{
this._device.GattCallback.CharacteristicValueWritten -= this.OnWritten;
this.ValueWritten (sender, args);
}

// HACK: UNTESTED - this API has only been tested on iOS
public Task<ICharacteristic> ReadAsync()
Expand All @@ -132,20 +126,20 @@ public Task<ICharacteristic> ReadAsync()
// it may be other characteristics, so we need to test
var c = e.Characteristic;
tcs.SetResult(c);
if (this._gattCallback != null) {
if (this._device.GattCallback != null) {
// wire up the characteristic value updating on the gattcallback
this._gattCallback.CharacteristicValueUpdated -= updated;
this._device.GattCallback.CharacteristicValueUpdated -= updated;
}
};


if (this._gattCallback != null) {
if (this._device.GattCallback != null) {
// wire up the characteristic value updating on the gattcallback
this._gattCallback.CharacteristicValueUpdated += updated;
this._device.GattCallback.CharacteristicValueUpdated += updated;
}

Console.WriteLine(".....ReadAsync");
this._gatt.ReadCharacteristic (this._nativeCharacteristic);
this._device._gatt.ReadCharacteristic (this._nativeCharacteristic);

return tcs.Task;
}
Expand All @@ -156,12 +150,12 @@ public void StartUpdates ()
bool successful = false;
if (CanRead) {
Console.WriteLine ("Characteristic.RequestValue, PropertyType = Read, requesting updates");
successful = this._gatt.ReadCharacteristic (this._nativeCharacteristic);
successful = this._device._gatt.ReadCharacteristic (this._nativeCharacteristic);
}
if (CanUpdate) {
Console.WriteLine ("Characteristic.RequestValue, PropertyType = Notify, requesting updates");

successful = this._gatt.SetCharacteristicNotification (this._nativeCharacteristic, true);
successful = this._device._gatt.SetCharacteristicNotification (this._nativeCharacteristic, true);

// [TO20131211@1634] It seems that setting the notification above isn't enough. You have to set the NOTIFY
// descriptor as well, otherwise the receiver will never get the updates. I just grabbed the first (and only)
Expand All @@ -177,7 +171,7 @@ public void StartUpdates ()
if (_nativeCharacteristic.Descriptors.Count > 0) {
BluetoothGattDescriptor descriptor = _nativeCharacteristic.Descriptors [0];
descriptor.SetValue (BluetoothGattDescriptor.EnableNotificationValue.ToArray ());
_gatt.WriteDescriptor (descriptor);
this._device._gatt.WriteDescriptor (descriptor);
} else {
Console.WriteLine ("RequestValue, FAILED: _nativeCharacteristic.Descriptors was empty, not sure why");
}
Expand All @@ -190,7 +184,7 @@ public void StopUpdates ()
{
bool successful = false;
if (CanUpdate) {
successful = this._gatt.SetCharacteristicNotification (this._nativeCharacteristic, false);
successful = this._device._gatt.SetCharacteristicNotification (this._nativeCharacteristic, false);
//TODO: determine whether
Console.WriteLine ("Characteristic.RequestValue, PropertyType = Notify, STOP updates");
}
Expand Down
Loading