示例#1
0
        private async void DeviceInRange(object sender, BluetoothWin32RadioInRangeEventArgs?e)
        {
            if (e?.Device == null)
            {
                Log.Warning("Windows.BluetoothService: Discovered NULL device");
                return;
            }

            if (e.Device.DeviceAddress == MacUtils.ToAddress(_currentMac))
            {
                Log.Debug($"Windows.BluetoothService: Target device inbound ({_currentMac})");
                if (IsStreamConnected)
                {
                    Log.Debug($"Windows.BluetoothService: Target device already connected");
                }
                else
                {
                    try
                    {
                        await ConnectAsync(_currentMac, _currentUuid);
                    }
                    catch (InvalidOperationException ex)
                    {
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.Unknown, ex.Message));
                        Log.Error($"Windows.BluetoothService: DeviceInRange: InvalidOperationException caught ({ex.Message})");
                    }
                    catch (BluetoothException ex)
                    {
                        BluetoothErrorAsync?.Invoke(this, ex);
                        Log.Error($"Windows.BluetoothService: DeviceInRange: BluetoothException caught ({ex.Message})");
                    }
                }
            }
        }
示例#2
0
 public async Task ConnectAsync(string macAddress, string serviceUuid, bool noRetry = false)
 {
     BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.Unknown,
                                                              "Platform configuration not supported." +
                                                              "For Windows users, it is recommended to use Windows 10 build 1803 and later since it provides a much more reliable and newer bluetooth interface. " +
                                                              $"You can find more information about the cause of this error in the application logs ({PlatformUtils.CombineDataPath("application.log")})."));
     await Task.CompletedTask;
 }
