Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blue radios compatibility #22

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ public Characteristic (CBCharacteristic nativeCharacteristic, CBPeripheral paren
}

public bool CanRead {get{return (this.Properties & CharacteristicPropertyType.Read) != 0; }}
public bool CanUpdate {get{return (this.Properties & CharacteristicPropertyType.Notify) != 0; }}
public bool CanUpdate { get { return (this.Properties & (CharacteristicPropertyType.Notify | CharacteristicPropertyType.Indicate)) != 0; } }
public bool CanWrite {get{return (this.Properties & (CharacteristicPropertyType.WriteWithoutResponse | CharacteristicPropertyType.AppleWriteWithoutResponse)) != 0; }}

public Task<ICharacteristic> ReadAsync()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Threading.Tasks;
using Robotics.Mobile.Core.Bluetooth.LE;
using System.Threading;
using System.IO;
using Robotics.Mobile.Core.Bluetooth.LE.Interfaces;
using Robotics.Mobile.Core.Bluetooth.LE.LeTypeIds;
using System.Diagnostics;

namespace Robotics.Mobile.Core.Bluetooth.LE {

/// <summary>Wrap the Bluetooth LE read write characteristics in a stream</summary>
public class BluetoothLEStream : Stream {

#region Data

const string CLASS = "BluetoothLEStream";

private readonly Task initTask;
private readonly IDevice device;
private IService service;
private ICharacteristic receive;
private ICharacteristic transmit;
private ICharacteristic mode;

const int ReadBufferSize = 64 * 1024;
readonly List<byte> readBuffer = new List<byte>(ReadBufferSize * 2);
readonly AutoResetEvent dataReceived = new AutoResetEvent(false);

IBluetoothLE_TypeSet idTypes = null;

byte[] modeValue = new byte[] { 1 }; // Data mode, 2= command mode

#endregion

/// <summary>Alternate event you can use to listen for incoming reads</summary>
public event Action<byte[]> OnRead;

#region Properties

public IDevice CurrentDevice {
get {
return this.device;
}
}

#endregion

#region Constructors

public BluetoothLEStream(IDevice device, IBluetoothLE_TypeSet idTypes) {
this.device = device;
this.idTypes = idTypes;
this.initTask = InitializeAsync();
}

#endregion

public void CancelRead() {
this.receive.StopUpdates();
this.receive.ValueUpdated -= this.HandleReceiveValueUpdated;
this.dataReceived.Set();
}

#region implemented abstract members of Stream

public override int Read(byte[] buffer, int offset, int count) {
Task<int> t = ReadAsync(buffer, offset, count, CancellationToken.None);
t.Wait();
return t.Result;
}

public override async Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) {
await initTask;
while (!cancellationToken.IsCancellationRequested) {
lock (this.readBuffer) {
if (this.readBuffer.Count > 0) {
int n = Math.Min(count, readBuffer.Count);
this.readBuffer.CopyTo(0, buffer, offset, n);
this.readBuffer.RemoveRange(0, n);
return n;
}
}
await Task.Run(() => this.dataReceived.WaitOne());
}
return 0;
}

public override void Write(byte[] buffer, int offset, int count) {
WriteAsync(buffer, offset, count).Wait();
}

public override async Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) {
if (count > 20) {
throw new ArgumentOutOfRangeException("count", "This function is limited to buffers of 20 bytes and less.");
}

await initTask;

var b = buffer;
if (offset != 0 || count != b.Length) {
b = new byte[count];
Array.Copy(buffer, offset, b, 0, count);
}

// Write the data
transmit.Write(b);

// Throttle
await Task.Delay(TimeSpan.FromMilliseconds(b.Length)); // 1 ms/byte is slow but reliable
}

public override void Flush() {
}

public override long Seek(long offset, SeekOrigin origin) {
throw new NotSupportedException();
}
public override void SetLength(long value) {
throw new NotSupportedException();
}
public override bool CanRead {
get {
return true;
}
}
public override bool CanSeek {
get {
return false;
}
}
public override bool CanWrite {
get {
return true;
}
}
public override long Length {
get {
return 0;
}
}
public override long Position {
get {
return 0;
}
set {
}
}
#endregion

#region Private methods

private async Task InitializeAsync() {
Debug.WriteLine("LEStream: Looking for service " + this.idTypes.GetId(BluetoothLE_IdType.Service) + "...");
this.service = await device.GetServiceAsync(this.idTypes.GetId(BluetoothLE_IdType.Service));
Debug.WriteLine("LEStream: Got service: " + service.ID);

Debug.WriteLine("LEStream: Getting characteristics...");
this.receive = await service.GetCharacteristicAsync(this.idTypes.GetId(BluetoothLE_IdType.ReceiveCharacteristic));
this.transmit = await service.GetCharacteristicAsync(this.idTypes.GetId(BluetoothLE_IdType.TransmitCharacteristic));
this.mode = await this.service.GetCharacteristicAsync(this.idTypes.GetId(BluetoothLE_IdType.ModeCharacteristic));
Debug.WriteLine("LEStream: Got characteristics");

this.SetMode();

// Set the receive thread going
this.receive.ValueUpdated += this.HandleReceiveValueUpdated;
this.receive.StartUpdates();
}


/// <summary>Set Bluetooth Serial Port Mode On</summary>
/// <remarks>TODO - modify to pass in data or command mode enums</remarks>
private void SetMode() {
this.mode.Write(this.modeValue);
}


