mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2024-11-05 10:36:30 +01:00
254 lines
10 KiB
C#
254 lines
10 KiB
C#
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)
|
|
{
|
|
}
|
|
|
|
/// <summary> Reconnects to device. </summary>
|
|
/// <remarks> Consists of a bluetooth connect plus invocation of an authentication sequence. </remarks>
|
|
/// <param name="authInfo">Info required to connect.</param>
|
|
/// <param name="connectTimeout">Timeout to apply when connecting to bluetooth lock.</param>
|
|
/// <returns>True if connecting succeeded, false if not.</returns>
|
|
public override async Task ReconnectAsync(
|
|
LockInfoAuthTdo authInfo,
|
|
TimeSpan connectTimeout)
|
|
=> await ReconnectAsync(
|
|
authInfo,
|
|
connectTimeout,
|
|
() => new LockItPolling(Device, Adapter, Cipher));
|
|
|
|
/// <summary> Connects to device. </summary>
|
|
/// <param name="authInfo">Info required to connect.</param>
|
|
/// <param name="device">Device with must be connected.</param>
|
|
/// <returns>True if connecting succeeded, false if not.</returns>
|
|
public static async Task<LockItBase> Authenticate(
|
|
IDevice device,
|
|
LockInfoAuthTdo authInfo,
|
|
IAdapter adapter,
|
|
ICipher cipher)
|
|
=> await Authenticate(
|
|
device,
|
|
authInfo,
|
|
adapter,
|
|
cipher,
|
|
() => new LockItPolling(device, adapter, cipher));
|
|
|
|
/// <summary> Opens the lock. </summary>
|
|
public async override Task<LockitLockingState?> 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<LockItPolling>().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<LockItPolling>().Information("No need to open lock. Lock is already open.");
|
|
return await Task.FromResult((LockitLockingState?)null);
|
|
}
|
|
|
|
Log.ForContext<LockItPolling>().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<LockItPolling>().Information($"Opening lock failed.");
|
|
return await Task.FromResult(initialLockingState);
|
|
}
|
|
|
|
var subsequentLockingStateNullable = (await GetLockStateAsync()).State;
|
|
if (subsequentLockingStateNullable == null)
|
|
{
|
|
// Device not reachable.
|
|
Log.ForContext<LockItPolling>().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<LockItPolling>().Debug($"Waiting for lock to open. Current lock state is {subsequentLockingStateNullable}.");
|
|
}
|
|
|
|
if (!(subsequentLockingStateNullable is LockitLockingState subsequentLockingState))
|
|
{
|
|
Log.ForContext<LockItPolling>().Fatal($"Opening lock failed. State object is null.");
|
|
return null;
|
|
}
|
|
|
|
switch (subsequentLockingState)
|
|
{
|
|
case LockitLockingState.Open:
|
|
Log.ForContext<LockItPolling>().Information($"Lock was opened successfully.");
|
|
return subsequentLockingState;
|
|
|
|
case LockitLockingState.CouldntOpenBoltBlocked:
|
|
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
|
|
Log.ForContext<LockItPolling>().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<LockItPolling>().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<LockItPolling>().Debug($"Opening lock failed. Unexpected lock state {subsequentLockingState.GetLockingState()} detected.");
|
|
throw new CouldntOpenInconsistentStateExecption(subsequentLockingState.GetLockingState());
|
|
}
|
|
}
|
|
|
|
/// <summary> Close the lock.</summary>
|
|
/// <returns>Locking state.</returns>
|
|
public async override Task<LockitLockingState?> 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<LockItPolling>().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<LockItPolling>().Error("No need to close lock. Lock is already closed.");
|
|
return await Task.FromResult(initialLockingState);
|
|
}
|
|
|
|
Log.ForContext<LockItPolling>().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<LockItPolling>().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<LockItPolling>().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<LockItPolling>().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<LockItPolling>().Debug($"Closing lock failed. Bold was blocked and lock was reopened.");
|
|
throw new CouldntCloseBoltBlockedException(LockingState.Open);
|
|
}
|
|
|
|
Log.ForContext<LockItPolling>().Debug($"Waiting for lock to close. Current lock state is {subsequentLockingStateNullable}.");
|
|
}
|
|
|
|
if (info == null)
|
|
{
|
|
Log.ForContext<LockItPolling>().Fatal($"Closing lock failed. State object is null.");
|
|
return null;
|
|
}
|
|
|
|
if (!(subsequentLockingStateNullable is LockitLockingState subsequentLockingState))
|
|
{
|
|
// Device not reachable.
|
|
Log.ForContext<LockItPolling>().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<LockItPolling>().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<LockItPolling>().Debug($"Closing lock failed. Bike is moving.");
|
|
throw new CouldntCloseMovingException();
|
|
|
|
case LockitLockingState.Closed:
|
|
// Everything is ok.
|
|
Log.ForContext<LockItPolling>().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<LockItPolling>().Debug($"Closing lock failed. Unexpected lock state {subsequentLockingState.GetLockingState()} detected.");
|
|
throw new CouldntCloseInconsistentStateExecption(subsequentLockingState.GetLockingState());
|
|
}
|
|
}
|
|
}
|
|
}
|