Beispiel #1
0
        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);
 }
Beispiel #15
0
        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;
            }
        }
Beispiel #16
0
        public override void OnReliableWriteCompleted(BluetoothGatt gatt, GattStatus status)
        {
            base.OnReliableWriteCompleted(gatt, status);

            Trace.Message("OnReliableWriteCompleted: {0}", status);
        }