using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Serilog; using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock; using ShareeBike.Model.Connector; using ShareeBike.Model.Device; using ShareeBike.Services.BluetoothLock.Tdo; using Xamarin.Essentials; namespace ShareeBike.Services.BluetoothLock.BLE { public abstract class LockItServiceBase { /// Constructs base object. /// Encrpyting/ decrypting object. /// Timeout to apply when connecting to bluetooth lock. public LockItServiceBase(ICipher cipher) { Cipher = cipher; } /// Holds timeout values for series of connecting attempts to a lock or multiple locks. public ITimeOutProvider TimeOut { get; set; } protected ICipher Cipher { get; } /// List of available or connected bluetooth devices. protected List DeviceList = new List(); /// Reconnect locks of interest if required. /// Consists of a bluetooth connect plus invocation of an authentication sequence for each lock to be reconnected. /// Locks to reconnect. /// Timeout for connect operation. public static async Task CheckReconnect( IEnumerable deviceList, IEnumerable locksInfo, TimeSpan connectTimeout) { // Get list of target locks without invalid entries. var validLocksInfo = locksInfo .Where(x => x.IsIdValid || x.IsGuidValid) .Where(x => x.K_seed.Length > 0 && x.K_u.Length > 0) .ToList(); foreach (var device in deviceList) { if (device.GetDeviceState() == DeviceState.Connected) { // No need to reconnect device because device is already connected. continue; } var lockInfo = validLocksInfo.FirstOrDefault(x => x.Id == device.Name.GetBluetoothLockId() || x.Guid == device.Guid); if (lockInfo == null) { // Current lock from deviceList is not of interest detected, no need to reconnect. continue; } try { await device.ReconnectAsync(lockInfo, connectTimeout); } catch (System.Exception exception) { // Member is called for background update of missing devices. // Do not display any error messages. Log.ForContext().Error($"Reconnect failed. {exception.Message}"); continue; } } } /// Gets the state for locks of interest. /// /// Might require a /// - connect to lock /// - reconnect to lock /// /// Locks toget info for. /// Timeout for connect operation of a single lock. public async Task> GetLocksStateAsync( IEnumerable locksInfo, TimeSpan connectTimeout) { if (locksInfo.Count() == 0) { // Nothing to do. return new List(); } // Reconnect locks. await CheckReconnect(DeviceList, locksInfo, connectTimeout); // Connect to locks which were not yet discovered. DeviceList.AddRange(await CheckConnectMissing(locksInfo, connectTimeout)); // Get devices for which to update state. var locksInfoState = new List(); foreach (var device in DeviceList) { var lockInfoAuth = locksInfo.FirstOrDefault(x => x.Guid == device.Guid || x.Id == device.Id); if (lockInfoAuth == null) { // Device is not of interest. continue; } var state = await device.GetLockStateAsync(); locksInfoState.Add(new LockInfoTdo.Builder { Id = lockInfoAuth.Id, Guid = lockInfoAuth.IsGuidValid ? lockInfoAuth.Guid : device.Guid, State = state?.State }.Build()); } return locksInfoState; } /// Consists of a bluetooth connect plus invocation of an authentication sequence. /// Locks to reconnect. /// Timeout for connect operation of a single lock. protected abstract Task> CheckConnectMissing( IEnumerable locksInfo, TimeSpan connectTimeout); /// Gets a lock by bike Id. /// /// Lock object public ILockService this[int bikeId] { get { var device = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == bikeId); if (device == null) { return new NullLock(bikeId); } return device; } } /// Connects to lock. /// Consists of a bluetooth connect plus invocation of an authentication sequence. /// Info required to connect to lock. /// Timeout for connect operation. public async Task DisconnectAsync(int bikeId, Guid bikeGuid) { if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false) { throw new System.Exception("Can not disconnect from lock. Platform must not be unknown and bluetooth code must be run on main thread."); } Log.ForContext().Debug($"Request to disconnect from device {bikeId}/ {bikeGuid}."); var lockIt = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == bikeId || x.Guid == bikeGuid); if (lockIt == null) { // Nothing to do return LockingState.UnknownDisconnected; } DeviceList.Remove(lockIt); if (lockIt.GetDeviceState() == DeviceState.Disconnected) { // Nothing to do return LockingState.UnknownDisconnected; } await lockIt.Disconnect(); return LockingState.UnknownDisconnected; } } }