public static AndroidScanMode ToNative(this ScanMode scanMode) { if (Build.VERSION.SdkInt < BuildVersionCodes.Lollipop) { throw new InvalidOperationException("Scan modes are not implemented in API lvl < 21."); } switch (scanMode) { case ScanMode.Passive: if (Build.VERSION.SdkInt < BuildVersionCodes.M) { Trace.Message("Scanmode Passive is not supported on API lvl < 23. Falling back to LowPower."); return(AndroidScanMode.LowPower); } return(AndroidScanMode.Opportunistic); case ScanMode.LowPower: return(AndroidScanMode.LowPower); case ScanMode.Balanced: return(AndroidScanMode.Balanced); case ScanMode.LowLatency: return(AndroidScanMode.LowLatency); default: throw new ArgumentOutOfRangeException(nameof(scanMode), scanMode, null); } }
protected Task StopUpdatesNativeAsync() { var exception = new Exception($"Device {Service.Device.Id} disconnected while stopping updates for characteristic with {Id}."); _parentDevice.UpdatedCharacterteristicValue -= UpdatedNotify; return(TaskBuilder.FromEvent <bool, EventHandler <CBCharacteristicEventArgs>, EventHandler <CBPeripheralErrorEventArgs> >( execute: () => { if (_parentDevice.State != CBPeripheralState.Connected) { throw exception; } _parentDevice.SetNotifyValue(false, NativeCharacteristic); }, getCompleteHandler: (complete, reject) => (sender, args) => { if (args.Characteristic.UUID != NativeCharacteristic.UUID) { return; } if (args.Error != null) { reject(new Exception($"Stop Notifications: Error {args.Error.Description}")); } else { Trace.Message($"StopUpdates IsNotifying: {args.Characteristic.IsNotifying}"); complete(args.Characteristic.IsNotifying); } }, subscribeComplete: handler => _parentDevice.UpdatedNotificationState += handler, unsubscribeComplete: handler => _parentDevice.UpdatedNotificationState -= handler, getRejectHandler: reject => ((sender, args) => { if (args.Peripheral.Identifier == _parentDevice.Identifier) { reject(exception); } }), subscribeReject: handler => _bleCentralManagerDelegate.DisconnectedPeripheral += handler, unsubscribeReject: handler => _bleCentralManagerDelegate.DisconnectedPeripheral -= handler)); }
protected Task StartScanningForDevicesNativeAsync(Guid[] serviceUuids, bool allowDuplicatesKey, CancellationToken scanCancellationToken) { _serviceUuids = serviceUuids; _bleWatcher = new BluetoothLEAdvertisementWatcher { ScanningMode = ScanMode.ToNative() }; Trace.Message("Starting a scan for devices."); _foundIds = new List <Guid>(); _bleWatcher.Received -= DeviceFoundAsync; _bleWatcher.Received += DeviceFoundAsync; _bleWatcher.Start(); return(Task.FromResult(true)); }
protected Task <byte[]> ReadNativeAsync() { var exception = new Exception($"Device '{Service.Device.Id}' disconnected while reading characteristic with {Id}."); return(TaskBuilder.FromEvent <byte[], EventHandler <CBCharacteristicEventArgs>, EventHandler <CBPeripheralErrorEventArgs> >( execute: () => { if (_parentDevice.State != CBPeripheralState.Connected) { throw exception; } _parentDevice.ReadValue(NativeCharacteristic); }, getCompleteHandler: (complete, reject) => (sender, args) => { if (args.Characteristic.UUID != NativeCharacteristic.UUID) { return; } if (args.Error != null) { reject(new CharacteristicReadException($"Read async error: {args.Error.Description}")); } else { Trace.Message($"Read characterteristic value: {Value?.ToHexString()}"); complete(Value); } }, subscribeComplete: handler => _parentDevice.UpdatedCharacterteristicValue += handler, unsubscribeComplete: handler => _parentDevice.UpdatedCharacterteristicValue -= handler, getRejectHandler: reject => ((sender, args) => { if (args.Peripheral.Identifier == _parentDevice.Identifier) { reject(exception); } }), subscribeReject: handler => _bleCentralManagerDelegate.DisconnectedPeripheral += handler, unsubscribeReject: handler => _bleCentralManagerDelegate.DisconnectedPeripheral -= handler)); }
public void ClearServices() { this.CancelEverythingAndReInitialize(); lock (KnownServices) { foreach (var service in KnownServices) { try { service.Dispose(); } catch (Exception ex) { Trace.Message("Exception while cleanup of service: {0}", ex.Message); } } KnownServices.Clear(); } }
protected Task ConnectToDeviceNativeAsync(Device device, ConnectParameters connectParameters, CancellationToken cancellationToken) { if (connectParameters.AutoConnect) { Trace.Message("Warning: Autoconnect is not supported in iOS"); } _deviceOperationRegistry[device.Id.ToString()] = device; _centralManager.ConnectPeripheral(device.NativeDevice as CBPeripheral, new PeripheralConnectionOptions()); // this is dirty: We should not assume, AdapterBase is doing the cleanup for us... // move ConnectToDeviceAsync() code to native implementations. cancellationToken.Register(() => { Trace.Message("Canceling the connect attempt"); _centralManager.CancelPeripheralConnection(device.NativeDevice as CBPeripheral); }); return(Task.FromResult(true)); }
protected async Task StartScanningForDevicesNativeAsync(Guid[] serviceUuids, bool allowDuplicatesKey, CancellationToken scanCancellationToken) { // Wait for the PoweredOn state await WaitForState(CBCentralManagerState.PoweredOn, scanCancellationToken).ConfigureAwait(false); if (scanCancellationToken.IsCancellationRequested) { throw new TaskCanceledException("StartScanningForDevicesNativeAsync cancelled"); } Trace.Message("Adapter: Starting a scan for devices."); CBUUID[] serviceCbuuids = null; if (serviceUuids != null && serviceUuids.Any()) { serviceCbuuids = serviceUuids.Select(u => CBUUID.FromString(u.ToString())).ToArray(); Trace.Message("Adapter: Scanning for " + serviceCbuuids.First()); } _centralManager.ScanForPeripherals(serviceCbuuids, new PeripheralScanningOptions { AllowDuplicatesKey = allowDuplicatesKey }); }
private void OnNameUpdated(object sender, System.EventArgs e) { Name = ((CBPeripheral)sender).Name; Trace.Message("Device changed name: {0}", Name); }
private bool UpdateConnectionIntervalNative(ConnectionInterval interval) { Trace.Message("Cannot update connection inteval on iOS."); return(false); }
private async Task <int> RequestMtuNativeAsync(int requestValue) { Trace.Message($"Request MTU is not supported on iOS."); return(await Task.FromResult((int)NativeDevice.GetMaximumWriteValueLength(CBCharacteristicWriteType.WithoutResponse))); }
public static List <AdvertisementRecord> ParseAdvertismentData(NSDictionary advertisementData) { var records = new List <AdvertisementRecord>(); /*var keys = new List<NSString> * { * CBAdvertisement.DataLocalNameKey, * CBAdvertisement.DataManufacturerDataKey, * CBAdvertisement.DataOverflowServiceUUIDsKey, //ToDo ??which one is this according to ble spec * CBAdvertisement.DataServiceDataKey, * CBAdvertisement.DataServiceUUIDsKey, * CBAdvertisement.DataSolicitedServiceUUIDsKey, * CBAdvertisement.DataTxPowerLevelKey * };*/ foreach (var o in advertisementData.Keys) { var key = (NSString)o; if (key == CBAdvertisement.DataLocalNameKey) { records.Add(new AdvertisementRecord(AdvertisementRecordType.CompleteLocalName, NSData.FromString(advertisementData.ObjectForKey(key) as NSString).ToArray())); } else if (key == CBAdvertisement.DataManufacturerDataKey) { var arr = ((NSData)advertisementData.ObjectForKey(key)).ToArray(); records.Add(new AdvertisementRecord(AdvertisementRecordType.ManufacturerSpecificData, arr)); } else if (key == CBAdvertisement.DataServiceUUIDsKey || key == CBAdvertisement.DataOverflowServiceUUIDsKey) { var array = (NSArray)advertisementData.ObjectForKey(key); for (nuint i = 0; i < array.Count; i++) { var cbuuid = array.GetItem <CBUUID>(i); switch (cbuuid.Data.Length) { case 16: // 128-bit UUID records.Add(new AdvertisementRecord(AdvertisementRecordType.UuidsComplete128Bit, cbuuid.Data.ToArray())); break; case 8: // 32-bit UUID records.Add(new AdvertisementRecord(AdvertisementRecordType.UuidCom32Bit, cbuuid.Data.ToArray())); break; case 2: // 16-bit UUID records.Add(new AdvertisementRecord(AdvertisementRecordType.UuidsComplete16Bit, cbuuid.Data.ToArray())); break; default: // Invalid data length for UUID break; } } } else if (key == CBAdvertisement.DataTxPowerLevelKey) { //iOS stores TxPower as NSNumber. Get int value of number and convert it into a signed Byte //TxPower has a range from -100 to 20 which can fit into a single signed byte (-128 to 127) sbyte byteValue = Convert.ToSByte(((NSNumber)advertisementData.ObjectForKey(key)).Int32Value); //add our signed byte to a new byte array and return it (same parsed value as android returns) byte[] arr = { (byte)byteValue }; records.Add(new AdvertisementRecord(AdvertisementRecordType.TxPowerLevel, arr)); } else if (key == CBAdvertisement.DataServiceDataKey) { //Service data from CoreBluetooth is returned as a key/value dictionary with the key being //the service uuid (CBUUID) and the value being the NSData (bytes) of the service //This is where you'll find eddystone and other service specific data NSDictionary serviceDict = (NSDictionary)advertisementData.ObjectForKey(key); //There can be multiple services returned in the dictionary, so loop through them foreach (CBUUID dKey in serviceDict.Keys) { //Get the service key in bytes (from NSData) byte[] keyAsData = dKey.Data.ToArray(); //Service UUID's are read backwards (little endian) according to specs, //CoreBluetooth returns the service UUIDs as Big Endian //but to match the raw service data returned from Android we need to reverse it back //Note haven't tested it yet on 128bit service UUID's, but should work Array.Reverse(keyAsData); //The service data under this key can just be turned into an arra var data = (NSData)serviceDict.ObjectForKey(dKey); byte[] valueAsData = data.Length > 0 ? data.ToArray() : new byte[0]; //Now we append the key and value data and return that so that our parsing matches the raw //byte value returned from the Android library (which matches the raw bytes from the device) byte[] arr = new byte[keyAsData.Length + valueAsData.Length]; Buffer.BlockCopy(keyAsData, 0, arr, 0, keyAsData.Length); Buffer.BlockCopy(valueAsData, 0, arr, keyAsData.Length, valueAsData.Length); records.Add(new AdvertisementRecord(AdvertisementRecordType.ServiceData, arr)); } } else if (key == CBAdvertisement.IsConnectable) { // A Boolean value that indicates whether the advertising event type is connectable. // The value for this key is an NSNumber object. You can use this value to determine whether a peripheral is connectable at a particular moment. records.Add(new AdvertisementRecord(AdvertisementRecordType.IsConnectable, new byte[] { ((NSNumber)advertisementData.ObjectForKey(key)).ByteValue })); } else { Trace.Message($"Parsing Advertisement: Ignoring Advertisement entry for key {key}, since we don't know how to parse it yet. Maybe you can open a Pull Request and implement it ;)"); } } return(records); }
internal Adapter(CBCentralManager centralManager, IBleCentralManagerDelegate bleCentralManagerDelegate) { _centralManager = centralManager; _bleCentralManagerDelegate = bleCentralManagerDelegate; _bleCentralManagerDelegate.DiscoveredPeripheral += (sender, e) => { Trace.Message("DiscoveredPeripheral: {0}, Id: {1}", e.Peripheral.Name, e.Peripheral.Identifier); var name = e.Peripheral.Name; if (e.AdvertisementData.ContainsKey(CBAdvertisement.DataLocalNameKey)) { // iOS caches the peripheral name, so it can become stale (if changing) // keep track of the local name key manually name = ((NSString)e.AdvertisementData.ValueForKey(CBAdvertisement.DataLocalNameKey)).ToString(); } var device = new Device(this, e.Peripheral, _bleCentralManagerDelegate, name, e.RSSI.Int32Value, ParseAdvertismentData(e.AdvertisementData)); HandleDiscoveredDevice(device); }; _bleCentralManagerDelegate.UpdatedState += (sender, e) => { Trace.Message("UpdatedState: {0}", _centralManager.State); _stateChanged.Set(); //handle PoweredOff state //notify subscribers about disconnection if (_centralManager.State == CBCentralManagerState.PoweredOff) { foreach (var device in ConnectedDeviceRegistry.Values.ToList()) { ((Device)device).ClearServices(); HandleDisconnectedDevice(false, device); } ConnectedDeviceRegistry.Clear(); } }; _bleCentralManagerDelegate.ConnectedPeripheral += (sender, e) => { Trace.Message("ConnectedPeripherial: {0}", e.Peripheral.Name); // when a peripheral gets connected, add that peripheral to our running list of connected peripherals var guid = ParseDeviceGuid(e.Peripheral).ToString(); Device device; if (_deviceOperationRegistry.TryGetValue(guid, out device)) { _deviceOperationRegistry.Remove(guid); ((Device)device).Update(e.Peripheral); } else { Trace.Message("Device not found in operation registry. Creating a new one."); device = new Device(this, e.Peripheral, _bleCentralManagerDelegate); } ConnectedDeviceRegistry[guid] = device; HandleConnectedDevice(device); }; _bleCentralManagerDelegate.DisconnectedPeripheral += (sender, e) => { if (e.Error != null) { Trace.Message("Disconnect error {0} {1} {2}", e.Error.Code, e.Error.Description, e.Error.Domain); } // when a peripheral disconnects, remove it from our running list. var id = ParseDeviceGuid(e.Peripheral); var stringId = id.ToString(); // normal disconnect (requested by user) var isNormalDisconnect = _deviceOperationRegistry.TryGetValue(stringId, out var foundDevice); if (isNormalDisconnect) { _deviceOperationRegistry.Remove(stringId); } // check if it is a peripheral disconnection, which would be treated as normal if (e.Error != null && e.Error.Code == 7 && e.Error.Domain == "CBErrorDomain") { isNormalDisconnect = true; } // remove from connected devices if (!ConnectedDeviceRegistry.TryRemove(stringId, out foundDevice)) { Trace.Message($"Device with id '{stringId}' was not found in the connected device registry. Nothing to remove."); } foundDevice = foundDevice ?? new Device(this, e.Peripheral, _bleCentralManagerDelegate); //make sure all cached services are cleared this will also clear characteristics and descriptors implicitly ((Device)foundDevice).ClearServices(); HandleDisconnectedDevice(isNormalDisconnect, foundDevice); }; _bleCentralManagerDelegate.FailedToConnectPeripheral += (sender, e) => { var id = ParseDeviceGuid(e.Peripheral); var stringId = id.ToString(); // remove instance from registry if (_deviceOperationRegistry.TryGetValue(stringId, out var foundDevice)) { _deviceOperationRegistry.Remove(stringId); } foundDevice = foundDevice ?? new Device(this, e.Peripheral, _bleCentralManagerDelegate); HandleConnectionFail(foundDevice, e.Error.Description); }; }
private Task <int> RequestMtuNativeAsync(int requestValue) { Trace.Message("Request MTU not supported in UWP"); return(Task.FromResult(-1)); }
private bool UpdateConnectionIntervalNative(ConnectionInterval interval) { Trace.Message("Update Connection Interval not supported in UWP"); return(false); }
public override void OnConnectionStateChange(BluetoothGatt gatt, GattStatus status, ProfileState newState) { base.OnConnectionStateChange(gatt, status, newState); if (!gatt.Device.Address.Equals(_device.NativeDevice.Address)) { Trace.Message($"Gatt callback for device {_device.NativeDevice.Address} was called for device with address {gatt.Device.Address}. This shoud not happen. Please log an issue."); return; } //ToDo ignore just for me Trace.Message($"References of parent device and gatt callback device equal? {ReferenceEquals(_device.NativeDevice, gatt.Device).ToString().ToUpper()}"); Trace.Message($"OnConnectionStateChange: GattStatus: {status}"); switch (newState) { // disconnected case ProfileState.Disconnected: // Close GATT regardless, else we can accumulate zombie gatts. CloseGattInstances(gatt); // If status == 19, then connection was closed by the peripheral device (clean disconnect), consider this as a DeviceDisconnected if (_device.IsOperationRequested || (int)status == 19) { Trace.Message("Disconnected by user"); //Found so we can remove it _device.IsOperationRequested = false; _adapter.ConnectedDeviceRegistry.TryRemove(gatt.Device.Address, out _); if (status != GattStatus.Success && (int)status != 19) { // The above error event handles the case where the error happened during a Connect call, which will close out any waiting asyncs. // Android > 5.0 uses this switch branch when an error occurs during connect Trace.Message($"Error while connecting '{_device.Name}'. Not raising disconnect event."); _adapter.HandleConnectionFail(_device, $"GattCallback error: {status}"); } else { //we already hadled device error so no need th raise disconnect event(happens when device not in range) _adapter.HandleDisconnectedDevice(true, _device); } break; } //connection must have been lost, because the callback was not triggered by calling disconnect Trace.Message($"Disconnected '{_device.Name}' by lost connection"); _adapter.ConnectedDeviceRegistry.TryRemove(gatt.Device.Address, out _); _adapter.HandleDisconnectedDevice(false, _device); // inform pending tasks ConnectionInterrupted?.Invoke(this, System.EventArgs.Empty); break; // connecting case ProfileState.Connecting: Trace.Message("Connecting"); break; // connected case ProfileState.Connected: Trace.Message("Connected"); //Check if the operation was requested by the user if (_device.IsOperationRequested) { _device.Update(gatt.Device, gatt); //Found so we can remove it _device.IsOperationRequested = false; } else { //ToDo explore this //only for on auto-reconnect (device is not in operation registry) _device.Update(gatt.Device, gatt); } if (status != GattStatus.Success) { // The above error event handles the case where the error happened during a Connect call, which will close out any waiting asyncs. // Android <= 4.4 uses this switch branch when an error occurs during connect Trace.Message($"Error while connecting '{_device.Name}'. GattStatus: {status}. "); _adapter.HandleConnectionFail(_device, $"GattCallback error: {status}"); CloseGattInstances(gatt); } else { _adapter.ConnectedDeviceRegistry[gatt.Device.Address] = _device; _adapter.HandleConnectedDevice(_device); } break; // disconnecting case ProfileState.Disconnecting: Trace.Message("Disconnecting"); break; } }
public override void OnReliableWriteCompleted(BluetoothGatt gatt, GattStatus status) { base.OnReliableWriteCompleted(gatt, status); Trace.Message("OnReliableWriteCompleted: {0}", status); }