ScpControl/Bluetooth/BthDevice.cs (205 lines of code) (raw):

using System; using System.Net.NetworkInformation; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Threading.Tasks; using ScpControl.ScpCore; using ScpControl.Shared.Core; using ScpControl.Sound; using ScpControl.Utilities; namespace ScpControl.Bluetooth { /// <summary> /// Represents a generic Bluetooth client device. /// </summary> public class BthDevice : BthConnection, IDsDevice { #region Private fields private readonly IObservable<long> _outputReportSchedule = Observable.Interval(TimeSpan.FromMilliseconds(10), Scheduler.Default); private IDisposable _outputReportTask; private readonly TaskQueue _inputReportQueue = new TaskQueue(); #endregion #region Protected fields protected bool m_Blocked, m_IsIdle = true, m_IsDisconnect; protected byte m_CableStatus = 0; protected readonly IBthDevice BluetoothDevice; protected byte m_Init = 0; protected DateTime m_Last = DateTime.Now, m_Idle = DateTime.Now, m_Tick = DateTime.Now, m_Disconnect = DateTime.Now; protected uint m_Packet; protected byte m_PlugStatus = 0; private bool m_Publish; protected uint m_Queued = 0; #endregion #region Public properties public DsState State { get; protected set; } public DsConnection Connection { get { return DsConnection.Bluetooth; } } public DsBattery Battery { get; protected set; } public PhysicalAddress HostAddress { get; private set; } public virtual DsPadId PadId { get; set; } public uint? XInputSlot { get; set; } #endregion #region Public methods public ScpHidReport NewHidReport() { return new ScpHidReport { PadId = PadId, PadState = State, ConnectionType = Connection, Model = Model, PadMacAddress = DeviceAddress, BatteryStatus = (byte) Battery }; } public virtual bool Start() { _outputReportTask = _outputReportSchedule.Subscribe(tick => OnTimer()); // play connection sound if (GlobalConfiguration.Instance.IsBluetoothConnectSoundEnabled) AudioPlayer.Instance.PlayCustomFile(GlobalConfiguration.Instance.BluetoothConnectSoundFile); return State == DsState.Connected; } public virtual bool Rumble(byte large, byte small) { return false; } public virtual bool Pair(PhysicalAddress master) { return false; } public virtual bool Disconnect() { m_Publish = false; return BluetoothDevice.HCI_Disconnect(HciHandle) > 0; } public void Stop() { if (State == DsState.Connected) { if (_outputReportTask != null) _outputReportTask.Dispose(); State = GlobalConfiguration.Instance.ReservePadSlot ? DsState.Reserved : DsState.Disconnected; m_Packet = 0; m_Publish = false; OnHidReportReceived(NewHidReport()); // play disconnect sound if (GlobalConfiguration.Instance.IsBluetoothDisconnectSoundEnabled) AudioPlayer.Instance.PlayCustomFile(GlobalConfiguration.Instance.BluetoothDisconnectSoundFile); } } public virtual bool Close() { Stop(); if (State == DsState.Reserved) { State = DsState.Disconnected; m_Packet = 0; m_Publish = false; OnHidReportReceived(NewHidReport()); } return State == DsState.Disconnected; } public virtual void ParseHidReport(byte[] report) { } public virtual bool InitHidReport(byte[] report) { return true; } public override string ToString() { switch (State) { case DsState.Disconnected: return string.Format("Pad {0} : Disconnected", PadId); case DsState.Reserved: return string.Format("Pad {0} : {1} {2} - Reserved", PadId, Model, DeviceAddress.AsFriendlyName()); case DsState.Connected: return string.Format("Pad {0} : {1} {2} - {3} {4:X8} {5}", PadId, Model, DeviceAddress.AsFriendlyName(), Connection, m_Packet, Battery ); } throw new Exception(); } public virtual void Completed() { lock (this) { m_Blocked = false; } } #endregion #region Events public event EventHandler<ScpHidReport> HidReportReceived; protected void OnHidReportReceived(ScpHidReport report) { if (GlobalConfiguration.Instance.UseAsyncHidReportProcessing) { _inputReportQueue.Enqueue(() => Task.Run(() => { if (HidReportReceived != null) HidReportReceived.Invoke(this, report); })); } else { if (HidReportReceived != null) HidReportReceived.Invoke(this, report); } } #endregion #region Protected methods protected virtual void Process(DateTime now) { } private void OnTimer() { if (State != DsState.Connected) return; #region Calculate and trigger idle auto-disconnect var now = DateTime.Now; if (m_IsIdle && GlobalConfiguration.Instance.IdleDisconnect) { if ((now - m_Idle).TotalMilliseconds >= GlobalConfiguration.Instance.IdleTimeout) { Log.InfoFormat("Pad {0} disconnected due to idle timeout", PadId); m_IsDisconnect = false; m_IsIdle = false; Disconnect(); return; } } else if (m_IsDisconnect) { if ((now - m_Disconnect).TotalMilliseconds >= 2000) { Log.InfoFormat("Pad {0} disconnected due to quick disconnect combo", PadId); m_IsDisconnect = false; m_IsIdle = false; Disconnect(); return; } } #endregion Process(now); } #endregion #region Ctors protected BthDevice() { DeviceAddress = PhysicalAddress.None; HostAddress = PhysicalAddress.None; } protected BthDevice(IBthDevice device, PhysicalAddress master, byte lsb, byte msb) : base(new BthHandle(lsb, msb)) { BluetoothDevice = device; HostAddress = master; } #endregion } }