using System; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using Swan; namespace Unosquare.RaspberryIO.Computer { /// /// Represents the Bluetooth information. /// public class Bluetooth : SingletonBase { private const String BcCommand = "bluetoothctl"; /// /// Turns on the Bluetooth adapter. /// /// The cancellation token. /// /// Returns true or false depending if the controller was turned on. /// /// Failed to power on:. public async Task PowerOn(CancellationToken cancellationToken = default) { try { String output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power on", null, cancellationToken).ConfigureAwait(false); return output.Contains("succeeded"); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to power on: {ex.Message}"); } } /// /// Turns off the bluetooth adapter. /// /// The cancellation token. /// /// Returns true or false depending if the controller was turned off. /// /// Failed to power off:. public async Task PowerOff(CancellationToken cancellationToken = default) { try { String output = await ProcessRunner.GetProcessOutputAsync(BcCommand, "power off", null, cancellationToken).ConfigureAwait(false); return output.Contains("succeeded"); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to power off: {ex.Message}"); } } /// /// Gets the list of detected devices. /// /// The cancellation token. /// /// Returns the list of detected devices. /// /// Failed to retrieve devices:. public async Task> ListDevices(CancellationToken cancellationToken = default) { try { using CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(3000); _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan on", null, cancellationTokenSource.Token).ConfigureAwait(false); _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "scan off", null, cancellationToken).ConfigureAwait(false); String devices = await ProcessRunner.GetProcessOutputAsync(BcCommand, "devices", null, cancellationToken).ConfigureAwait(false); return devices.Trim().Split('\n').Select(x => x.Trim()); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to retrieve devices: {ex.Message}"); } } /// /// Gets the list of bluetooth controllers. /// /// The cancellation token. /// /// Returns the list of bluetooth controllers. /// /// Failed to retrieve controllers:. public async Task> ListControllers(CancellationToken cancellationToken = default) { try { String controllers = await ProcessRunner.GetProcessOutputAsync(BcCommand, "list", null, cancellationToken).ConfigureAwait(false); return controllers.Trim().Split('\n').Select(x => x.Trim()); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to retrieve controllers: {ex.Message}"); } } /// /// Pairs a specific device with a specific controller. /// /// The mac address of the controller that will be used to pair. /// The mac address of the device that will be paired. /// The cancellation token. /// /// Returns true or false if the pair was successfully. /// /// Failed to Pair:. public async Task Pair(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) { try { // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false); // Makes the controller visible to other devices. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false); // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false); // Pairs the device with the controller. String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"pair {deviceAddress}", null, cancellationToken).ConfigureAwait(false); // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false); return result.Contains("Paired: yes"); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to Pair: {ex.Message}"); } } /// /// Performs a connection of a given controller with a given device. /// /// The mac address of the controller that will be used to make the connection. /// The mac address of the device that will be connected. /// The cancellation token. /// /// Returns true or false if the connection was successfully. /// /// Failed to connect:. public async Task Connect(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) { try { // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false); // Makes the controller visible to other devices. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false); // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false); // Readies the device for pairing. String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"connect {deviceAddress}", null, cancellationToken).ConfigureAwait(false); // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false); return result.Contains("Connected: yes"); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to connect: {ex.Message}"); } } /// /// Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. /// /// The mac address of the controller will be used. /// The mac address of the device will be added to the trust list devices. /// The cancellation token. /// /// Returns true or false if the operation was successful. /// /// Failed to add to trust devices list:. public async Task Trust(String controllerAddress, String deviceAddress, CancellationToken cancellationToken = default) { try { // Selects the controller to pair. Once you select the controller, all controller-related commands will apply to it for three minutes. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"select {controllerAddress}", null, cancellationToken).ConfigureAwait(false); // Makes the controller visible to other devices. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable on", null, cancellationToken).ConfigureAwait(false); // Readies the controller for pairing. Remember that you have three minutes after running this command to pair. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "pairable on", null, cancellationToken).ConfigureAwait(false); // Sets the device to re-pair automatically when it is turned on, which eliminates the need to pair all over again. String result = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"trust {deviceAddress}", null, cancellationToken).ConfigureAwait(false); // Hides the controller from other Bluetooth devices. Otherwise, any device that can detect it has access to it, leaving a major security hole. _ = await ProcessRunner.GetProcessOutputAsync(BcCommand, "discoverable off", null, cancellationToken).ConfigureAwait(false); return result.Contains("Trusted: yes"); } catch(Exception ex) { throw new BluetoothErrorException($"Failed to add to trust devices list: {ex.Message}"); } } /// /// Displays information about a particular device. /// /// The mac address of the device which info will be retrieved. /// The cancellation token. /// /// Returns the device info. /// /// Failed to retrieve info for {deviceAddress}. public async Task DeviceInfo(String deviceAddress, CancellationToken cancellationToken = default) { String info = await ProcessRunner.GetProcessOutputAsync(BcCommand, $"info {deviceAddress}", null, cancellationToken).ConfigureAwait(false); return !String.IsNullOrEmpty(info) ? info : throw new BluetoothErrorException($"Failed to retrieve info for {deviceAddress}"); } } }