-
Notifications
You must be signed in to change notification settings - Fork 43
Expand file tree
/
Copy pathBluetoothLEService.cs
More file actions
179 lines (151 loc) · 6.33 KB
/
Copy pathBluetoothLEService.cs
File metadata and controls
179 lines (151 loc) · 6.33 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using CoreBluetooth;
using CoreFoundation;
using Foundation;
using BrickController2.PlatformServices.BluetoothLE;
using static BrickController2.Protocols.BluetoothLowEnergy;
namespace BrickController2.iOS.PlatformServices.BluetoothLE
{
public class BluetoothLEService : CBCentralManagerDelegate, IBluetoothLEService
{
private readonly CBCentralManager _centralManager;
private readonly IDictionary<CBPeripheral, BluetoothLEDevice> _peripheralMap = new Dictionary<CBPeripheral, BluetoothLEDevice>();
private readonly object _lock = new();
private Action<ScanResult>? _scanCallback;
public BluetoothLEService()
{
#pragma warning disable CA1422 // Validate platform compatibility
_centralManager = new CBCentralManager(this, DispatchQueue.CurrentQueue);
#pragma warning restore CA1422 // Validate platform compatibility
}
public bool IsBluetoothLESupported => true;
public bool IsBluetoothOn => _centralManager.State == CBManagerState.PoweredOn;
public async Task<bool> ScanDevicesAsync(Action<ScanResult> scanCallback, CancellationToken token)
{
if (!IsBluetoothLESupported || !IsBluetoothOn || _centralManager.IsScanning)
{
return false;
}
var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
using (token.Register(() =>
{
lock (_lock)
{
_centralManager.StopScan();
_scanCallback = null;
tcs.TrySetResult(true);
}
}))
{
_scanCallback = scanCallback;
_centralManager.ScanForPeripherals(Array.Empty<CBUUID>(), new PeripheralScanningOptions { AllowDuplicatesKey = true });
return await tcs.Task;
}
}
public IBluetoothLEDevice? GetKnownDevice(string address)
{
var peripheral = _centralManager?.RetrievePeripheralsWithIdentifiers(new NSUuid(address)).FirstOrDefault();
if (peripheral is null)
{
return null;
}
var device = new BluetoothLEDevice(_centralManager!, peripheral);
_peripheralMap[peripheral] = device;
return device;
}
public override void UpdatedState(CBCentralManager central)
{
}
public override void DiscoveredPeripheral(CBCentralManager central, CBPeripheral peripheral, NSDictionary advertisementData, NSNumber RSSI)
{
lock(_lock)
{
if (peripheral is null || peripheral.Identifier is null || string.IsNullOrEmpty(peripheral.Name))
{
return;
}
var processedAdvertisementData = ProcessAdvertisementData(advertisementData);
_scanCallback?.Invoke(new ScanResult(peripheral.Name, peripheral.Identifier.ToString(), processedAdvertisementData));
}
}
public override void ConnectedPeripheral(CBCentralManager central, CBPeripheral peripheral)
{
var device = _peripheralMap[peripheral];
device.OnDeviceConnected();
}
public override void DisconnectedPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError? error)
{
var device = _peripheralMap[peripheral];
device.OnDeviceDisconnected();
}
public override void FailedToConnectPeripheral(CBCentralManager central, CBPeripheral peripheral, NSError? error)
{
var device = _peripheralMap[peripheral];
device.OnDeviceDisconnected();
}
private IDictionary<byte, byte[]> ProcessAdvertisementData(NSDictionary advertisementData)
{
var result = new Dictionary<byte, byte[]>();
var manufacturerData = GetDataForKey(advertisementData, CBAdvertisement.DataManufacturerDataKey);
if (manufacturerData is not null)
{
result[ADTYPE_MANUFACTURER_SPECIFIC] = manufacturerData;
}
var completeDeviceName = GetDataForKey(advertisementData, CBAdvertisement.DataLocalNameKey);
if (completeDeviceName is not null)
{
result[ADTYPE_LOCAL_NAME_COMPLETE] = completeDeviceName;
}
var serviceUuid = GetServiceUuidForKey(advertisementData, CBAdvertisement.DataServiceUUIDsKey);
if (serviceUuid is not null)
{
result[ADTYPE_SERVICE_128BIT] = serviceUuid;
}
// TODO: add the rest of the advertisementdata...
return result;
}
private byte[]? GetDataForKey(NSDictionary advertisementData, NSString key)
{
if (advertisementData == null || !advertisementData.ContainsKey(key))
{
return null;
}
var rawObject = advertisementData[key];
if (rawObject is NSData dataObject)
{
return dataObject.ToArray();
}
else if (rawObject is NSString stringObject)
{
return Encoding.ASCII.GetBytes(stringObject.ToString());
}
return null;
}
private static byte[]? GetServiceUuidForKey(NSDictionary advertisementData, NSString key)
{
if (advertisementData != null &&
advertisementData.TryGetValue(key, out var rawObject) &&
rawObject is NSArray arrayObject)
{
// find first available 128-bit UUID
for (nuint i = 0; i < arrayObject.Count; i++)
{
var cbuuid = arrayObject.GetItem<CBUUID>(i);
if (cbuuid.Data.Length == 16)
{
// Service UUID's are read backwards (little endian) according to specs
var serviceUUid = cbuuid.Data.ToArray();
Array.Reverse(serviceUUid);
return serviceUUid;
}
}
}
return null;
}
}
}