|
@@ -1,86 +1,341 @@
|
|
|
-using InABox.Avalonia.Platform;
|
|
|
+using System.Collections.ObjectModel;
|
|
|
+using Android.Bluetooth;
|
|
|
+using Android.Bluetooth.LE;
|
|
|
+using Android.Content;
|
|
|
+using Android.OS;
|
|
|
+using Android.Text.Style;
|
|
|
+using FluentResults;
|
|
|
using InABox.Core;
|
|
|
using Microsoft.Maui.ApplicationModel;
|
|
|
-using Plugin.BLE;
|
|
|
-using Plugin.BLE.Abstractions;
|
|
|
-using Plugin.BLE.Abstractions.Contracts;
|
|
|
|
|
|
namespace InABox.Avalonia.Platform.Android;
|
|
|
|
|
|
-public class Android_Bluetooth : IBluetooth
|
|
|
+public class Android_BluetoothDevice(ScanResult scan) : IBluetoothDevice, IDisposable
|
|
|
{
|
|
|
- public Logger? Logger { get; set; }
|
|
|
+ public ScanResult Scan { get; } = scan;
|
|
|
+ public string ID { get; } = scan.Device?.Address ?? string.Empty;
|
|
|
+ public string Name { get; } = scan.ScanRecord?.DeviceName ?? "Unknown Device";
|
|
|
+
|
|
|
+ public void Dispose()
|
|
|
+ {
|
|
|
+ Scan.Dispose();
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public class Android_ConnectedBluetoothDevice(BluetoothDevice device) : BluetoothGattCallback,IConnectedBluetoothDevice
|
|
|
+{
|
|
|
+
|
|
|
+ private BluetoothDevice _device = device;
|
|
|
+ public string ID { get; } = device?.Address ?? string.Empty;
|
|
|
+ public string Name { get; } = device?.Name ?? "Unknown Device";
|
|
|
|
|
|
- public async Task<bool> IsAvailable()
|
|
|
+ private BluetoothGatt? _bluetoothGatt;
|
|
|
+
|
|
|
+ private TaskCompletionSource<bool> _connectionTaskCompletionSource;
|
|
|
+ private TaskCompletionSource<bool> _serviceDiscoveryTaskCompletionSource;
|
|
|
+ private TaskCompletionSource<byte[]> _readTaskCompletionSource;
|
|
|
+ private TaskCompletionSource<bool> _writeTaskCompletionSource;
|
|
|
+
|
|
|
+ public async Task<bool> ConnectAsync()
|
|
|
+ {
|
|
|
+ _connectionTaskCompletionSource = new TaskCompletionSource<bool>();
|
|
|
+ _bluetoothGatt = _device.ConnectGatt(Application.Context, false, this);
|
|
|
+ return await _connectionTaskCompletionSource.Task;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override void OnConnectionStateChange(BluetoothGatt? gatt, GattStatus status, ProfileState newState)
|
|
|
+ {
|
|
|
+ base.OnConnectionStateChange(gatt, status, newState);
|
|
|
+
|
|
|
+ if (newState == ProfileState.Connected && status == GattStatus.Success)
|
|
|
+ {
|
|
|
+ Console.WriteLine("Connected to GATT server.");
|
|
|
+ _connectionTaskCompletionSource?.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine("Failed to connect or disconnected.");
|
|
|
+ _connectionTaskCompletionSource?.TrySetResult(false);
|
|
|
+ Dispose();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<bool> DiscoverServicesAsync()
|
|
|
+ {
|
|
|
+ _serviceDiscoveryTaskCompletionSource = new TaskCompletionSource<bool>();
|
|
|
+ _bluetoothGatt?.DiscoverServices();
|
|
|
+ return await _serviceDiscoveryTaskCompletionSource.Task;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override void OnServicesDiscovered(BluetoothGatt? gatt, GattStatus status)
|
|
|
+ {
|
|
|
+ base.OnServicesDiscovered(gatt, status);
|
|
|
+
|
|
|
+ if (status == GattStatus.Success)
|
|
|
+ {
|
|
|
+ Console.WriteLine("Services discovered.");
|
|
|
+ _serviceDiscoveryTaskCompletionSource?.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine("Service discovery failed.");
|
|
|
+ _serviceDiscoveryTaskCompletionSource?.TrySetResult(false);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<byte[]?> ReadAsync(Guid serviceid, Guid characteristicid)
|
|
|
+ {
|
|
|
+ byte[]? result = null;
|
|
|
+ if (_bluetoothGatt != null)
|
|
|
+ {
|
|
|
+ var service = _bluetoothGatt.GetService(Java.Util.UUID.FromString(serviceid.ToString()));
|
|
|
+ if (service != null)
|
|
|
+ {
|
|
|
+ var characteristic = service.GetCharacteristic(Java.Util.UUID.FromString(characteristicid.ToString()));
|
|
|
+ if (characteristic != null)
|
|
|
+ result = await ReadCharacteristicAsync(characteristic);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task<byte[]?> ReadCharacteristicAsync(BluetoothGattCharacteristic characteristic)
|
|
|
+ {
|
|
|
+ if (_bluetoothGatt != null)
|
|
|
+ {
|
|
|
+ _readTaskCompletionSource = new TaskCompletionSource<byte[]>();
|
|
|
+
|
|
|
+ if (!_bluetoothGatt.ReadCharacteristic(characteristic))
|
|
|
+ {
|
|
|
+ _readTaskCompletionSource.TrySetException(
|
|
|
+ new InvalidOperationException("Failed to initiate characteristic read."));
|
|
|
+ }
|
|
|
+
|
|
|
+ return await _readTaskCompletionSource.Task;
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override void OnCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, byte[] data, GattStatus status)
|
|
|
+ {
|
|
|
+ base.OnCharacteristicRead(gatt, characteristic, data, status);
|
|
|
+
|
|
|
+ if (status == GattStatus.Success)
|
|
|
+ {
|
|
|
+ Console.WriteLine("Characteristic read successfully.");
|
|
|
+ _readTaskCompletionSource?.TrySetResult(data);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine("Failed to read characteristic.");
|
|
|
+ _readTaskCompletionSource?.TrySetException(new InvalidOperationException("Characteristic read failed."));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<bool> WriteAsync(Guid serviceid, Guid characteristicid, byte[] data)
|
|
|
+ {
|
|
|
+ var result = false;
|
|
|
+ if (_bluetoothGatt != null)
|
|
|
+ {
|
|
|
+ var service = _bluetoothGatt.GetService(Java.Util.UUID.FromString(serviceid.ToString()));
|
|
|
+ if (service != null)
|
|
|
+ {
|
|
|
+ var characteristic = service.GetCharacteristic(Java.Util.UUID.FromString(characteristicid.ToString()));
|
|
|
+ if (characteristic != null)
|
|
|
+ result = await WriteCharacteristicAsync(characteristic, data);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private async Task<bool> WriteCharacteristicAsync(BluetoothGattCharacteristic characteristic, byte[] data)
|
|
|
+ {
|
|
|
+ bool result = false;
|
|
|
+ if (_bluetoothGatt != null)
|
|
|
+ {
|
|
|
+ _writeTaskCompletionSource = new TaskCompletionSource<bool>();
|
|
|
+ _bluetoothGatt.WriteCharacteristic(characteristic, data, 2);
|
|
|
+ result = await _writeTaskCompletionSource.Task;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ public override void OnCharacteristicWrite(BluetoothGatt? gatt, BluetoothGattCharacteristic? characteristic, GattStatus status)
|
|
|
+ {
|
|
|
+ base.OnCharacteristicWrite(gatt, characteristic, status);
|
|
|
+ if (status == GattStatus.Success)
|
|
|
+ {
|
|
|
+ Console.WriteLine("Characteristic written successfully.");
|
|
|
+ _writeTaskCompletionSource?.TrySetResult(true);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine("Failed to write characteristic.");
|
|
|
+ _writeTaskCompletionSource?.TrySetException(new InvalidOperationException("Characteristic write failed."));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public void Dispose()
|
|
|
{
|
|
|
try
|
|
|
{
|
|
|
- PermissionStatus status = await Permissions.CheckStatusAsync<Permissions.Bluetooth>();
|
|
|
- if (status != PermissionStatus.Granted)
|
|
|
- status = await Permissions.RequestAsync<Permissions.Bluetooth>();
|
|
|
- return status == PermissionStatus.Granted;
|
|
|
+ _bluetoothGatt?.Disconnect();
|
|
|
+ _bluetoothGatt?.Close();
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ Console.WriteLine($"Error during disposal: {ex.Message}");
|
|
|
}
|
|
|
- catch (Exception e)
|
|
|
+ finally
|
|
|
{
|
|
|
- Console.WriteLine(e);
|
|
|
+ _bluetoothGatt = null;
|
|
|
}
|
|
|
|
|
|
- return false;
|
|
|
+ Console.WriteLine("Resources released.");
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
|
|
|
+public class BluetoothScanManager : ScanCallback
|
|
|
+{
|
|
|
+ private readonly Action<ScanResult> _onDeviceFound;
|
|
|
+ private readonly Action _onScanStopped;
|
|
|
|
|
|
+ public BluetoothScanManager(Action<ScanResult> onDeviceFound, Action onScanStopped)
|
|
|
+ {
|
|
|
+ _onDeviceFound = onDeviceFound;
|
|
|
+ _onScanStopped = onScanStopped;
|
|
|
}
|
|
|
|
|
|
- public async Task<bool> WriteAsync(string macaddress, Guid serviceid, Guid characteristicid, byte[] data)
|
|
|
+ public override void OnScanResult(ScanCallbackType callbackType, ScanResult result)
|
|
|
+ {
|
|
|
+ base.OnScanResult(callbackType, result);
|
|
|
+ _onDeviceFound?.Invoke(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ public override void OnScanFailed(ScanFailure errorCode)
|
|
|
+ {
|
|
|
+ base.OnScanFailed(errorCode);
|
|
|
+ _onScanStopped?.Invoke();
|
|
|
+ throw new Exception($"Scan failed with error code: {errorCode}");
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+public class Android_Bluetooth : IBluetooth
|
|
|
+{
|
|
|
+ public Logger? Logger { get; set; }
|
|
|
+
|
|
|
+ public CoreObservableCollection<IBluetoothDevice> Devices { get; private set; } = new CoreObservableCollection<IBluetoothDevice>();
|
|
|
+
|
|
|
+ private readonly BluetoothLeScanner? _scanner;
|
|
|
+
|
|
|
+ public event EventHandler? Changed;
|
|
|
+
|
|
|
+ public Android_Bluetooth()
|
|
|
+ {
|
|
|
+ var _manager = Application.Context.GetSystemService(Context.BluetoothService) as BluetoothManager;
|
|
|
+ var _adapter = _manager?.Adapter;
|
|
|
+ _scanner = _adapter?.BluetoothLeScanner;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static async Task<bool> IsPermitted<TPermission>() where TPermission : Permissions.BasePermission, new()
|
|
|
{
|
|
|
- if (await IsAvailable() != true)
|
|
|
- return false;
|
|
|
-
|
|
|
- IDevice? _device = null;
|
|
|
- var adapter = CrossBluetoothLE.Current.Adapter;
|
|
|
- adapter.DeviceDiscovered += (s, e) => _device = e.Device;
|
|
|
try
|
|
|
{
|
|
|
- var options = new ScanFilterOptions();
|
|
|
- options.DeviceAddresses = [macaddress];
|
|
|
- options.ServiceUuids = [serviceid];
|
|
|
- await adapter.StartScanningForDevicesAsync(options, x=> true, false);
|
|
|
+ PermissionStatus status = await Permissions.CheckStatusAsync<TPermission>();
|
|
|
+ if (status == PermissionStatus.Granted)
|
|
|
+ return true;
|
|
|
+ var request = await Permissions.RequestAsync<TPermission>();
|
|
|
+ return request == PermissionStatus.Granted;
|
|
|
+
|
|
|
}
|
|
|
- catch (Exception e)
|
|
|
+ catch (TaskCanceledException ex)
|
|
|
{
|
|
|
- Console.WriteLine(e);
|
|
|
- Logger?.Error(e.Message);
|
|
|
+ return false;
|
|
|
}
|
|
|
-
|
|
|
- if (_device != null)
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<bool> IsAvailable()
|
|
|
+ {
|
|
|
+ if (await IsPermitted<Permissions.Bluetooth>())
|
|
|
+ return _scanner != null;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ BluetoothScanManager? _callback;
|
|
|
+
|
|
|
+ public async Task<bool> StartScanningAsync(Guid serviceid)
|
|
|
+ {
|
|
|
+ if (await IsAvailable())
|
|
|
{
|
|
|
- try
|
|
|
+ _callback = new BluetoothScanManager((d) => DoDeviceFound(d, serviceid), ScanStopped);
|
|
|
+ _scanner!.StartScan(_callback);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<bool> StopScanningAsync()
|
|
|
+ {
|
|
|
+ if (await IsAvailable())
|
|
|
+ {
|
|
|
+ if (_callback != null)
|
|
|
{
|
|
|
- //await adapter.StopScanningForDevicesAsync();
|
|
|
- //ConnectParameters connectParameters = new ConnectParameters(true, true);
|
|
|
- await adapter.ConnectToDeviceAsync(_device);
|
|
|
- try
|
|
|
- {
|
|
|
- var service = await _device.GetServiceAsync(serviceid);
|
|
|
- var characteristic = await service.GetCharacteristicAsync(characteristicid);
|
|
|
- var bytes = await characteristic.ReadAsync();
|
|
|
- await characteristic.WriteAsync(data);
|
|
|
- }
|
|
|
- catch (Exception e)
|
|
|
- {
|
|
|
- Logger?.Error(e.Message);
|
|
|
- return false;
|
|
|
- }
|
|
|
- await adapter.DisconnectDeviceAsync(_device);
|
|
|
+ _scanner!.StopScan(_callback);
|
|
|
return true;
|
|
|
}
|
|
|
- catch (Exception e)
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private void DoDeviceFound(ScanResult device, Guid serviceid)
|
|
|
+ {
|
|
|
+ bool bMatch = true;
|
|
|
+
|
|
|
+ bMatch = false;
|
|
|
+ if (device.ScanRecord?.ServiceUuids?.Any() == true)
|
|
|
{
|
|
|
- Logger?.Error(e.Message);
|
|
|
- return false;
|
|
|
+ foreach (var uuid in device.ScanRecord.ServiceUuids)
|
|
|
+ {
|
|
|
+ if (Guid.TryParse(uuid.ToString(), out Guid guid))
|
|
|
+ bMatch = bMatch || Guid.Equals(serviceid,guid);
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
|
|
|
+ if (bMatch && !Devices.Any(x => x.ID == device.Device?.Address))
|
|
|
+ {
|
|
|
+ var abd = new Android_BluetoothDevice(device);
|
|
|
+ Devices.Add(abd);
|
|
|
}
|
|
|
- return false;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ private void ScanStopped()
|
|
|
+ {
|
|
|
+ _callback = null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public async Task<IConnectedBluetoothDevice?> Connect(IBluetoothDevice device)
|
|
|
+ {
|
|
|
+ if (device is Android_BluetoothDevice d && d.Scan.Device is BluetoothDevice bd)
|
|
|
+ {
|
|
|
+ var result = new Android_ConnectedBluetoothDevice(bd);
|
|
|
+ if (await result.ConnectAsync())
|
|
|
+ {
|
|
|
+ await result.DiscoverServicesAsync();
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ public async Task<bool> Disconnect(IConnectedBluetoothDevice device)
|
|
|
+ {
|
|
|
+ if (device is Android_ConnectedBluetoothDevice d)
|
|
|
+ d.Dispose();
|
|
|
+
|
|
|
+ return await Task.FromResult(true);
|
|
|
}
|
|
|
}
|