2021-05-13 17:25:46 +02:00
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 )
{
2021-06-26 20:57:55 +02:00
case LockitLockingState . Open :
return lockingState . Value ;
2021-05-13 17:25:46 +02:00
case LockitLockingState . CouldntOpenBoldBlocked :
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
2021-06-26 20:57:55 +02:00
throw new CouldntOpenBoldIsBlockedException ( ) ;
2021-05-13 17:25:46 +02:00
2021-06-26 20:57:55 +02:00
case LockitLockingState . Unknown :
// Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....)
throw new CouldntOpenBoldWasBlockedException ( ) ;
2021-05-13 17:25:46 +02:00
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 CounldntCloseMovingException ( ) ;
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 ;
}
} ;
}
}