diff --git a/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs b/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs index 77645e23..67f1fc32 100644 --- a/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs +++ b/BrickController2/BrickController2.Android/PlatformServices/BluetoothLE/BluetoothLEDevice.cs @@ -8,6 +8,7 @@ using Android.OS; using Android.Runtime; using Java.Util; +using BrickController2.Helpers; using BrickController2.PlatformServices.BluetoothLE; namespace BrickController2.Droid.PlatformServices.BluetoothLE @@ -18,7 +19,7 @@ public class BluetoothLEDevice : BluetoothGattCallback, IBluetoothLEDevice private readonly Context _context; private readonly BluetoothAdapter _bluetoothAdapter; - private readonly object _lock = new object(); + private readonly AsyncLock _lock = new(); private BluetoothDevice? _bluetoothDevice = null; private BluetoothGatt? _bluetoothGatt = null; @@ -49,14 +50,14 @@ public BluetoothLEDevice(Context context, BluetoothAdapter bluetoothAdapter, str { using (token.Register(() => { - lock (_lock) + using (_lock.Lock()) { Disconnect(); _connectCompletionSource?.TrySetResult(null); } })) { - lock (_lock) + using (_lock.Lock(token)) { if (State != BluetoothLEDeviceState.Disconnected) { @@ -77,11 +78,11 @@ public BluetoothLEDevice(Context context, BluetoothAdapter bluetoothAdapter, str if (Build.VERSION.SdkInt >= BuildVersionCodes.M) { - _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect, this, BluetoothTransports.Le); + _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect: false, this, BluetoothTransports.Le); } else { - _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect, this); + _bluetoothGatt = _bluetoothDevice.ConnectGatt(_context, autoConnect: false, this); } if (_bluetoothGatt is null) @@ -97,7 +98,7 @@ public BluetoothLEDevice(Context context, BluetoothAdapter bluetoothAdapter, str var result = await _connectCompletionSource.Task; - lock (_lock) + using (_lock.Lock(token)) { _connectCompletionSource = null; return result; @@ -125,27 +126,25 @@ internal void Disconnect() State = BluetoothLEDeviceState.Disconnected; } - public Task DisconnectAsync() + public async Task DisconnectAsync() { - lock (_lock) + using (await _lock.LockAsync()) { Disconnect(); } - - return Task.CompletedTask; } public async Task EnableNotificationAsync(IGattCharacteristic characteristic, CancellationToken token) { using (token.Register(() => { - lock (_lock) + using (_lock.Lock()) { _descriptorWriteCompletionSource?.TrySetResult(false); } })) - lock (_lock) + using (await _lock.LockAsync(token)) { if (_bluetoothGatt == null || State != BluetoothLEDeviceState.Connected) { @@ -158,33 +157,54 @@ public async Task EnableNotificationAsync(IGattCharacteristic characterist return false; } + // wait slightly for local internal state to settle + await Task.Delay(400, token); + var descriptor = nativeCharacteristic.GetDescriptor(ClientCharacteristicConfigurationUUID); if (descriptor == null) { return false; } -#pragma warning disable CA1422 // Validate platform compatibility - if (!(descriptor?.SetValue(BluetoothGattDescriptor.EnableNotificationValue!.ToArray()) ?? false)) + _descriptorWriteCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var valueToSet = BluetoothGattDescriptor.EnableNotificationValue!.ToArray(); + bool writeInitiated = false; + + if (Build.VERSION.SdkInt >= BuildVersionCodes.Tiramisu) // API 33+ { - return false; + // New API: Pass value and write type explicitly +#pragma warning disable CA1416 // Validate platform compatibility + var resultCode = _bluetoothGatt.WriteDescriptor(descriptor, valueToSet); +#pragma warning restore CA1416 // Validate platform compatibility + writeInitiated = resultCode == 0; } + else + { +#pragma warning disable CA1422 // Validate platform compatibility + // Old API: Set local value, then write + if (!descriptor.SetValue(valueToSet)) + { + _descriptorWriteCompletionSource = null; + return false; + } + writeInitiated = _bluetoothGatt.WriteDescriptor(descriptor); #pragma warning restore CA1422 // Validate platform compatibility + } - _descriptorWriteCompletionSource = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - -#pragma warning disable CA1422 // Validate platform compatibility - if (!(_bluetoothGatt?.WriteDescriptor(descriptor) ?? false)) + if (!writeInitiated) { _descriptorWriteCompletionSource = null; - return false; - } -#pragma warning restore CA1422 // Validate platform compatibility + return false; // BLE stack was busy or request failed immediately } + } - var result = await _descriptorWriteCompletionSource.Task.ConfigureAwait(false); + // restrict wait time to avoid hanging till target device disconnects + var result = await _descriptorWriteCompletionSource.Task + .WaitAsync(TimeSpan.FromSeconds(10), token) + .ConfigureAwait(false); - lock (_lock) + using (await _lock.LockAsync(token)) { _descriptorWriteCompletionSource = null; return result; @@ -195,13 +215,13 @@ public async Task EnableNotificationAsync(IGattCharacteristic characterist { using (token.Register(() => { - lock (_lock) + using (_lock.Lock()) { _readCompletionSource?.TrySetResult(null); } })) { - lock (_lock) + using (await _lock.LockAsync(token)) { var nativeCharacteristic = ((GattCharacteristic)characteristic).BluetoothGattCharacteristic; @@ -216,7 +236,7 @@ public async Task EnableNotificationAsync(IGattCharacteristic characterist var result = await _readCompletionSource.Task; - lock (_lock) + using (await _lock.LockAsync(token)) { _readCompletionSource = null; return result; @@ -228,13 +248,13 @@ public async Task WriteAsync(IGattCharacteristic characteristic, byte[] da { using (token.Register(() => { - lock (_lock) + using (_lock.Lock()) { _writeCompletionSource?.TrySetResult(false); } })) { - lock (_lock) + using (await _lock.LockAsync(token)) { if (_bluetoothGatt == null || State != BluetoothLEDeviceState.Connected) { @@ -264,7 +284,7 @@ public async Task WriteAsync(IGattCharacteristic characteristic, byte[] da var result = await _writeCompletionSource.Task.ConfigureAwait(false); - lock (_lock) + using (await _lock.LockAsync(token)) { _writeCompletionSource = null; return result; @@ -272,13 +292,13 @@ public async Task WriteAsync(IGattCharacteristic characteristic, byte[] da } } - public Task WriteNoResponseAsync(IGattCharacteristic characteristic, byte[] data, CancellationToken token) + public async Task WriteNoResponseAsync(IGattCharacteristic characteristic, byte[] data, CancellationToken token) { - lock (_lock) + using (await _lock.LockAsync(token)) { if (_bluetoothGatt == null || State != BluetoothLEDeviceState.Connected) { - return Task.FromResult(false); + return false; } var nativeCharacteristic = ((GattCharacteristic)characteristic).BluetoothGattCharacteristic; @@ -287,14 +307,14 @@ public Task WriteNoResponseAsync(IGattCharacteristic characteristic, byte[ #pragma warning disable CA1422 // Validate platform compatibility if (!(nativeCharacteristic?.SetValue(data) ?? false)) { - return Task.FromResult(false); + return false; } #pragma warning restore CA1422 // Validate platform compatibility #pragma warning disable CA1422 // Validate platform compatibility var result = _bluetoothGatt?.WriteCharacteristic(nativeCharacteristic) ?? false; #pragma warning restore CA1422 // Validate platform compatibility - return Task.FromResult(result); + return result; } } @@ -306,7 +326,7 @@ public override void OnConnectionStateChange(BluetoothGatt? gatt, [GeneratedEnum break; case ProfileState.Connected: - lock (_lock) + using (_lock.Lock()) { if (State == BluetoothLEDeviceState.Connecting && status == GattStatus.Success) { @@ -314,7 +334,7 @@ public override void OnConnectionStateChange(BluetoothGatt? gatt, [GeneratedEnum Task.Run(async () => { await Task.Delay(750); - lock (_lock) + using (await _lock.LockAsync()) { if (State == BluetoothLEDeviceState.Discovering && _bluetoothGatt != null) { @@ -340,7 +360,7 @@ public override void OnConnectionStateChange(BluetoothGatt? gatt, [GeneratedEnum break; case ProfileState.Disconnected: - lock (_lock) + using (_lock.Lock()) { switch (State) { @@ -372,7 +392,7 @@ public override void OnConnectionStateChange(BluetoothGatt? gatt, [GeneratedEnum public override void OnServicesDiscovered(BluetoothGatt? gatt, [GeneratedEnum] GattStatus status) { - lock (_lock) + using (_lock.Lock()) { if (status == GattStatus.Success && State == BluetoothLEDeviceState.Discovering) { @@ -407,7 +427,7 @@ public override void OnServicesDiscovered(BluetoothGatt? gatt, [GeneratedEnum] G public override void OnCharacteristicRead(BluetoothGatt? gatt, BluetoothGattCharacteristic? characteristic, [GeneratedEnum] GattStatus status) { - lock (_lock) + using (_lock.Lock()) { #pragma warning disable CA1422 // Validate platform compatibility _readCompletionSource?.TrySetResult(characteristic?.GetValue()); @@ -417,7 +437,7 @@ public override void OnCharacteristicRead(BluetoothGatt? gatt, BluetoothGattChar public override void OnCharacteristicWrite(BluetoothGatt? gatt, BluetoothGattCharacteristic? characteristic, [GeneratedEnum] GattStatus status) { - lock (_lock) + using (_lock.Lock()) { _writeCompletionSource?.TrySetResult(status == GattStatus.Success); } @@ -425,7 +445,7 @@ public override void OnCharacteristicWrite(BluetoothGatt? gatt, BluetoothGattCha public override void OnDescriptorWrite(BluetoothGatt? gatt, BluetoothGattDescriptor? descriptor, [GeneratedEnum] GattStatus status) { - lock (_lock) + using (_lock.Lock()) { _descriptorWriteCompletionSource?.TrySetResult(status == GattStatus.Success); } @@ -433,7 +453,7 @@ public override void OnDescriptorWrite(BluetoothGatt? gatt, BluetoothGattDescrip public override void OnCharacteristicChanged(BluetoothGatt? gatt, BluetoothGattCharacteristic? characteristic) { - lock (_lock) + using (_lock.Lock()) { var guid = characteristic?.Uuid?.ToGuid(); #pragma warning disable CA1422 // Validate platform compatibility diff --git a/BrickController2/BrickController2/DeviceManagement/TechnicMoveDevice.cs b/BrickController2/BrickController2/DeviceManagement/TechnicMoveDevice.cs index bd3e28b6..b6195f37 100644 --- a/BrickController2/BrickController2/DeviceManagement/TechnicMoveDevice.cs +++ b/BrickController2/BrickController2/DeviceManagement/TechnicMoveDevice.cs @@ -133,6 +133,20 @@ protected override byte[] GetServoCommand(int channel, int servoValue, int servo return base.GetServoCommand(channel, servoValue, servoSpeed); } + protected override async Task ValidateServicesAsync(IEnumerable? services, CancellationToken token) + { + // better handle this type of device + if (services?.Any(s => s.Uuid == ServiceUuid && s.Characteristics.Any(c => c.Uuid == CharacteristicUuid)) == true) + { + // give some additional wait time for the device to be ready + await Task.Delay(TimeSpan.FromSeconds(1), token); + + return await base.ValidateServicesAsync(services, token); + } + + return false; + } + protected override async Task AfterConnectSetupAsync(bool requestDeviceInformation, CancellationToken token) { if (await base.AfterConnectSetupAsync(requestDeviceInformation, token)) diff --git a/BrickController2/BrickController2/Helpers/AsyncLock.cs b/BrickController2/BrickController2/Helpers/AsyncLock.cs index 14429f6d..b9ed7c8a 100644 --- a/BrickController2/BrickController2/Helpers/AsyncLock.cs +++ b/BrickController2/BrickController2/Helpers/AsyncLock.cs @@ -19,6 +19,13 @@ public async Task LockAsync(CancellationToken token) return new Releaser(_semaphore); } + + public IDisposable Lock(CancellationToken token = default) + { + _semaphore.Wait(token); + return new Releaser(_semaphore); + } + private struct Releaser : IDisposable { private SemaphoreSlim? _semaphore;