public async Task DisconnectDevice(IAxLE device)
        {
            var bleDevice = _devices[device.SerialNumber];
            await _ble.DisconnectDevice(bleDevice);

            device.Dispose();
        }
        public async Task DisconnectDevice(IAxLE device)
        {
            await device.LEDFlash(); // Await LED flash to ensure command buffer is cleared before disconnect.

            var bleDevice = _devices[device.SerialNumber];
            await _ble.DisconnectDevice(bleDevice);

            device.Dispose();
        }
        private async Task ConnectToDevice(DeploymentUser dUser)
        {
            DeploymentUser deploymentUser = _deploymentUsers.SingleOrDefault(du => du.device.macAddress == dUser.device.macAddress);

            // if deploymentUser is found
            if (deploymentUser != default(DeploymentUser))
            {
                IAxLE device = null;
                try
                {
                    await _axLEManager.StopScan();

                    Console.WriteLine($"CONNECTION: ConnectDevice()");

                    for (var retry = 0; ; retry++)
                    {
                        try
                        {
                            device = await _axLEManager.ConnectDevice(deploymentUser.device.macAddress);

                            break;
                        }
                        catch (OpenMovement.AxLE.Comms.Exceptions.CommandFailedException e)
                        {
                            Console.WriteLine($"CONNECTION: GATT error, retry {retry}");
                            Crashes.TrackError(e);
                            if (retry > 10)
                            {
                                throw;
                            }
                        }
                        catch (OpenMovement.AxLE.Comms.Exceptions.ConnectException e)
                        {
                            Console.WriteLine($"CONNECTION: ConnectException error, retry {retry}");
                            Crashes.TrackError(e);
                            if (retry > 10)
                            {
                                throw;
                            }
                        }
                    }

                    // try auth with known password
                    Console.WriteLine($"CONNECTION: Authenticate()");
                    bool authSuccess = await device.Authenticate(deploymentUser.device.password);

                    // if fails, reset device
                    if (!authSuccess)
                    {
                        Console.WriteLine($"CONNECTION: ResetPassword()");
                        await device.ResetPassword();

                        Console.WriteLine($"CONNECTION: Authenticate()");
                        await device.Authenticate(device.SerialNumber.Substring(device.SerialNumber.Length - 6));

                        Console.WriteLine($"CONNECTION: SetPassword()");
                        await device.SetPassword(deploymentUser.device.password);

                        deploymentUser.device.lastBlockSynced = null;
                        deploymentUser.device.lastRTC         = null;
                        deploymentUser.device.lastSyncTime    = null;
                    }

                    // buzz device
                    Console.WriteLine($"FLASH AND BUZZ START:{ DateTime.Now }");
                    //await device.LEDFlash();
                    await device.VibrateDevice();

                    Console.WriteLine($"FLASH AND BUZZ END:{ DateTime.Now }");

                    // HACK: Use this butchered version to skip updating cueing
                    //await ((OpenMovement.AxLE.Comms.AxLEv1_5)device).UpdateDeviceState((uint)(0xffffffff & ~0x0010));   // Don't update cueing status
                    await device.UpdateDeviceState();

                    // proceed with sync and update deployment user
                    if (device.EpochPeriod != 300)
                    {
                        device.EpochPeriod = 300; // 5 minutes (300 seconds)
                    }

                    if (!deploymentUser.device.lastBlockSynced.HasValue || !deploymentUser.device.lastRTC.HasValue || !deploymentUser.device.lastSyncTime.HasValue)
                    {
                        BlockDetails blockDetails = await device.ReadBlockDetails();

                        deploymentUser.device.lastBlockSynced = blockDetails.ActiveBlock;
                        deploymentUser.device.lastRTC         = blockDetails.Time;
                        deploymentUser.device.lastSyncTime    = DateTime.UtcNow;
                        // send off server request to update these details
                        DeviceSyncAttempt _deviceSyncAttempt = new DeviceSyncAttempt
                        {
                            deploymentUserId   = deploymentUser.id,
                            batteryLevel       = device.Battery,
                            deploymentDeviceId = deploymentUser.device.id,
                            samples            = new DeviceSampleRecord[0],
                            lastBlockSynced    = (blockDetails.ActiveBlock),
                            lastRTC            = blockDetails.Time,
                            lastSyncTime       = DateTime.UtcNow,
                            raw = new byte[0][]
                        };
                        RunOnUiThread(() =>
                        {
                            _webViewClient.RecievedData(_webView, _deviceSyncAttempt);
                        });

                        WriteSyncAttemptToDisk(deploymentUser.deploymentId, deploymentUser.device.macAddress, _deviceSyncAttempt);
                    }
                    else
                    {
                        var start = DateTime.Now;
                        // we have data on the device, sync the data using previous deploymentUser.device...
                        Console.WriteLine($"BLOCK READ START:{ DateTime.Now }");
                        var blocks = await device.SyncEpochData((UInt16)deploymentUser.device.lastBlockSynced, (UInt32)deploymentUser.device.lastRTC, (DateTimeOffset)deploymentUser.device.lastSyncTime);

                        Console.WriteLine($"BLOCK READ END:{ DateTime.Now }");
                        var end = DateTime.Now;

                        /*
                         * {
                         *      deploymentUserId: 15,
                         *      batteryLevel: 15,
                         *      deploymentDeviceId: 1,
                         *      epochInterval: 60000
                         *      samples: [
                         *          {
                         *              steps: 123,
                         *              batteryLevel: 15
                         *              recordedOn: 2018-05-31 14:23:01Z4
                         *          }
                         *      ],
                         *      raw: "RAW BYTES FROM SYNC"
                         * }
                         */
                        DeviceSampleRecord[] samples = blocks.SelectMany(b =>
                        {
                            var deviceSampleRecords = new List <DeviceSampleRecord>();
                            ulong samplesLength     = (ulong)b.Samples.Length;
                            for (ulong i = 0; i < samplesLength; i++)
                            {
                                deviceSampleRecords.Add(new DeviceSampleRecord()
                                {
                                    steps        = b.Samples[i].Steps,
                                    batteryLevel = b.Samples[i].Battery,
                                    recordedOn   = b.BlockInfo.Timestamp.AddSeconds(i * b.BlockInfo.EpochPeriod)
                                });
                            }

                            return(deviceSampleRecords);
                        }).ToArray();

                        var lastBlock = blocks.LastOrDefault();

                        var lastBlockNumber = lastBlock == default(EpochBlock) ?
                                              deploymentUser.device.lastBlockSynced : lastBlock.BlockInfo.BlockNumber;

                        DeviceSyncAttempt _deviceSyncAttempt = new DeviceSyncAttempt
                        {
                            deploymentUserId   = deploymentUser.id,
                            batteryLevel       = device.Battery,
                            deploymentDeviceId = deploymentUser.device.id,
                            samples            = samples,
                            lastBlockSynced    = (ushort)lastBlockNumber,
                            lastRTC            = device.DeviceTime,
                            lastSyncTime       = DateTime.UtcNow,
                            raw = blocks.Select(b => b.Raw).ToArray()
                        };

                        WriteSyncAttemptToDisk(deploymentUser.deploymentId, deploymentUser.device.macAddress, _deviceSyncAttempt);

                        // save locally
                        // post data off to tablet
                        RunOnUiThread(() =>
                        {
                            _webViewClient.RecievedData(_webView, _deviceSyncAttempt);
                        });
                    }
                }
                catch (BlockSyncFailedException e)
                {
                    // move read head to active block
                    Console.WriteLine("BlockSyncFailedException:" + e.ToString());
                    Crashes.TrackError(e);
                    RunOnUiThread(() =>
                    {
                        _webViewClient.RequestDataFailed(_webView);
                    });
                }
                catch (DeviceNotInRangeException e)
                {
                    // display not in range
                    Console.WriteLine("DeviceNotInRangeException:" + e.ToString());
                    Crashes.TrackError(e);
                    RunOnUiThread(() =>
                    {
                        _webViewClient.RequestDataFailed(_webView);
                    });
                }
                catch (CommandFailedException e)
                {
                    // display not in range
                    Console.WriteLine("Command Failed Exception:" + e.ToString());
                    Crashes.TrackError(e);
                    RunOnUiThread(() =>
                    {
                        _webViewClient.RequestDataFailed(_webView);
                    });
                }
                catch (Exception e)
                {
                    Console.WriteLine("Exception:" + e.ToString());
                    Crashes.TrackError(e);
                    // restart this process
                    RunOnUiThread(() =>
                    {
                        _webViewClient.RequestDataFailed(_webView);
                    });
                }
                finally
                {
                    if (device != null)
                    {
                        await _axLEManager.DisconnectDevice(device);
                    }

                    //DisableBluetooth();
                }
            }
            else
            {
                Toast.MakeText(ApplicationContext, "Could not connect to your activity tracker", ToastLength.Short).Show();
            }
        }
        private async Task VibrateDevice(string macAddress)
        {
            IAxLE device = null;

            try
            {
                Console.WriteLine($"VIBRATE_DEVICE: ConnectDevice()");

                for (var retry = 0; ; retry++)
                {
                    try
                    {
                        device = await _axLEManager.ConnectDevice(macAddress);

                        break;
                    }
                    catch (OpenMovement.AxLE.Comms.Exceptions.CommandFailedException e)
                    {
                        Console.WriteLine($"VIBRATE_DEVICE: GATT error, retry {retry}");
                        Crashes.TrackError(e);
                        if (retry > 10)
                        {
                            throw;
                        }
                    }
                    catch (OpenMovement.AxLE.Comms.Exceptions.ConnectException e)
                    {
                        Console.WriteLine($"VIBRATE_DEVICE: ConnectException error, retry {retry}");
                        Crashes.TrackError(e);
                        if (retry > 10)
                        {
                            throw;
                        }
                    }
                }

                // try auth with known password
                Console.WriteLine($"VIBRATE_DEVICE: Authenticate()");
                bool authSuccess = await device.Authenticate("YOUR_AUTH_CODE");

                // if fails, reset device
                if (authSuccess)
                {
                    // buzz device
                    Console.WriteLine($"FLASH AND BUZZ START:{ DateTime.Now }");
                    //await device.LEDFlash();
                    await device.VibrateDevice();

                    Console.WriteLine($"FLASH AND BUZZ END:{ DateTime.Now }");
                }
            }
            catch (BlockSyncFailedException e)
            {
                // move read head to active block
                Console.WriteLine("BlockSyncFailedException:" + e.ToString());
                Crashes.TrackError(e);
            }
            catch (DeviceNotInRangeException e)
            {
                // display not in range
                Console.WriteLine("DeviceNotInRangeException:" + e.ToString());
                Crashes.TrackError(e);
            }
            catch (CommandFailedException e)
            {
                // display not in range
                Console.WriteLine("Command Failed Exception:" + e.ToString());
                Crashes.TrackError(e);
            }
            catch (Exception e)
            {
                Console.WriteLine("Exception:" + e.ToString());
                Crashes.TrackError(e);
            }
            finally
            {
                if (device != null)
                {
                    await _axLEManager.DisconnectDevice(device);
                }
            }
        }