using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Plugin.BLE.Abstractions.Contracts;
using Serilog;
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
using ShareeBike.Model.Device;
using ShareeBike.Services.BluetoothLock.Exception;
using ShareeBike.Services.BluetoothLock.Tdo;
using Xamarin.Essentials;
namespace ShareeBike.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 the lock.
public async override Task OpenAsync()
{
if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false)
{
throw new System.Exception("Can not open lock. Platform must not be unknown and bluetooth code must be run on main thread.");
}
var info = await GetLockStateAsync();
if (!(info?.State is LockitLockingState initialLockingState))
{
// Device not reachable.
Log.ForContext().Information("Can not open lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
if (initialLockingState.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 open lock. Current locking state is {info}, counter value {ActivateLockWriteCounter}.");
// Send command to open to lock.
var result = await OpenCloseLockAsync(
true /* Close lock*/ );
if (!result)
{
// State did not change. Return previous state.
Log.ForContext().Information($"Opening lock failed.");
return await Task.FromResult(initialLockingState);
}
var subsequentLockingStateNullable = (await GetLockStateAsync()).State;
if (subsequentLockingStateNullable == 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 (subsequentLockingStateNullable != null
&& subsequentLockingStateNullable != LockitLockingState.CouldntOpenBoltBlocked
&& subsequentLockingStateNullable != LockitLockingState.Open
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
subsequentLockingStateNullable = (await GetLockStateAsync(true)).State; // While opening lock seems not always respond to reading operations.
Log.ForContext().Debug($"Waiting for lock to open. Current lock state is {subsequentLockingStateNullable}.");
}
if (!(subsequentLockingStateNullable is LockitLockingState subsequentLockingState))
{
Log.ForContext().Fatal($"Opening lock failed. State object is null.");
return null;
}
switch (subsequentLockingState)
{
case LockitLockingState.Open:
Log.ForContext().Information($"Lock was opened successfully.");
return subsequentLockingState;
case LockitLockingState.CouldntOpenBoltBlocked:
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
Log.ForContext().Debug($"Opening lock failed. Bold is blocked.");
throw new CouldntOpenBoldIsBlockedException();
case LockitLockingState.Unknown:
// Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....)
Log.ForContext().Debug($"Opening lock failed. Bold status is unknown.");
throw new CouldntOpenBoldStatusIsUnknownException();
default:
// Comprises values
// - LockitLockingState.Closed
// - LockitLockingState.CouldntCloseMoving (should never happen because command open was send)
// - LockitLockingState.CouldntCloseBoltBlocked (should never happen because command open was send)
// Internal error which should never occur. Lock refuses to open but connection is ok.
Log.ForContext().Debug($"Opening lock failed. Unexpected lock state {subsequentLockingState.GetLockingState()} detected.");
throw new CouldntOpenInconsistentStateExecption(subsequentLockingState.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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
// Get current state
var info = await GetLockStateAsync();
if (!(info?.State is LockitLockingState initialLockingState))
{
// Device not reachable.
Log.ForContext().Error("Can not close lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
if (initialLockingState.GetLockingState() == LockingState.Closed)
{
// Lock is already closed.
Log.ForContext().Error("No need to close lock. Lock is already closed.");
return await Task.FromResult(initialLockingState);
}
Log.ForContext().Debug($"Request to close lock. Current locking state is {info}, counter value {ActivateLockWriteCounter}.");
// Send command to close to lock.
var result = await OpenCloseLockAsync(
false /*Close lock*/);
if (!result)
{
// State did not change. Return previous state.
Log.ForContext().Information($"Closing lock failed.");
return await Task.FromResult(initialLockingState);
}
// Get lock state until either lock state changes or until log gets unreachable.
var subsequentLockingStateNullable = (await GetLockStateAsync()).State;
if (subsequentLockingStateNullable == 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();
var hasBeenLocked = false;
while (subsequentLockingStateNullable != null
&& subsequentLockingStateNullable != LockitLockingState.CouldntCloseMoving
&& subsequentLockingStateNullable != LockitLockingState.Closed
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
subsequentLockingStateNullable = (await GetLockStateAsync(true)).State; ; // While closing lock seems not always respond to reading operations.
if (subsequentLockingStateNullable == LockitLockingState.CouldntCloseBoltBlocked)
{
// Lock reported a blocked bold.
hasBeenLocked = true;
Log.ForContext().Debug($"Waiting for lock to close. Bold is blocked.");
continue;
}
if (hasBeenLocked && subsequentLockingStateNullable == LockitLockingState.Open)
{
// ILockIt could not be closed. Bolt was blocked but was opened again.
Log.ForContext().Debug($"Closing lock failed. Bold was blocked and lock was reopened.");
throw new CouldntCloseBoltBlockedException(LockingState.Open);
}
Log.ForContext().Debug($"Waiting for lock to close. Current lock state is {subsequentLockingStateNullable}.");
}
if (info == null)
{
Log.ForContext().Fatal($"Closing lock failed. State object is null.");
return null;
}
if (!(subsequentLockingStateNullable is LockitLockingState subsequentLockingState))
{
// Device not reachable.
Log.ForContext().Information($"Lock state after close command unknown.");
return await Task.FromResult((LockitLockingState?)null);
}
switch (subsequentLockingState)
{
case LockitLockingState.CouldntCloseBoltBlocked:
// Expected error. ILockIt could not be closed (Spoke blocks lock, ....)
Log.ForContext().Debug($"Closing lock failed. Bold is blocked.");
throw new CouldntCloseBoltBlockedException();
case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving)
Log.ForContext().Debug($"Closing lock failed. Bike is moving.");
throw new CouldntCloseMovingException();
case LockitLockingState.Closed:
// Everything is ok.
Log.ForContext().Information($"Lock was closed successfully.");
return subsequentLockingState;
default:
// Comprises values
// - LockitLockingState.Open
// - LockitLockingState.Unknown
// - LockitLockingState.CouldntOpenBoltBlocked
// Internal error which should never occur. Lock refuses to close but connection is ok.
Log.ForContext().Debug($"Closing lock failed. Unexpected lock state {subsequentLockingState.GetLockingState()} detected.");
throw new CouldntCloseInconsistentStateExecption(subsequentLockingState.GetLockingState());
}
}
}
}