示例#3
0
        public void SelectAdapter()
        {
            try
            {
                _client = new BluetoothClient();
            }
            catch (PlatformNotSupportedException)
            {
                BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.NoAdaptersAvailable));
                return;
            }

            Log.Debug($"Windows.BluetoothService: Bluetooth adapter found");
        }
        public async Task SendAsync(byte[] data)
        {
            if (!IsStreamConnected)
            {
                Log.Error("WindowsRT.BluetoothService: Cannot send message. Not connected.");
                BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.SendFailed,
                                                                         "Stream disconnected while dispatching a message"));
                await DisconnectAsync();

                return;
            }

            try
            {
                if (_writer == null)
                {
                    Log.Warning("WindowsRT.BluetoothService: Cannot send message. Writer is NULL");
                    IsStreamConnected = false;
                    BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.SendFailed,
                                                                             "Stream disconnected while dispatching a message"));
                    await DisconnectAsync();

                    return;
                }

                _writer.WriteBytes(data);
                await _writer.StoreAsync();
            }
            catch (Exception ex) when((uint)ex.HResult == 0x80072745)
            {
                // The remote device has disconnected the connection
                Log.Error("WindowsRT.BluetoothService: Remote closed connection while dispatching message");
                BluetoothErrorAsync?.Invoke(this,
                                            new BluetoothException(BluetoothException.ErrorCodes.SendFailed,
                                                                   "Remote device closed connection: " + ex.HResult.ToString() + " - " + ex.Message));
                await DisconnectAsync();
            }
            catch (Exception ex)
            {
                Log.Error("WindowsRT.BluetoothService: Error while sending: " + ex.Message);
                BluetoothErrorAsync?.Invoke(this,
                                            new BluetoothException(BluetoothException.ErrorCodes.SendFailed,
                                                                   "Remote device closed connection: " + ex.HResult.ToString() + " - " + ex.Message));
                await DisconnectAsync();
            }
        }
        private async void BluetoothServiceLoop()
        {
            while (true)
            {
                try
                {
                    _loopCancellation.Token.ThrowIfCancellationRequested();
                    Task.Delay(100).Wait(_loopCancellation.Token);
                }
                catch (OperationCanceledException)
                {
                    IsStreamConnected = false;
                    Log.Debug("WindowsRT.BluetoothService: BluetoothServiceLoop cancelled.");
                    return;
                }

                try
                {
                    Stream?incoming = null;
                    if (_socket == null)
                    {
                        Log.Error($"WindowsRT.BluetoothService: StreamSocket is null");
                        BluetoothErrorAsync?.Invoke(this,
                                                    new BluetoothException(BluetoothException.ErrorCodes.ReceiveFailed,
                                                                           "Cannot retrieve incoming data stream. Device probably disconnected."));
                        await DisconnectAsync();

                        return;
                    }

                    lock (_socket)
                    {
                        incoming = _socket?.InputStream.AsStreamForRead();
                    }

                    if (incoming == null)
                    {
                        Log.Error($"WindowsRT.BluetoothService: Cannot retrieve incoming data stream");
                        BluetoothErrorAsync?.Invoke(this,
                                                    new BluetoothException(BluetoothException.ErrorCodes.ReceiveFailed,
                                                                           "Cannot retrieve incoming data stream. Device probably disconnected."));
                        await DisconnectAsync();

                        return;
                    }

                    byte[] buffer = new byte[10000];

                    var b = incoming.Read(buffer, 0, buffer.Length);
                    if (b == 0)
                    {
                        // EOS
                        Log.Warning("WindowsRT.BluetoothService: End of stream. Connection closed by remote host");
                        break;
                    }

                    buffer = buffer.ShrinkTo(b);

                    NewDataAvailable?.Invoke(this, buffer);
                }
                catch (Exception ex)
                {
                    if (_socket == null)
                    {
                        switch ((uint)ex.HResult)
                        {
                        // the user closed the socket.
                        case 0x80072745:

                            BluetoothErrorAsync?.Invoke(this, new BluetoothException(
                                                            BluetoothException.ErrorCodes.ReceiveFailed,
                                                            "Disconnect triggered by remote device"));
                            break;

                        case 0x800703E3:
                            Log.Debug(
                                "WindowsRT.BluetoothService: The I/O operation has been aborted because of either a thread exit or an application request");
                            break;
                        }
                    }
                    else
                    {
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(
                                                        BluetoothException.ErrorCodes.ReceiveFailed,
                                                        "Failed to read stream: " + ex.Message));
                    }

                    await DisconnectAsync();
                }
            }
        }
        public async Task ConnectAsync(string macAddress, string serviceUuid, bool noRetry = false)
        {
            if (IsConnecting)
            {
                Log.Debug($"WindowsRT.BluetoothService: Already connecting. Skipping request.");
                return;
            }
            if (IsStreamConnected)
            {
                Log.Debug($"WindowsRT.BluetoothService: Already connected. Skipping request.");
                return;
            }

            Connecting?.Invoke(this, EventArgs.Empty);

            try
            {
                var matches = _deviceCache.Where(x =>
                                                 string.Equals(x.Address, macAddress, StringComparison.CurrentCultureIgnoreCase)).ToList();
                if (matches.Count <= 0)
                {
                    Log.Error(
                        $"WindowsRT.BluetoothService: Registered device not available. Expected MAC: {macAddress}");
                    BluetoothErrorAsync?.Invoke(this, new BluetoothException(
                                                    BluetoothException.ErrorCodes.ConnectFailed,
                                                    "Device unavailable. Not device with registered MAC address not found nearby. If you are certain that your earbuds are connected to this computer, please unregister them and try again."));
                }
                else
                {
                    Log.Debug(
                        $"WindowsRT.BluetoothService: Selected '{matches[0].Name}' ({matches[0].Address}) from cache as target");
                }

                // Perform device access checks before trying to get the device.
                // First, we check if consent has been explicitly denied by the user.
                var accessStatus = DeviceAccessInformation.CreateFromId(matches[0].Id).CurrentStatus;
                if (accessStatus == DeviceAccessStatus.DeniedByUser)
                {
                    Log.Error($"WindowsRT.BluetoothService: Access to device explicitly denied by user");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "This app does not have access to connect to the remote device (please grant access in Settings > Privacy > Other Devices"));
                    return;
                }

                if (accessStatus == DeviceAccessStatus.DeniedBySystem)
                {
                    Log.Error($"WindowsRT.BluetoothService: Access to device denied by system");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Access denied by system. This app does not have access to connect to the remote device"));
                    return;
                }

                // If not, try to get the Bluetooth device
                try
                {
                    _bluetoothDevice = await Windows.Devices.Bluetooth.BluetoothDevice.FromIdAsync(matches[0].Id);
                }
                catch (Exception ex)
                {
                    Log.Error(
                        $"WindowsRT.BluetoothService: Error while getting Bluetooth device from cached id: {ex.Message}");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       ex.Message));
                    return;
                }

                // If we were unable to get a valid Bluetooth device object,
                // it's most likely because the user has specified that all unpaired devices
                // should not be interacted with.
                if (_bluetoothDevice == null)
                {
                    Log.Error($"WindowsRT.BluetoothService: BluetoothDevice.FromIdAsync returned NULL");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Unable to retrieve device object. Try to re-pair your Bluetooth device."));
                    return;
                }

                // This should return a list of uncached Bluetooth services (so if the server was not active when paired, it will still be detected by this call
                var rfcommServices = await _bluetoothDevice.GetRfcommServicesForIdAsync(
                    RfcommServiceId.FromUuid(new Guid(serviceUuid)), BluetoothCacheMode.Uncached);

                if (rfcommServices.Services.Count > 0)
                {
                    _service = rfcommServices.Services[0];
                }
                else
                {
                    Log.Error($"WindowsRT.BluetoothService: SPP service not discovered");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Unable to discover SDP record for the RFCOMM protocol. Either your earbuds are out of range or ran out of battery."));
                    return;
                }

                lock (this)
                {
                    _socket = new StreamSocket();
                }

                try
                {
                    await _socket.ConnectAsync(_service.ConnectionHostName, _service.ConnectionServiceName);

                    Connected?.Invoke(this, EventArgs.Empty);
                    Log.Debug($"WindowsRT.BluetoothService: Connected");

                    _writer = new DataWriter(_socket.OutputStream);

                    Log.Debug("WindowsRT.BluetoothService: Launching BluetoothServiceLoop...");
                    RfcommConnected?.Invoke(this, EventArgs.Empty);

                    IsStreamConnected = true;

                    _loopCancellation = new CancellationTokenSource();
                    _loop             = Task.Run(BluetoothServiceLoop);
                }
                catch (Exception ex) when((uint)ex.HResult == 0x80070490)   // ERROR_ELEMENT_NOT_FOUND
                {
                    Log.Error(
                        "WindowsRT.BluetoothService: Error while connecting (HRESULT: ERROR_ELEMENT_NOT_FOUND): " +
                        ex.Message);
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "SPP server on remote device unavailable. Please reboot your earbuds by placing both into the case and closing it. (ERROR_ELEMENT_NOT_FOUND)"));
                }
                catch (Exception ex) when((uint)ex.HResult == 0x80072740)   // WSAEADDRINUSE
                {
                    Log.Error("WindowsRT.BluetoothService: Address already in use");
                    BluetoothErrorAsync?.Invoke(this,
                                                new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                       "Target address already in use. Only one app can talk to the Galaxy Buds at a time. " +
                                                                       "Please make sure to close duplicate instances of this app and close all applications that are interacting with the proprietary RFCOMM protocol, such as Samsung's official firmware updater"));
                }
            }
            catch (Exception ex)
            {
                Log.Error("WindowsRT.BluetoothService: Unknown error while connecting: " + ex);
                BluetoothErrorAsync?.Invoke(this,
                                            new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed,
                                                                   ex.Message));
            }
        }
