123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366 |
- using System.Collections.ObjectModel;
- using Windows.Devices.Enumeration;
- using Windows.Devices.Bluetooth;
- using Windows.Devices.Bluetooth.GenericAttributeProfile;
- using System.Runtime.InteropServices.WindowsRuntime;
- using System.Text;
- using Windows.Devices.Bluetooth.Advertisement;
- using Windows.Storage.Streams;
- using InABox.Core;
- namespace BluetoothLENet
- {
- public class BLECharacteristic(GattCharacteristic native) : IDisposable
- {
- public GattCharacteristic? Native { get; private set; } = native;
- public async Task<byte[]?> ReadAsync()
- {
- var result = await Native?.ReadValueAsync(BluetoothCacheMode.Uncached);
- if (result.Status == GattCommunicationStatus.Success)
- return result.Value.ToArray();
- return null;
- }
- public async Task<bool> WriteAsync(byte[] data)
- {
- using var writer = new DataWriter();
- writer.ByteOrder = ByteOrder.LittleEndian;
- writer.WriteBytes(data);
- var buffer = writer.DetachBuffer();
- var result = await Native?.WriteValueWithResultAsync(buffer);
- return result.Status == GattCommunicationStatus.Success;
- }
- public void Dispose()
- {
- Native = null;
- }
- }
-
- public class BLEService(GattDeviceService native) : IDisposable
- {
- public GattDeviceService? Native { get; private set; } = native;
- public ObservableCollection<BLECharacteristic> Characteristics { get; } = new();
- public void Dispose()
- {
- Characteristics.Clear();
- Native?.Dispose();
- }
- }
- public enum BLEDeviceStatus
- {
- Connected,
- Disconnected
- }
-
- public class BLEDevice: IDisposable
- {
- public BluetoothLEDevice? Native { get; private set; }
- public Guid[] AvailableServices { get; set; } = [];
- public byte[]? ManufacturerData { get; set; }
- public ObservableCollection<BLEService> Services { get; } = new();
- public DateTime LastSeen { get; set; }
- public string MacAddress => ParseMacAddress(Native?.BluetoothAddress ?? 0);
- public BLEDevice(BluetoothLEDevice native, Guid[] availableServices)
- {
- Native = native;
- AvailableServices = availableServices;
- BetterScanner.StartScanner(0,29,29);
- }
-
- public BLEDeviceStatus Status => Services.Any()
- ? BLEDeviceStatus.Connected
- : BLEDeviceStatus.Disconnected;
-
- private string ParseMacAddress(ulong bluetoothAddress)
- {
- var macWithoutColons = bluetoothAddress.ToString("x").PadLeft(12, '0');
- string macWithColons = "";
- for (int i = 0; i < 6; i++)
- {
- if (!string.IsNullOrEmpty(macWithColons))
- macWithColons += ":";
- macWithColons += macWithoutColons.Substring(i * 2, 2).ToUpper();
- }
-
- return macWithColons;
- }
- public void Dispose()
- {
- foreach (var service in Services)
- service.Dispose();
- Services.Clear();
-
- Native?.Dispose();
- Native = null;
- }
- }
-
- public partial class BLE
- {
-
- public CoreObservableCollection<BLEDevice> Devices { get; } = new();
- private readonly BluetoothLEAdvertisementWatcher _scanner;
- private readonly object lockobject = new();
-
- public BLE()
- {
- Task.Run(() =>
- {
-
- while (true)
- {
- lock (lockobject)
- {
- try
- {
-
- var devices = Devices.ToArray();
- foreach (var device in devices)
- {
- if (device.LastSeen < DateTime.Now.Subtract(new TimeSpan(0, 0, 15)))
- {
- Console.WriteLine($"BLE:Clearing Stale Device {device?.Native?.Name ?? "Unknown"}");
- DeviceStatuses.Remove(device?.Native?.BluetoothAddress ?? 0);
- Devices.Remove(device);
- }
- }
- //var stale = devices.Where(x => ).ToArray();
- //if (stale.Any())
- // Devices.RemoveRange(stale);
- }
- catch (Exception e)
- {
- }
-
- }
- Task.Delay(500);
- }
- });
-
- Devices.CollectionChanged += (sender, args) => Changed?.Invoke(this, EventArgs.Empty);
- _scanner = new BluetoothLEAdvertisementWatcher()
- {
- ScanningMode = BluetoothLEScanningMode.Active,
- AllowExtendedAdvertisements = true
- };
-
- _scanner.Stopped += ScannerStopped;
- _scanner.Received += (sender, args) => DoReceived(sender,args);
- }
- // private void CheckForStaleDevices(object? sender, EventArgs e)
- // {
- // foreach (var device in Devices.ToArray())
- // {
- //
- // }
- // }
- private TaskCompletionSource<bool> stopTask = null;
- private void ScannerStopped(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementWatcherStoppedEventArgs args)
- {
- stopTask?.TrySetResult(true);
- }
- public event EventHandler Changed;
-
- private Guid? _serviceUUID;
-
- public async Task<bool> StartScanningAsync(Guid serviceUuid)
- {
- await StopScanningAsync();
- _serviceUUID = serviceUuid;
- Devices.Clear();
- //_scanner.AdvertisementFilter.Advertisement.ManufacturerData.Add(new BluetoothLEManufacturerData() { CompanyId = 0xFFFF });
- // _scanner.AdvertisementFilter.Advertisement.ServiceUuids.Clear();
- // _scanner.AdvertisementFilter.Advertisement.ServiceUuids.Add(serviceUuid); ;
- _scanner.Start();
- return true;
- }
- public async Task<bool> StopScanningAsync()
- {
- if (_scanner.Status == BluetoothLEAdvertisementWatcherStatus.Started)
- {
- TaskCompletionSource<bool> stopTask = new();
- _scanner.Stop();
- await stopTask.Task;
- }
- _serviceUUID = null;
- return true;
- }
- public Dictionary<ulong, byte[]?> DeviceStatuses = new();
-
- private void DoReceived(BluetoothLEAdvertisementWatcher sender, BluetoothLEAdvertisementReceivedEventArgs args)
- {
- bool bChanged = false;
- byte[]? manufacturerData = DeviceStatuses.GetValueOrDefault(args.BluetoothAddress);
- Guid[]? serviceUuids = null;
-
- if (args.Advertisement.ManufacturerData.Any())
- {
- manufacturerData = args.Advertisement.ManufacturerData.LastOrDefault()?.Data.ToArray();
- //Console.WriteLine($"{DateTime.Now:hh:mm:ss} {args.BluetoothAddress} Found Manufacturer Data [{manufacturerData?.Length ?? -1}]");
- DeviceStatuses[args.BluetoothAddress] = manufacturerData;
- }
-
- var bledevice = Devices.FirstOrDefault(x => Equals(x.Native?.BluetoothAddress, args.BluetoothAddress));
- if (bledevice != null)
- {
- Console.WriteLine($"{DateTime.Now:hh:mm:ss} {args.BluetoothAddress} Found Device - Updating Manufacturer Data [{{manufacturerData?.Length ?? -1}}]");
- bledevice.ManufacturerData = DeviceStatuses.GetValueOrDefault(args.BluetoothAddress);
- bledevice.LastSeen = DateTime.Now;
- Changed?.Invoke(this, EventArgs.Empty);
- return;
- }
-
- serviceUuids = args.Advertisement.ServiceUuids.ToArray();
- if (_serviceUUID == null || !serviceUuids.Contains(_serviceUUID.Value))
- return;
-
- serviceUuids = serviceUuids.Where(x => x != _serviceUUID.Value).ToArray();
- Console.WriteLine($"{DateTime.Now:hh:mm:ss} {args.BluetoothAddress} Found Advertisement [{string.Join(", ",serviceUuids)}]");
- TaskCompletionSource<BluetoothLEDevice?> tcs = new TaskCompletionSource<BluetoothLEDevice?>();
- Task.Run(async () =>
- {
- var result = await BluetoothLEDevice.FromBluetoothAddressAsync(args.BluetoothAddress);
- tcs.SetResult(result);
- });
- var device = tcs.Task.Result;
- if (device == null)
- return;
-
- Console.WriteLine($"{DateTime.Now:hh:mm:ss} {args.BluetoothAddress} Creating New Device");
- bledevice = new BLEDevice(device, serviceUuids.Where(x => x != _serviceUUID).ToArray())
- {
- LastSeen = args.Timestamp.DateTime,
- ManufacturerData = DeviceStatuses.GetValueOrDefault(args.BluetoothAddress)
- };
- Devices.RemoveAll(x => x.Native == null);
- if (Devices.Any(x => string.Equals(x.MacAddress, bledevice.MacAddress)))
- {
- Console.WriteLine($"{DateTime.Now:hh:mm:ss} {args.BluetoothAddress} Duplicate MAC Address [{bledevice.MacAddress}] - skipping");
- return;
- }
- Devices.Add(bledevice);
- Changed?.Invoke(this, EventArgs.Empty);
-
- // string? name = null;
- // List<Guid> serviceUUIDs = new();
- // byte[]? mfgData = null;
- // foreach (var dataSection in args.Advertisement.DataSections)
- // {
- // if (dataSection.DataType == 0x09)
- // {
- // var namebytes = new byte[dataSection.Data.Length];
- // using (var reader = DataReader.FromBuffer(dataSection.Data))
- // reader.ReadBytes(namebytes);
- // name = Encoding.ASCII.GetString(namebytes);
- // }
- // else if (dataSection.DataType == 0x07)
- // {
- // byte[] Guid_A = new byte[4];
- // byte[] Guid_B = new byte[2];
- // byte[] Guid_C = new byte[2];
- // byte[] Guid_D = new byte[8];
- // using (var reader = DataReader.FromBuffer(dataSection.Data))
- // {
- // reader.ReadBytes(Guid_A);
- // reader.ReadBytes(Guid_B);
- // reader.ReadBytes(Guid_C);
- // reader.ReadBytes(Guid_D);
- // }
- // Guid serviceid = new Guid(
- // BitConverter.ToInt32(Guid_A.Reverse().ToArray(), 0),
- // BitConverter.ToInt16(Guid_B.Reverse().ToArray(), 0),
- // BitConverter.ToInt16(Guid_C.Reverse().ToArray(), 0),
- // Guid_D);
- // serviceUUIDs.Add(serviceid);
- // }
- // else if (dataSection.DataType == 0x05)
- // {
- // var uuid32bytes = new byte[dataSection.Data.Length];
- // using (var reader = DataReader.FromBuffer(dataSection.Data))
- // reader.ReadBytes(uuid32bytes);
- // var uuid32int = BitConverter.ToInt32(uuid32bytes.ToArray(), 0);
- // var uuid32string = $"{uuid32int:X}-0000-1000-8000-00805F9B34FB";
- // serviceUUIDs.Add(Guid.Parse(uuid32string));
- // }
- // else if (dataSection.DataType == 0xFF)
- // {
- // mfgData = new byte[dataSection.Data.Length];
- // using (var reader = DataReader.FromBuffer(dataSection.Data))
- // reader.ReadBytes(mfgData);
- // }
- // }
-
-
- }
- /// <summary>
- /// Connect to the specific device by name or number, and make this device current
- /// </summary>
- /// <param name="device"></param>
- /// <returns></returns>
- public async Task<ConnectDeviceResult> Connect(BLEDevice device)
- {
- try
- {
- var result = await device.Native.GetGattServicesAsync(BluetoothCacheMode.Uncached);
- if (result.Status == GattCommunicationStatus.Success)
- {
- foreach (var t in result.Services)
- {
- var service = new BLEService(t);
- var accessStatus = await service.Native.RequestAccessAsync();
- if (accessStatus == DeviceAccessStatus.Allowed)
- {
- // BT_Code: Get all the child characteristics of a service. Use the cache mode to specify uncached characterstics only
- // and the new Async functions to get the characteristics of unpaired devices as well.
- var getCharacteristicResult =
- await service.Native.GetCharacteristicsAsync(BluetoothCacheMode.Uncached);
- if (getCharacteristicResult.Status == GattCommunicationStatus.Success)
- {
- foreach (var characteristic in getCharacteristicResult.Characteristics)
- service.Characteristics.Add(new BLECharacteristic(characteristic));
- }
- device.Services.Add(service);
- }
- }
- }
- return result.Status == GattCommunicationStatus.Success ? ConnectDeviceResult.Ok : ConnectDeviceResult.Unreachable;;
- }
- catch (Exception e)
- {
- return ConnectDeviceResult.Error;
- }
- }
-
- /// <summary>
- /// Disconnect current device and clear list of services and characteristics
- /// </summary>
- public void Disconnect(BLEDevice? device)
- {
- device?.Dispose();
- device = null;
- }
-
-
- }
- }
|