private void HandleReceiveValueUpdated(object sender, CharacteristicReadEventArgs e) {
//WrapErr.ToErrReport(1234, "Failed to receive value", () => {
byte[] bytes = e.Characteristic.Value;
if (bytes == null || bytes.Length == 0) {
return;
}

// Instead of an async wait on ReadAsync you can just subscribe to the
this.RaiseMessage(e.Characteristic.Value);

// Original way of returning data via the ReadAsync
//Log.Info(CLASS, "HandleReceiveValueUpdated", () => string.Format("Receive.Value: {0} - {1}",
// ByteTools.ToPrintableBytes(bytes), ByteTools.ToPrintableString(bytes)));
lock (this.readBuffer) {
if (this.readBuffer.Count + bytes.Length > ReadBufferSize) {
this.readBuffer.RemoveRange(0, ReadBufferSize / 2);
}
this.readBuffer.AddRange(bytes);
}
this.dataReceived.Set();
//});
}



#endregion


private void RaiseMessage(byte[] message) {
if (this.OnRead != null) {
Task.Factory.StartNew(() => {
try {
this.OnRead(message);
}
catch (Exception e) {
Debug.WriteLine(e.Message);
}
});
}
else {
Debug.WriteLine("No subscribers to OnRead");
}
}


}



}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using Robotics.Mobile.Core.Bluetooth.LE.LeTypeIds;
using System;

namespace Robotics.Mobile.Core.Bluetooth.LE.Interfaces {

/// <summary>Set of Bluetooth LE Ids for stream</summary>
public interface IBluetoothLE_TypeSet {

/// <summary>Get the LE Guid by type</summary>
/// <param name="type">The guid type for service or characteristic</param>
/// <returns>The id</returns>
Guid GetId(BluetoothLE_IdType type);

/// <summary>Query if the id exists</summary>
/// <param name="type">The guid type for service or characteristic</param>
/// <returns>true if found, otherwise false</returns>
bool HasId(BluetoothLE_IdType type);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,18 @@ public LEStream (IDevice device)
initTask = InitializeAsync ();
}

/// <summary>Allow user to break out of an async read</summary>
/// <remarks>
/// Using the cancelation token alone will not break if the user is
/// currently waiting on the dataReceived event
/// </remarks>
public void CancelRead() {
this.receive.StopUpdates();
this.receive.ValueUpdated -= this.HandleReceiveValueUpdated;
this.dataReceived.Set();
}


async Task InitializeAsync ()
{
Debug.WriteLine ("LEStream: Looking for service " + ServiceId + "...");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using Robotics.Mobile.Core.Bluetooth.LE.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Robotics.Mobile.Core.Bluetooth.LE.LeTypeIds {

/// <summary>Guids for the BlueRadio Bluetooth board</summary>
public class BlueRadioLE_Ids : IBluetoothLE_TypeSet {

#region Data

//flipped the transmit and receive from this reference
//https://github.com/RHWorkshop/IR-Blue-iPhone/blob/master/IR-Blue%20Version%201.0.11/IR-Blue/IR-Blue/RHViewController.m
private Guid serviceId = new Guid("DA2B84F1-6279-48DE-BDC0-AFBEA0226079");
private Guid transmitCharacteristicId = new Guid("BF03260C-7205-4C25-AF43-93B1C299D159");
private Guid receiveCharacteristicId = new Guid("18CDA784-4BD3-4370-85BB-BFED91EC86AF");
private Guid modeCharacteristicId = new Guid("A87988B9-694C-479C-900E-95DFA6C00A24");
private List<BluetoothLE_Id> ids = new List<BluetoothLE_Id>();

#endregion

#region Constructors

public BlueRadioLE_Ids() {
this.ids.Add(new BluetoothLE_Id(BluetoothLE_IdType.Service, this.serviceId));
this.ids.Add(new BluetoothLE_Id(BluetoothLE_IdType.ModeCharacteristic, this.modeCharacteristicId));
this.ids.Add(new BluetoothLE_Id(BluetoothLE_IdType.ReceiveCharacteristic, this.receiveCharacteristicId));
this.ids.Add(new BluetoothLE_Id(BluetoothLE_IdType.TransmitCharacteristic, this.transmitCharacteristicId));
}

#endregion

#region Methods

/// <summary>Get the LE Guid by type</summary>
/// <param name="type">The guid type for service or characteristic</param>
/// <returns>The id</returns>
public Guid GetId(BluetoothLE_IdType type) {
if (!this.HasId(type)) {
throw new Exception(string.Format("Type id {0} does not exist for BlueRadio", type));
}
return this.ids.FirstOrDefault(x => x.IdType == type).Id;
}


/// <summary>Query if the id exists</summary>
/// <param name="type">The guid type for service or characteristic</param>
/// <returns>true if found, otherwise false</returns>
public bool HasId(BluetoothLE_IdType type) {
return this.ids.Exists(x => x.IdType == type);
}

#endregion

}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;

namespace Robotics.Mobile.Core.Bluetooth.LE.LeTypeIds {

/// <summary>Releate a Bluetooth LE services or Characteristics Guid to an enum</summary>
public class BluetoothLE_Id {

/// <summary>Enum idetifier of id type</summary>
public BluetoothLE_IdType IdType { get; set; }

/// <summary>The Guid</summary>
public Guid Id { get; set; }

/// <summary>Constructor</summary>
/// <param name="idType">Enum type identifier</param>
/// <param name="id">Guid id</param>
public BluetoothLE_Id(BluetoothLE_IdType idType, Guid id) {
this.IdType = idType;
this.Id = id;
}

}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

namespace Robotics.Mobile.Core.Bluetooth.LE.LeTypeIds {

/// <summary>Identify types of Ids to initialize Bluetooth LE connection</summary>
public enum BluetoothLE_IdType {

/// <summary>Service Id</summary>
Service,

/// <summary>Transmit characteristic id</summary>
TransmitCharacteristic,

/// <summary>Receive characteristic id</summary>
ReceiveCharacteristic,

/// <summary>Mode characteristic id</summary>
ModeCharacteristic,

}

}