示例#7
0
        private void BluetoothServiceLoop()
        {
            Stream?peerStream = null;

            while (true)
            {
                try
                {
                    _cancelSource.Token.ThrowIfCancellationRequested();
                    Task.Delay(50).Wait(_cancelSource.Token);
                }
                catch (OperationCanceledException)
                {
                    peerStream?.Close();
                    _client?.Close();
                    throw;
                }

                if (_client == null || !_client.Connected)
                {
                    continue;
                }

                if (peerStream == null)
                {
                    lock (_btlock)
                    {
                        if (!_client.Connected)
                        {
                            continue;
                        }

                        peerStream = _client.GetStream();
                    }

                    RfcommConnected?.Invoke(this, EventArgs.Empty);
                    continue;
                }


                var available = _client.Available;
                if (available > 0 && peerStream.CanRead)
                {
                    byte[] buffer = new byte[available];
                    try
                    {
                        peerStream.Read(buffer, 0, available);
                    }
                    catch (SocketException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: SocketException thrown while reading from socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.ReceiveFailed, ex.Message));
                        return;
                    }
                    catch (IOException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: IOException thrown while writing to socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.ReceiveFailed, ex.Message));
                    }

                    if (buffer.Length > 0)
                    {
                        NewDataAvailable?.Invoke(this, buffer);
                    }
                }

                lock (TransmitterQueue)
                {
                    if (TransmitterQueue.Count <= 0)
                    {
                        continue;
                    }
                    if (!TransmitterQueue.TryDequeue(out var raw))
                    {
                        continue;
                    }
                    try
                    {
                        peerStream.Write(raw, 0, raw.Length);
                    }
                    catch (SocketException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: SocketException thrown while writing to socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.SendFailed, ex.Message));
                    }
                    catch (IOException ex)
                    {
                        Log.Error($"Windows.BluetoothService: BluetoothServiceLoop: IOException thrown while writing to socket: {ex.Message}. Cancelled.");
                        BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.SendFailed, ex.Message));
                    }
                }
            }
        }
