using System.Collections.Generic; using System.Threading.Tasks; using Plugin.BLE; using Plugin.BLE.Abstractions.Contracts; using Plugin.BLE.Abstractions; using System.Linq; using TINK.Services.BluetoothLock.Tdo; using TINK.Model.Connector; using Serilog; using System.Threading; using System; using TINK.Services.BluetoothLock.Exception; using Xamarin.Essentials; using TINK.Model.Device; using LockItShared.Services.BluetoothLock; namespace TINK.Services.BluetoothLock.BLE { public class LockItByGuidService : LockItServiceBase, ILocksService { /// Constructs a LockItByGuidService object. /// Encrpyting/ decrypting object. public LockItByGuidService(ICipher cipher) : base(cipher) { } /// Checks for locks which have not yet been discoverted and connects them. /// Consists of a bluetooth connect plus invocation of an authentication sequence. /// Locks to reconnect. /// Timeout for connect operation of a single lock. protected override async Task> CheckConnectMissing(IEnumerable locksInfo, TimeSpan connectTimeout) { if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false) { throw new System.Exception("Can not connect to locks by guid. Bluetooth code must be run on main thread"); } // Get list of target locks without invalid entries. var validLocksInfo = locksInfo .Where(x => x.IsGuidValid) .Where(x => x.K_seed.Length > 0 && x.K_u.Length > 0) .ToList(); var locksList = new List(); // Connect to foreach (var lockInfo in validLocksInfo) { if (DeviceList.Any(x => x.Name.GetBluetoothLockId() == lockInfo.Id || x.Guid == lockInfo.Guid)) { // Device is already connected. continue; } // Connect to device and authenticate. ILockService lockIt = null; try { lockIt = await ConnectByGuid(lockInfo, connectTimeout); } catch (System.Exception exception) { // Member is called for background update of missing devices. // Do not display any error messages. Log.ForContext().Error($"Authentication failed. {exception.Message}"); continue; } if (lockIt == null) { continue; } locksList.Add(lockIt); } return locksList; } /// 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 ConnectAsync(LockInfoAuthTdo authInfo, TimeSpan connectTimeout) { if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false) { throw new System.Exception("Can not connect to lock by guid. Bluetooth code must be run on main thread"); } // Connect to device and authenticate. var lockIt = await ConnectByGuid(authInfo, connectTimeout); if (lockIt == null) { return new LockInfoTdo.Builder { Id = authInfo.Id, Guid = authInfo.Guid, State = null }.Build(); } DeviceList.Add(lockIt); return await lockIt.GetLockStateAsync(); } /// Connects to lock. /// Consists of a bluetooth connect plus invocation of an authentication sequence. /// Info required to connect to lock. /// Timeout for connect operation. private async Task ConnectByGuid(LockInfoAuthTdo authInfo, TimeSpan connectTimeout) { if (authInfo.Guid == TextToLockItTypeHelper.INVALIDLOCKGUID) { Log.ForContext().Error($"Can not connect to lock {authInfo.Id}. Guid is unknown."); throw new GuidUnknownException(); } var lockIt = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == authInfo.Id || x.Guid == authInfo.Guid); if (lockIt != null && lockIt.GetDeviceState() == DeviceState.Connected) { // Device is already connected. return lockIt; } var adapter = CrossBluetoothLE.Current.Adapter; Log.ForContext().Debug($"Request connect to device {authInfo.Id}."); if (LockItByGuidServiceHelper.DevelGuids.ContainsKey(authInfo.Id) && LockItByGuidServiceHelper.DevelGuids[authInfo.Id] != authInfo.Guid) throw new System.Exception($"Invalid Guid {authInfo.Guid} for lock with id {authInfo.Id} detected. Guid should be {LockItByGuidServiceHelper.DevelGuids[authInfo.Id]}."); IDevice device; var cts = new CancellationTokenSource(connectTimeout); // Step 1: Perform bluetooth connect. try { device = await adapter.ConnectToKnownDeviceAsync( authInfo.Guid, new ConnectParameters(forceBleTransport: true), // Force BLE transport cts.Token); } catch (System.Exception exception) { if (exception is TaskCanceledException) { // A timeout occurred. throw new System.Exception($"Can not connect to lock by guid.\r\nTimeout of {connectTimeout.TotalMilliseconds} [ms] elapsed.", exception); } Log.ForContext().Error("Bluetooth connect to known device request failed. {Exception}", exception); throw new System.Exception($"Can not connect to lock by guid.\r\n{exception.Message}", exception); } // Step 2: Authenticate. lockIt = await LockItEventBased.Authenticate(device, authInfo, CrossBluetoothLE.Current.Adapter, Cipher); if (lockIt == null) { Log.ForContext().Error("Connect to device failed."); return null; } return lockIt; } } }