#region CmdMessenger - MIT - (c) 2014 Thijs Elenbaas. /* CmdMessenger - library that provides command based messaging Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. Copyright 2014 - Thijs Elenbaas */ #endregion using System; using System.Collections.Generic; using System.Linq; using System.Threading; using InTheHand.Net; using InTheHand.Net.Bluetooth; using InTheHand.Net.Sockets; // todo: User added common PINs and per-device PINs namespace CommandMessenger.Transport.Bluetooth { /// /// Class for storing last succesful connection /// [Serializable] public class BluetoothConnectionManagerSettings { public BluetoothAddress BluetoothAddress { get; set; } public Dictionary StoredDevicePins { get; set; } public BluetoothConnectionManagerSettings() { StoredDevicePins = new Dictionary(); } } /// /// Connection manager for Bluetooth devices /// public class BluetoothConnectionManager : ConnectionManager { private static readonly List CommonDevicePins = new List { "0000", "1111", "1234", }; private enum ScanType { None, Quick, Thorough } private BluetoothConnectionManagerSettings _bluetoothConnectionManagerSettings; private readonly IBluetoothConnectionStorer _bluetoothConnectionStorer; private readonly BluetoothTransport _bluetoothTransport; private ScanType _scanType; // The control to invoke the callback on private readonly object _tryConnectionLock = new object(); private readonly List _deviceList; private List _prevDeviceList; /// /// Lookup dictionary of Pincode per device /// public Dictionary DevicePins { get; set; } /// /// List of Pincodes tried for unknown devices /// public List GeneralPins { get; set; } /// /// Connection manager for Bluetooth devices /// public BluetoothConnectionManager(BluetoothTransport bluetoothTransport, CmdMessenger cmdMessenger, int watchdogCommandId = 0, string uniqueDeviceId = null, IBluetoothConnectionStorer bluetoothConnectionStorer = null) : base(cmdMessenger, watchdogCommandId, uniqueDeviceId) { if (bluetoothTransport == null) throw new ArgumentNullException("bluetoothTransport", "Transport is null."); _bluetoothTransport = bluetoothTransport; _bluetoothConnectionManagerSettings = new BluetoothConnectionManagerSettings(); _bluetoothConnectionStorer = bluetoothConnectionStorer; PersistentSettings = (_bluetoothConnectionStorer != null); ReadSettings(); _deviceList = new List(); _prevDeviceList = new List(); DevicePins = new Dictionary(); GeneralPins = new List(); } //Try to connect using current connections settings and trigger event if succesful protected override void DoWorkConnect() { const int timeOut = 1000; var activeConnection = false; try { activeConnection = TryConnection(timeOut); } catch { // Do nothing } if (activeConnection) { ConnectionFoundEvent(); } } // Perform scan to find connected systems protected override void DoWorkScan() { if (Thread.CurrentThread.Name == null) Thread.CurrentThread.Name = "BluetoothConnectionManager"; var activeConnection = false; // Starting scan if (_scanType == ScanType.None) { _scanType = ScanType.Quick; } switch (_scanType) { case ScanType.Quick: try { activeConnection = QuickScan(); } catch { //Do nothing } _scanType = ScanType.Thorough; break; case ScanType.Thorough: try { activeConnection = ThoroughScan(); } catch { //Do nothing } _scanType = ScanType.Quick; break; } // Trigger event when a connection was made if (activeConnection) { ConnectionFoundEvent(); } } // Quick scan of available devices private void QuickScanDevices() { // Fast _prevDeviceList = _deviceList; _deviceList.Clear(); _deviceList.AddRange(_bluetoothTransport.BluetoothClient.DiscoverDevices(255, true, true, false, false)); } // Thorough scan of available devices private void ThorougScanForDevices() { // Slow _deviceList.Clear(); _deviceList.AddRange(_bluetoothTransport.BluetoothClient.DiscoverDevices(65536, true, true, true, true)); } // Pair a Bluetooth device private bool PairDevice(BluetoothDeviceInfo device) { if (device.Authenticated) return true; Log(2, "Trying to pair device " + device.DeviceName + " (" + device.DeviceAddress + ") "); // Check if PIN for this device has been injected in ConnectionManager string adress = device.DeviceAddress.ToString(); var matchedDevicePin = FindPin(adress); if (matchedDevicePin!=null) { Log(3, "Trying known key for device " + device.DeviceName); if (BluetoothSecurity.PairRequest(device.DeviceAddress, matchedDevicePin)) { Log(2, "Pairing device " + device.DeviceName + " succesful! "); return true; } // When trying PINS, you really need to wait in between Thread.Sleep(1000); } // Check if PIN has been previously found and stored if (_bluetoothConnectionManagerSettings.StoredDevicePins.ContainsKey(device.DeviceAddress)) { Log(3, "Trying stored key for device " + device.DeviceName ); if (BluetoothSecurity.PairRequest(device.DeviceAddress, _bluetoothConnectionManagerSettings.StoredDevicePins[device.DeviceAddress])) { Log(2, "Pairing device " + device.DeviceName + " succesful! "); return true; } // When trying PINS, you really need to wait in between Thread.Sleep(1000); } // loop through general pins PIN numbers that have been injected to see if they pair foreach (string devicePin in GeneralPins) { Log(3, "Trying known general pin " + devicePin + " for device " + device.DeviceName); var isPaired = BluetoothSecurity.PairRequest(device.DeviceAddress, devicePin); if (isPaired) { _bluetoothConnectionManagerSettings.StoredDevicePins[device.DeviceAddress] = devicePin; Log(2, "Pairing device " + device.DeviceName + " succesful! "); return true; } // When trying PINS, you really need to wait in between Thread.Sleep(1000); } // loop through common PIN numbers to see if they pair foreach (string devicePin in CommonDevicePins) { Log(3, "Trying common pin " + devicePin + " for device " + device.DeviceName); var isPaired = BluetoothSecurity.PairRequest(device.DeviceAddress, devicePin); if (isPaired) { _bluetoothConnectionManagerSettings.StoredDevicePins[device.DeviceAddress] = devicePin; StoreSettings(); Log(2, "Pairing device " + device.DeviceName + " succesful! "); return true; } // When trying PINS, you really need to wait in between Thread.Sleep(1000); } Log(2, "Pairing device " + device.DeviceName + " unsuccesfull "); return true; } // Find the pin code for a Bluetooth adress private string FindPin(string adress) { return (from devicePin in DevicePins where BluetoothUtils.StripBluetoothAdress(devicePin.Key) == adress select devicePin.Value).FirstOrDefault(); } private bool TryConnection(BluetoothAddress bluetoothAddress, int timeOut) { if (bluetoothAddress == null) return false; // Find return (from bluetoothDeviceInfo in _deviceList where bluetoothDeviceInfo.DeviceAddress == bluetoothAddress select TryConnection(bluetoothDeviceInfo, timeOut)).FirstOrDefault(); } private bool TryConnection(BluetoothDeviceInfo bluetoothDeviceInfo, int timeOut) { // Try specific settings _bluetoothTransport.CurrentBluetoothDeviceInfo = bluetoothDeviceInfo; return TryConnection(timeOut); } private bool TryConnection(int timeOut) { lock (_tryConnectionLock) { // Check if an (old) connection exists if (_bluetoothTransport.CurrentBluetoothDeviceInfo == null) return false; Connected = false; Log(1, @"Trying Bluetooth device " + _bluetoothTransport.CurrentBluetoothDeviceInfo.DeviceName); if (_bluetoothTransport.Connect()) { Log(3, @"Connected with Bluetooth device " + _bluetoothTransport.CurrentBluetoothDeviceInfo.DeviceName + ", requesting response"); DeviceStatus status = ArduinoAvailable(timeOut, 5); Connected = (status == DeviceStatus.Available); if (Connected) { Log(1, "Connected with Bluetooth device " + _bluetoothTransport.CurrentBluetoothDeviceInfo.DeviceName); StoreSettings(); } else { Log(3, @"Connected with Bluetooth device " + _bluetoothTransport.CurrentBluetoothDeviceInfo.DeviceName + ", received no response"); } return Connected; } else { Log(3, @"No connection made with Bluetooth device " + _bluetoothTransport.CurrentBluetoothDeviceInfo.DeviceName ); } return false; } } protected override void StartScan() { base.StartScan(); if (ConnectionManagerMode == Mode.Scan) { _scanType = ScanType.None; } } private bool QuickScan() { Log(3, "Performing quick scan"); const int longTimeOut = 1000; const int shortTimeOut = 1000; // First try if currentConnection is open or can be opened if (TryConnection(longTimeOut)) return true; // Do a quick rescan of all devices in range QuickScanDevices(); if (PersistentSettings) { // Then try if last stored connection can be opened Log(3, "Trying last stored connection"); if (TryConnection(_bluetoothConnectionManagerSettings.BluetoothAddress, longTimeOut)) return true; } // Then see if new devices have been added to the list if (NewDevicesScan()) return true; foreach (var device in _deviceList) { Thread.Sleep(100); // Bluetooth devices seem to work more reliably with some waits Log(1, "Trying Device " + device.DeviceName + " (" + device.DeviceAddress + ") " ); if (TryConnection(device, shortTimeOut)) return true; } return false; } private bool ThoroughScan() { Log(3, "Performing thorough scan"); const int longTimeOut = 1000; const int shortTimeOut = 1000; // First try if currentConnection is open or can be opened if (TryConnection(longTimeOut)) return true; // Do a quick rescan of all devices in range ThorougScanForDevices(); // Then try if last stored connection can be opened Log(3, "Trying last stored connection"); if (TryConnection(_bluetoothConnectionManagerSettings.BluetoothAddress, longTimeOut)) return true; // Then see if new devices have been added to the list if (NewDevicesScan()) return true; foreach (var device in _deviceList) { Thread.Sleep(100); // Bluetooth devices seem to work more reliably with some waits if (PairDevice(device)) { Log(1, "Trying Device " + device.DeviceName + " (" + device.DeviceAddress + ") "); if (TryConnection(device, shortTimeOut)) return true; } } return false; } private bool NewDevicesScan() { const int shortTimeOut = 200; // Then see if port list has changed var newDevices = NewDevicesInList(); if (newDevices.Count == 0) { return false; } Log(1, "Trying new devices"); foreach (var device in newDevices) { if (TryConnection(device, shortTimeOut)) return true; Thread.Sleep(100); } return false; } private List NewDevicesInList() { return (from device in _deviceList from prevdevice in _prevDeviceList where device.DeviceAddress != prevdevice.DeviceAddress select device).ToList(); } protected override void StoreSettings() { if (!PersistentSettings) return; _bluetoothConnectionManagerSettings.BluetoothAddress = _bluetoothTransport.CurrentBluetoothDeviceInfo.DeviceAddress; _bluetoothConnectionStorer.StoreSettings(_bluetoothConnectionManagerSettings); } protected override sealed void ReadSettings() { if (!PersistentSettings) return; _bluetoothConnectionManagerSettings = _bluetoothConnectionStorer.RetrieveSettings(); } } }