示例#8
0
        public async Task ConnectAsync(string macAddress, string uuid, bool noRetry = false)
        {
            var semResult = await _connSemaphore.WaitAsync(5000);

            if (semResult == false)
            {
                Log.Error($"Windows.BluetoothService: Connection attempt timed out due to blocked semaphore");
                throw new BluetoothException(BluetoothException.ErrorCodes.TimedOut, "Timed out while waiting to enter connection phase. Another task is already preparing a connection.");
            }

            if (_client?.Connected ?? false)
            {
                Log.Debug("Windows.BluetoothService: Already connected, skipped.");
                _connSemaphore.Release();
                return;
            }

            SelectAdapter();

            Connecting?.Invoke(this, EventArgs.Empty);
            Log.Debug($"Windows.BluetoothService: Connecting...");

            _cancelSource?.Cancel();
            _client?.Close();
            _client = null;
            SelectAdapter();

            if (_client == null)
            {
                Log.Error("Windows.BluetoothService: Cannot create client and connect.");
                BluetoothErrorAsync?.Invoke(this, new BluetoothException(BluetoothException.ErrorCodes.Unknown, "Cannot create client"));
                _connSemaphore.Release();
                return;
            }

            try
            {
                try
                {
                    var addr = MacUtils.ToAddress(macAddress);
                    if (addr == null)
                    {
                        Log.Error("Windows.BluetoothService: Invalid MAC address. Failed to connect.");
                        throw new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed, "Invalid MAC address. Please deregister your device and try again.");
                    }

                    _currentMac  = macAddress;
                    _currentUuid = uuid;

                    _client.Connect(addr, new Guid(uuid));

                    /*await  Task.Factory.FromAsync(
                     *  (callback, stateObject) => _client.BeginConnect(addr, new Guid(uuid), callback, stateObject),
                     *  _client.EndConnect, null);*/
                }
                catch (ArgumentException)
                {
                    BluetoothErrorAsync?.Invoke(this, new BluetoothException(
                                                    BluetoothException.ErrorCodes.ConnectFailed,
                                                    $"Invalid MAC address. Please deregister your device and try again."));
                    _connSemaphore.Release();
                    return;
                }

                Connected?.Invoke(this, EventArgs.Empty);
                Log.Debug($"Windows.BluetoothService: Connected. Launching BluetoothServiceLoop.");

                _cancelSource = new CancellationTokenSource();
                _loop         = Task.Run(BluetoothServiceLoop, _cancelSource.Token);
            }
            catch (SocketException e)
            {
                BluetoothErrorAsync?.Invoke(this,
                                            new BluetoothException(BluetoothException.ErrorCodes.ConnectFailed, e.Message));
            }
            finally
            {
                _connSemaphore.Release();
            }
        }