ScpControl/Bluetooth/BthDongle.cs (224 lines of code) (raw):
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.NetworkInformation;
using System.Threading;
using System.Threading.Tasks;
using ScpControl.Bluetooth.Ds3;
using ScpControl.Bluetooth.Ds4;
using ScpControl.Shared.Core;
using ScpControl.Utilities;
namespace ScpControl.Bluetooth
{
/// <summary>
/// Represents a Bluetooth host device.
/// </summary>
public sealed partial class BthDongle : ScpDevice, IBthDevice
{
#region HIDP Commands
public int HID_Command(byte[] handle, byte[] channel, byte[] data)
{
var transfered = 0;
var buffer = new byte[data.Length + 8];
buffer[0] = handle[0];
buffer[1] = handle[1];
buffer[2] = (byte) ((data.Length + 4)%256);
buffer[3] = (byte) ((data.Length + 4)/256);
buffer[4] = (byte) (data.Length%256);
buffer[5] = (byte) (data.Length/256);
buffer[6] = channel[0];
buffer[7] = channel[1];
for (var i = 0; i < data.Length; i++) buffer[i + 8] = data[i];
WriteBulkPipe(buffer, data.Length + 8, ref transfered);
return transfered;
}
#endregion
#region Overridden methods
public override string ToString()
{
switch (State)
{
case DsState.Reserved:
if (Initialised)
{
return
string.Format("Host Address : {0}\n\nHCI Version : {1}\n\nLMP Version : {2}\n\nReserved",
BluetoothHostAddress.AsFriendlyName(),
_hciVersion,
_lmpVersion
);
}
return "Host Address : <Error>";
case DsState.Connected:
if (Initialised)
{
return string.Format("Host Address : {0}\n\nHCI Version : {1}\n\nLMP Version : {2}",
BluetoothHostAddress.AsFriendlyName(),
_hciVersion,
_lmpVersion
);
}
return "Host Address : <Error>";
}
return "Host Address : Disconnected";
}
#endregion
#region Connection list
private class ConnectionList : SortedDictionary<BthHandle, BthDevice>
{
}
#endregion
#region Private fields
private CancellationTokenSource _hciCancellationTokenSource = new CancellationTokenSource();
private CancellationTokenSource _l2CapCancellationTokenSource = new CancellationTokenSource();
private string _hciVersion = string.Empty;
private byte _l2CapDataIdentifier = 0x01;
private string _lmpVersion = string.Empty;
private DsState _state = DsState.Disconnected;
private readonly ConnectionList _connected = new ConnectionList();
private readonly ManualResetEvent _connectionPendingEvent = new ManualResetEvent(true);
#endregion
#region Ctors
public BthDongle()
: base(DeviceClassGuid)
{
Initialised = false;
}
#endregion
#region Properties
public static Guid DeviceClassGuid
{
get { return Guid.Parse("{2F87C733-60E0-4355-8515-95D6978418B2}"); }
}
public PhysicalAddress BluetoothHostAddress { get; protected set; }
public string HciVersion
{
get { return _hciVersion; }
set { _hciVersion = value; }
}
public string LmpVersion
{
get { return _lmpVersion; }
set { _lmpVersion = value; }
}
public DsState State
{
get { return _state; }
}
public bool Initialised { get; private set; }
#endregion
#region Actions
public override bool Open(int instance = 0)
{
if (base.Open(instance))
{
_state = DsState.Reserved;
}
return State == DsState.Reserved;
}
public override bool Open(string devicePath)
{
if (base.Open(devicePath))
{
_state = DsState.Reserved;
}
return State == DsState.Reserved;
}
public override bool Start()
{
if (!IsActive) return State == DsState.Connected;
_state = DsState.Connected;
Task.Factory.StartNew(HicWorker, _hciCancellationTokenSource.Token);
Task.Factory.StartNew(L2CapWorker, _l2CapCancellationTokenSource.Token);
return State == DsState.Connected;
}
public override bool Stop()
{
if (!IsActive) return base.Stop();
_state = DsState.Reserved;
// notify tasks to stop work
_hciCancellationTokenSource.Cancel();
_l2CapCancellationTokenSource.Cancel();
// reset tokens
_hciCancellationTokenSource = new CancellationTokenSource();
_l2CapCancellationTokenSource = new CancellationTokenSource();
lock (_connected)
{
// disconnect all connected devices gracefully
foreach (var device in _connected.Values)
{
device.Disconnect();
device.Stop();
}
_connected.Clear();
}
return base.Stop();
}
public override bool Close()
{
var closed = base.Close();
_state = DsState.Disconnected;
return closed;
}
#endregion
#region Device management methods
private BthDevice Add(byte lsb, byte msb, string name)
{
lock (_connected)
{
BthDevice connection = null;
if (_connected.Count < 4)
{
// TODO: weak check, maybe improve in future
if (name.Equals(BthDs4.GenuineProductName, StringComparison.OrdinalIgnoreCase))
connection = new BthDs4(this, BluetoothHostAddress, lsb, msb);
else
connection = new BthDs3(this, BluetoothHostAddress, lsb, msb);
_connected[connection.HciHandle] = connection;
}
return connection;
}
}
private BthDevice GetConnection(L2CapDataPacket packet)
{
lock (_connected)
{
return (!_connected.Any() | !_connected.ContainsKey(packet.Handle)) ? null : _connected[packet.Handle];
}
}
private void Remove(byte lsb, byte msb)
{
lock (_connected)
{
var connection = new BthHandle(lsb, msb);
if (!_connected.ContainsKey(connection))
return;
_connected[connection].Stop();
_connected.Remove(connection);
}
}
#endregion
#region Events
public event EventHandler<ArrivalEventArgs> DeviceArrived;
public event EventHandler<ScpHidReport> HidReportReceived;
private bool OnDeviceArrival(IDsDevice arrived)
{
var args = new ArrivalEventArgs(arrived);
if (DeviceArrived != null)
{
DeviceArrived(this, args);
}
return args.Handled;
}
private void OnInitialised(BthDevice connection)
{
if (OnDeviceArrival(connection))
{
connection.HidReportReceived += OnHidReportReceived;
connection.Start();
}
}
private void OnCompletedCount(byte lsb, byte msb, ushort count)
{
if (count > 0) _connected[new BthHandle(lsb, msb)].Completed();
}
private void OnHidReportReceived(object sender, ScpHidReport e)
{
if (HidReportReceived != null) HidReportReceived(sender, e);
}
#endregion
}
}