using Plugin.BLE.Abstractions.Contracts; using Serilog; using System; using System.Diagnostics; using System.Threading.Tasks; using TINK.Model.Bike.BluetoothLock; using TINK.Model.Device; using TINK.Services.BluetoothLock.Exception; using TINK.Services.BluetoothLock.Tdo; using Xamarin.Essentials; namespace TINK.Services.BluetoothLock.BLE { public class LockItPolling : LockItBase { public LockItPolling(IDevice device, IAdapter adapter, ICipher cipher) : base(device, adapter, cipher) { } /// Reconnects to device. /// Consists of a bluetooth connect plus invocation of an authentication sequence. /// Info required to connect. /// Timeout to apply when connecting to bluetooth lock. /// True if connecting succeeded, false if not. public override async Task ReconnectAsync( LockInfoAuthTdo authInfo, TimeSpan connectTimeout) => await ReconnectAsync( authInfo, connectTimeout, () => new LockItPolling(Device, Adapter, Cipher)); /// Connects to device. /// Info required to connect. /// Device with must be connected. /// True if connecting succeeded, false if not. public static async Task Authenticate( IDevice device, LockInfoAuthTdo authInfo, IAdapter adapter, ICipher cipher) => await Authenticate( device, authInfo, adapter, cipher, () => new LockItPolling(device, adapter, cipher)); /// Opens lock. /// Locking state. public async override Task OpenAsync() { if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false) { throw new System.Exception("Can not open lock. Bluetooth code must be run on main thread"); } var info = await GetLockStateAsync(); if (info?.State == null) { // Device not reachable. Log.ForContext().Information("Can not open lock. Device is not reachable (get state)."); return await Task.FromResult((LockitLockingState?)null); } if (info.State.Value.GetLockingState() == LockingState.Open) { // Lock is already open. Log.ForContext().Information("No need to open lock. Lock is already open."); return await Task.FromResult((LockitLockingState?)null); } Log.ForContext().Debug($"Request to closed lock. Current lockikng state is {info}, counter value {ActivateLockWriteCounter}."); var result = await OpenCloseLock( true); // Close lock); if (!result) { // State did not change. Return previous state. Log.ForContext().Information($"Opening lock failed."); return await Task.FromResult(info.State.Value); } info = await GetLockStateAsync(); if (info?.State == null) { // Device not reachable. Log.ForContext().Information($"State after open command unknown. Device is not reachable (get state)."); return await Task.FromResult((LockitLockingState?)null); } var watch = new Stopwatch(); watch.Start(); while (info?.State != null && info.State.Value != LockitLockingState.CouldntOpenBoldBlocked && info.State.Value != LockitLockingState.Open && watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS)) { info = await GetLockStateAsync(true); // While opening lock seems not always respond to reading operations. Log.ForContext().Information($"Current lock state is {info?.State.Value}."); } if (info == null) { return null; } switch (info.State.Value) { case LockitLockingState.Open: return info.State.Value; case LockitLockingState.CouldntOpenBoldBlocked: // Expected error. ILockIt count not be opened (Spoke blocks lock, ....) throw new CouldntOpenBoldIsBlockedException(); case LockitLockingState.Unknown: // Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....) throw new CouldntOpenBoldWasBlockedException(); default: // Comprises values // - LockitLockingState.Closed // - LockitLockingState.CouldntCloseMoving (should never happen because command open was send) // - LockitLockingState.CouldntCloseBoldBlocked (should never happen because command open was send) // Internal error which sould never occure. Lock refuses to open but connection is ok. throw new CouldntOpenInconsistentStateExecption(info.State.Value.GetLockingState()); } } /// Close the lock. /// Locking state. public async override Task CloseAsync() { if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false) { throw new System.Exception("Can not close lock. Bluetooth code must be run on main thread"); } // Get current state var info = await GetLockStateAsync(); if (info?.State == null) { // Device not reachable. Log.ForContext().Error("Can not close lock. Device is not reachable (get state)."); return await Task.FromResult((LockitLockingState?)null); } if (info.State.Value.GetLockingState() == LockingState.Closed) { // Lock is already closed. Log.ForContext().Error("No need to close lock. Lock is already closed."); return await Task.FromResult(info.State.Value); } Log.ForContext().Debug($"Request to closed lock. Current state is {info}, counter value {ActivateLockWriteCounter}."); var result = await OpenCloseLock(false); // Close lock if (!result) { // State did not change. Return previous state. Log.ForContext().Information($"Closing lock failed."); return await Task.FromResult(info.State.Value); } // Get lock state until either lock state chaneges or until log gets unreachable. info = await GetLockStateAsync(); if (info?.State == null) { // Device not reachable. Log.ForContext().Information($"Lock state after close command unknown."); return await Task.FromResult((LockitLockingState?)null); } var watch = new Stopwatch(); watch.Start(); while (info.State != null && info.State.Value != LockitLockingState.CouldntCloseBoldBlocked && info.State.Value != LockitLockingState.CouldntCloseMoving && info.State.Value != LockitLockingState.Closed && watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS)) { info = await GetLockStateAsync(true); ; // While closing lock seems not always respond to reading operations. Log.ForContext().Information($"Current lock state is {info?.State.Value}."); } if (info == null) { return null; } switch (info.State.Value) { case LockitLockingState.CouldntCloseBoldBlocked: // Expected error. ILockIt could not be closed (Spoke blocks lock, ....) throw new CouldntCloseBoldBlockedException(); case LockitLockingState.CouldntCloseMoving: // Expected error. ILockIt could not be closed (bike is moving) throw new CouldntCloseMovingException(); case LockitLockingState.Closed: // Everything is ok. return info.State.Value; default: // Comprises values // - LockitLockingState.Open // - LockitLockingState.Unknown // - LockitLockingState.CouldntOpenBoldBlocked // Internal error which sould never occurre. Lock refuses to close but connection is ok. throw new CouldntCloseInconsistentStateExecption(info.State.Value.GetLockingState()); } } } }