sharee.bike-App/LockItBLE/Services/BluetoothLock/BLE/LockItEventBased.cs
2022-04-25 22:15:15 +02:00

312 lines
13 KiB
C#

using Plugin.BLE.Abstractions.Contracts;
using Plugin.BLE.Abstractions.EventArgs;
using Serilog;
using System;
using System.Threading;
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 LockItEventBased : LockItBase
{
private LockItEventBased(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 async override Task ReconnectAsync(
LockInfoAuthTdo authInfo,
TimeSpan connectTimeout)
=> await ReconnectAsync(
authInfo,
connectTimeout,
() => new LockItEventBased(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 LockItEventBased(device, adapter, cipher));
/// <summary> Opens lock. </summary>
/// <returns> Locking state.</returns>
public async override Task<LockitLockingState?> 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");
}
LockitLockingState? lockingState = (await GetLockStateAsync())?.State;
if (lockingState == null)
{
// Device not reachable.
Log.ForContext<LockItEventBased>().Information("Can not open lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
if (lockingState.Value.GetLockingState() == LockingState.Open)
{
// Lock is already open.
Log.ForContext<LockItEventBased>().Information("No need to open lock. Lock is already open.");
return await Task.FromResult((LockitLockingState?)null);
}
Log.ForContext<LockItEventBased>().Debug($"Request to closed lock. Current lockikng state is {lockingState}, counter value {ActivateLockWriteCounter}.");
double batteryPercentage = double.NaN;
var lockStateCharacteristic = await GetStateCharacteristicAsync();
var batteryStateCharacteristic = await GetBatteryCharacteristicAsync();
TaskCompletionSource<LockitLockingState?> tcs = new TaskCompletionSource<LockitLockingState?>();
var cts = new CancellationTokenSource(OPEN_CLOSE_TIMEOUT_MS);
cts.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false);
EventHandler<CharacteristicUpdatedEventArgs> lockStateCharcteristicChanged = (sender, args) => GetStateValue(tcs, args.Characteristic.Value);
EventHandler<CharacteristicUpdatedEventArgs> batteryStateCharcteristicChanged = (sender, args) => batteryPercentage = GetChargeValue(args.Characteristic.Value);
try
{
lockStateCharacteristic.ValueUpdated += lockStateCharcteristicChanged;
batteryStateCharacteristic.ValueUpdated += batteryStateCharcteristicChanged;
} catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Subscribing to events when opening lock failed.{Exception}", ex);
return await Task.FromResult((LockitLockingState?)null);
}
try
{
await lockStateCharacteristic.StartUpdatesAsync();
await batteryStateCharacteristic.StartUpdatesAsync();
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Starting updates wen opening lock failed.{Exception}", ex);
return await Task.FromResult((LockitLockingState?)null);
}
try
{
var result = await OpenCloseLock(true); // Close lock;
if (!result)
{
// State did not change. Return previous state.
Log.ForContext<LockItEventBased>().Information($"Opening lock failed.");
return await Task.FromResult(lockingState.Value);
}
// Wait until event has been received.
lockingState = await tcs.Task;
}
finally
{
try
{
await lockStateCharacteristic.StopUpdatesAsync();
await batteryStateCharacteristic.StopUpdatesAsync();
lockStateCharacteristic.ValueUpdated -= lockStateCharcteristicChanged;
batteryStateCharacteristic.ValueUpdated -= batteryStateCharcteristicChanged;
} catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Sopping updates/ unsubscribing from events when opening lock failed.{Exception}", ex);
}
}
if (lockingState == null)
{
return null;
}
switch (lockingState.Value)
{
case LockitLockingState.Open:
return lockingState.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.Unknown
// - LockitLockingState.CouldntOpenBoldBlocked
// Internal error which sould never occure. Lock refuses to open but connection is ok.
throw new CouldntOpenInconsistentStateExecption(lockingState.Value.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. Bluetooth code must be run on main thread");
}
// Get current state
LockitLockingState? lockingState = (await GetLockStateAsync()).State;
if (lockingState == null)
{
// Device not reachable.
Log.ForContext<LockItEventBased>().Error("Can not close lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
if (lockingState.Value.GetLockingState() == LockingState.Closed)
{
// Lock is already closed.
Log.ForContext<LockItEventBased>().Error("No need to close lock. Lock is already closed.");
return await Task.FromResult(lockingState.Value);
}
lockingState = null;
var lockStateCharacteristic = await GetStateCharacteristicAsync();
TaskCompletionSource<LockitLockingState?> tcs = new TaskCompletionSource<LockitLockingState?>();
var cts = new CancellationTokenSource(OPEN_CLOSE_TIMEOUT_MS);
cts.Token.Register(() => tcs.TrySetCanceled(), useSynchronizationContext: false);
EventHandler<CharacteristicUpdatedEventArgs> lockStateCharcteristicChanged = (sender, args) => GetStateValue(tcs, args.Characteristic.Value);
try
{
lockStateCharacteristic.ValueUpdated += lockStateCharcteristicChanged;
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Subscribing to events when closing lock failed.{Exception}", ex);
return await Task.FromResult((LockitLockingState?)null);
}
try
{
await lockStateCharacteristic.StartUpdatesAsync();
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Starting update when closing lock failed.{Exception}", ex);
return await Task.FromResult((LockitLockingState?)null);
}
try
{
Log.ForContext<LockItEventBased>().Debug($"Request to closed lock. Current state is {lockingState}, counter value {ActivateLockWriteCounter}.");
var result = await OpenCloseLock(false); // Close lock
if (!result)
{
// State did not change. Return previous state.
Log.ForContext<LockItEventBased>().Information($"Closing lock failed.");
return await Task.FromResult(lockingState.Value);
}
// Wait until event has been received.
lockingState = await tcs.Task;
}
finally
{
try
{
await lockStateCharacteristic.StopUpdatesAsync();
lockStateCharacteristic.ValueUpdated -= lockStateCharcteristicChanged;
} catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Sopping update/ unsubscribing from events when closing lock failed.{Exception}", ex);
}
}
if (lockingState == null)
{
return null;
}
switch (lockingState.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:
return lockingState;
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(lockingState.Value.GetLockingState());
}
}
/// <summary> Gets the locking state from event argument. </summary>
Action<TaskCompletionSource<LockitLockingState?>, byte[]> GetStateValue = (tcs, value) =>
{
try
{
if (value?.Length <= 0)
{
tcs.TrySetResult(null);
return;
}
tcs.TrySetResult((LockitLockingState?)value[0]);
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Error on sinking lock state characteristic on opening/closing .{Exception}", ex);
tcs.TrySetResult(null);
}
};
/// <summary> Gets the battery state from lock.</summary>
Func<byte[], double> GetChargeValue = (value) =>
{
try
{
if (value.Length <= 0)
{
return double.NaN;
}
return value[0];
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Error on sinking battery state characteristic on opening.{Exception}", ex);
return double.NaN;
}
};
}
}