using System; using System.Linq; using System.Threading; using System.Diagnostics; using System.Threading.Tasks; using System.Collections.Generic; using System.Collections.ObjectModel; using Plugin.BLE; using Plugin.BLE.Abstractions; using Plugin.BLE.Abstractions.Contracts; using MvvmCross.Platform; using Leaware.MvvmCross.Logger; using Leaware.MvvmCross.TranslatePlugin; using AgrippaHMS.DataAccess; namespace AgrippaHMS.Core { public class BleScanner { #region Const private const int CONNECTING_TIMEOUT = 30000; public readonly static int SCANNING_DURATION = 30000; //30 sek #endregion #region Fields [public] public List TemperatureData = new List(); #endregion #region Fields [private] private bool readStoredDataMode; private string deviceAddress; private readonly IBluetoothLE bluetoothLE; private List devices = new List(); #endregion #region Properties private ITranslatePlugin translate; public ITranslatePlugin Translate { get { if (translate == null) { translate = Mvx.Resolve(); } return translate; } } private ObservableCollection collection; public ObservableCollection Collection { get { if (collection == null) { collection = new ObservableCollection(); } return collection; } set { collection = value; } } private IBleScannerService bleScannerService; private IBleScannerService BleScannerService { get { if (bleScannerService == null) { bleScannerService = Mvx.Resolve(); } return bleScannerService; } } public IAdapter Adapter { get; set; } public Action OnDeviceUpdate { get; set; } public Action OnDeviceConnectionFailure { get; set; } public Action OnReadFailure { get; set; } public Action OnConnectionProgressChange { get; set; } #endregion #region Cnt public BleScanner() { bluetoothLE = CrossBluetoothLE.Current; Adapter = CrossBluetoothLE.Current.Adapter; } #endregion #region Methods [public] public async Task ScanForDevices() { Adapter.DeviceDiscovered -= Adapter_DeviceDiscovered; Adapter.DeviceAdvertised -= Adapter_DeviceAdvertised; Adapter.DeviceDiscovered += Adapter_DeviceDiscovered; Adapter.DeviceAdvertised += Adapter_DeviceAdvertised; Adapter.ScanTimeout = SCANNING_DURATION; await Adapter.StartScanningForDevicesAsync(); } public async Task> ReadStoredDataFromDevice(string address, CancellationTokenSource cts = null) { TemperatureData = new List(); var deviceToConnect = devices.Where(dev => BleScannerService.GetAdressFromDevice(dev) == address).FirstOrDefault(); var isConnectingFinished = default(bool); var isInitialSetupCompleted = default(bool); var configService = default(IService); var temperatureService = default(IService); var contentCharacteristic = default(ICharacteristic); var temperatureEnableCharacteristic = default(ICharacteristic); var configCharacteristic = default(ICharacteristic); var numberOfBytesCharacteristic = default(ICharacteristic); if (!cts.IsCancellationRequested) { OnConnectionProgressChange?.Invoke(Translate.GetText("ConnectingToDeviceProgress")); } if (deviceToConnect != null) { try { deviceAddress = BleScannerService.GetAdressFromDevice(deviceToConnect); var ct = default(CancellationToken); if (cts != null) { ct = cts.Token; } var ctTimeout = new CancellationTokenSource().Token; Task.Run(async () => { await Task.Delay(CONNECTING_TIMEOUT); if (!isConnectingFinished) { ctTimeout.ThrowIfCancellationRequested(); } }); await Adapter.ConnectToDeviceAsync(deviceToConnect, ConnectParameters.None, ctTimeout); if (!cts.IsCancellationRequested) { OnConnectionProgressChange?.Invoke(Translate.GetText("ConnectingToDeviceFinishedProgress")); } isConnectingFinished = true; //get configuration service configService = await deviceToConnect.GetServiceAsync(new Guid(GattAttributes.CONFIGURATION_SERVICE)); //get temperature/humidity service temperatureService = await deviceToConnect.GetServiceAsync(new Guid(GattAttributes.HUM_TEMP_SENSOR_SERVICE)); //get advertisement config characteristic configCharacteristic = await configService.GetCharacteristicAsync(new Guid(GattAttributes.CONFIG_REGISTER)); //get temperature enable/disable characteristic temperatureEnableCharacteristic = await temperatureService.GetCharacteristicAsync(new Guid(GattAttributes.HUM_TEMP_SENSOR_ENABLE)); //get content of flash characteristic contentCharacteristic = await temperatureService.GetCharacteristicAsync(new Guid(GattAttributes.CONTENT_OF_FLASH)); //get number of bytes characteristic numberOfBytesCharacteristic = await configService.GetCharacteristicAsync(new Guid(GattAttributes.NUMBER_OF_BYTES)); //disable temperature/humidity characteristic await temperatureEnableCharacteristic.WriteAsync(new byte[] { 0x00 }); isInitialSetupCompleted = true; byte[] record = default(byte[]); if (!cts.IsCancellationRequested) { OnConnectionProgressChange?.Invoke(Translate.GetText("BeginReadingDataProgress")); } int numberOfReadings = default(int); try { var numberOfBytes = await numberOfBytesCharacteristic.ReadAsync(); var numberOfBytesInt = BitConverter.ToInt32(numberOfBytes, 0); numberOfReadings = numberOfBytesInt / 8; } catch (Exception ex) { Debug.WriteLine($"BLE Sensor Error: {ex.Message}"); Logger.Instance.Error($"BLE Sensor Error: {ex.Message}"); } int i = 0; do { if (cts.IsCancellationRequested) { break; } List data = new List(); record = default(byte[]); record = await contentCharacteristic.ReadAsync(); if (record != null) { if (record.Length > 8) { var byteArray1 = new byte[8]; Array.Copy(record, 0, byteArray1, 0, 8); data.Add(byteArray1); var byteArray2 = new byte[8]; Array.Copy(record, 8, byteArray2, 0, 8); data.Add(byteArray2); } else { data.Add(record); } foreach (var word in data) { var dateTimeBytes = new byte[4]; var humidityBytes = new byte[2]; var temperatureBytes = new byte[2]; Array.Copy(word, 0, dateTimeBytes, 0, 4); Array.Copy(word, 4, temperatureBytes, 0, 2); Array.Copy(word, 6, humidityBytes, 0, 2); double timeStamp = BitConverter.ToInt32(dateTimeBytes, 0); var currentSensorTemperaturesData = default(CurrentSensorTemperaturesData); currentSensorTemperaturesData = new CurrentSensorTemperaturesData() { CreatedDate = BleBitConverter.UtcTimeStampToDateTime(timeStamp), Humidity = Convert.ToSingle(BleBitConverter.Humidity(humidityBytes)), Temperature = Convert.ToSingle(BleBitConverter.Temperature(temperatureBytes)) }; if (TemperatureData.Count > 0) { var prevousItem = TemperatureData.LastOrDefault(); if (prevousItem.CreatedDate.Equals(currentSensorTemperaturesData.CreatedDate) || prevousItem.CreatedDate > currentSensorTemperaturesData.CreatedDate) { record = null; break; } else { TemperatureData.Add(currentSensorTemperaturesData); } } else { TemperatureData.Add(currentSensorTemperaturesData); } i++; if (!cts.IsCancellationRequested) { OnConnectionProgressChange?.Invoke(Translate.GetText("ReadNoProgress", i, numberOfReadings)); } } } } while (record != null); if (!cts.IsCancellationRequested) { OnConnectionProgressChange?.Invoke(Translate.GetText("ReadingFinishedProgress")); } if (TemperatureData != null && TemperatureData.Count <= 1) { if (!cts.IsCancellationRequested) { OnReadFailure?.Invoke(); } } } catch (Plugin.BLE.Abstractions.Exceptions.CharacteristicReadException ex) { //iOS is getting here when there is no more data to receive if (!cts.IsCancellationRequested) { OnConnectionProgressChange?.Invoke(Translate.GetText("ReadingFinishedProgress")); } if (TemperatureData != null && TemperatureData.Count <= 1) { if (!cts.IsCancellationRequested) { OnReadFailure?.Invoke(); } } Debug.WriteLine(ex); } catch (NullReferenceException ex) { Debug.WriteLine(ex); OnDeviceConnectionFailure?.Invoke(); } catch (Exception ex) { if (!cts.IsCancellationRequested) { System.Diagnostics.Debug.WriteLine(ex); OnDeviceConnectionFailure?.Invoke(); } } finally { Task.Run(async() => { if (isInitialSetupCompleted) { if (cts.IsCancellationRequested) { await configCharacteristic.WriteAsync(new byte[] { 0x0D }); } else { await configCharacteristic.WriteAsync(new byte[] { 0x0F }); } await Task.Delay(3000); await temperatureEnableCharacteristic.WriteAsync(new byte[] { 0x01 }); } await Adapter.DisconnectDeviceAsync(deviceToConnect); }); } } return TemperatureData; } public async Task WakeUpBleDevice(string address, CancellationTokenSource cts = null) { var deviceToConnect = devices.Where(dev => BleScannerService.GetAdressFromDevice(dev) == address).FirstOrDefault(); if (deviceToConnect != null) { try { bool isConnectingFinished = default(bool); deviceAddress = BleScannerService.GetAdressFromDevice(deviceToConnect); var ct = cts.Token; Task.Run(async () => { await Task.Delay(CONNECTING_TIMEOUT); if (!isConnectingFinished) { ct.ThrowIfCancellationRequested(); } }); OnConnectionProgressChange?.Invoke(Translate.GetText("ConnectingToDeviceProgress")); await Adapter.ConnectToDeviceAsync(deviceToConnect, ConnectParameters.None, ct); OnConnectionProgressChange?.Invoke(Translate.GetText("ConnectingToDeviceFinishedProgress")); isConnectingFinished = true; OnConnectionProgressChange?.Invoke(Translate.GetText("ConfiguringDeviceProgress")); var configService = await deviceToConnect.GetServiceAsync(new Guid(GattAttributes.CONFIGURATION_SERVICE), ct); var temperatureService = await deviceToConnect.GetServiceAsync(new Guid(GattAttributes.HUM_TEMP_SENSOR_SERVICE), ct); //Disable temperature/humidity sensor var temperatureSensorEnableCharacteristic = await temperatureService.GetCharacteristicAsync(new Guid(GattAttributes.HUM_TEMP_SENSOR_ENABLE)); await temperatureSensorEnableCharacteristic.WriteAsync(new byte[] { 0x00 }, ct); //Set time var setDateTimeCharacteristic = await configService.GetCharacteristicAsync(new Guid(GattAttributes.CURRENT_DATETIME)); var epoch = (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; var epochInt = (int)epoch; await setDateTimeCharacteristic.WriteAsync(BitConverter.GetBytes(epochInt), ct); // Setting advertising interval var setAdvertisingDurationCharacteristic = await configService.GetCharacteristicAsync(new Guid(GattAttributes.DESCOVERABLE_INTERVAL)); await setAdvertisingDurationCharacteristic.WriteAsync(new byte[] { 0x01 }, ct); // Setting temperature/humidity sensor update interval var setTemperatureUpdateSensorItervalCharacteristic = await temperatureService.GetCharacteristicAsync(new Guid(GattAttributes.HUM_TEMP_SENSOR_INTERVAL)); await setTemperatureUpdateSensorItervalCharacteristic.WriteAsync(new byte[] { 0x1E }, ct); //Enable temperature/humidity sensor await temperatureSensorEnableCharacteristic.WriteAsync(new byte[] { 0x01 }, ct); // Setting advertising var setAdvertisingService = await configService.GetCharacteristicAsync(new Guid(GattAttributes.CONFIG_REGISTER)); await setAdvertisingService.WriteAsync(new byte[] { 0x0F }, ct); OnConnectionProgressChange?.Invoke(Translate.GetText("ConfiguringDeviceFinishedProgress")); } catch (Plugin.BLE.Abstractions.Exceptions.CharacteristicReadException ex) { if (!cts.IsCancellationRequested) { OnReadFailure?.Invoke(); System.Diagnostics.Debug.WriteLine(ex); } } catch (Exception ex) { if (!cts.IsCancellationRequested) { OnDeviceConnectionFailure?.Invoke(); System.Diagnostics.Debug.WriteLine(ex); } } finally { await Adapter.DisconnectDeviceAsync(deviceToConnect); } } } public async Task StopScaning() { await Adapter.StopScanningForDevicesAsync(); } public void SortList() { Collection = new ObservableCollection(Collection.OrderByDescending(i => i.Rssi)); } #endregion #region Methods [private] private void Adapter_DeviceDiscovered(object sender, Plugin.BLE.Abstractions.EventArgs.DeviceEventArgs e) { try { var device = e.Device; if (BleScannerService.CheckIfDeviceTypeIsLe(device)) { var knownDevice = Collection.Where(dev => dev.Address == BleScannerService.GetAdressFromDevice(device)).FirstOrDefault(); if (knownDevice == null) { var newItem = BleScannerService.ReadOrUpdateAdvertisementsData(device); Collection.Add(newItem); devices.Add(device); OnDeviceUpdate?.Invoke(); } } } catch(Exception ex) { Debug.WriteLine($"BLE Sensor Error: {ex.Message}"); Logger.Instance.Error($"BLE Sensor Error: {ex.Message}"); Utils.Logger.Instance.SendLog($"BLE Sensor Error: {ex.Message}", null, LogType.Error); } } private async void Adapter_DeviceAdvertised(object sender, Plugin.BLE.Abstractions.EventArgs.DeviceEventArgs e) { try { var device = e.Device; var knownDevice = Collection.Where(dev => dev.Address == BleScannerService.GetAdressFromDevice(device)).FirstOrDefault(); if (knownDevice != null) { var updatedData = BleScannerService.ReadOrUpdateAdvertisementsData(device); knownDevice.Rssi = updatedData.Rssi; knownDevice.Temperature = updatedData.Temperature; knownDevice.Humidity = updatedData.Humidity; OnDeviceUpdate?.Invoke(); } } catch (Exception ex) { Debug.WriteLine($"BLE Sensor Error: {ex.Message}"); Logger.Instance.Error($"BLE Sensor Error: {ex.Message}"); Utils.Logger.Instance.SendLog($"BLE Sensor Error: {ex.Message}", null, LogType.Error); } } #endregion } }