2021-05-13 17:25:46 +02:00
using System.Collections.Generic ;
using System.Threading.Tasks ;
using System.Linq ;
using TINK.Services.BluetoothLock.Tdo ;
using TINK.Model.Connector ;
using System ;
using Serilog ;
using TINK.Model.Device ;
using Xamarin.Essentials ;
using TINK.Model.Bike.BluetoothLock ;
namespace TINK.Services.BluetoothLock.BLE
{
public abstract class LockItServiceBase
{
/// <summary>Constructs base object.</summary>
/// <param name="cipher">Encrpyting/ decrypting object.</param> /// <param name="connectTimeout">Timeout to apply when connecting to bluetooth lock.</param>
public LockItServiceBase ( ICipher cipher )
{
Cipher = cipher ;
}
/// <summary> Holds timeout values for series of connecting attemps to a lock or multiple locks. </summary>
public ITimeOutProvider TimeOut { get ; set ; }
protected ICipher Cipher { get ; }
/// <summary> List of available or connected bluetooth devices. </summary>
protected List < ILockService > DeviceList = new List < ILockService > ( ) ;
/// <summary> Reconnect locks of interest if required. </summary>
/// <remarks> Consists of a bluetooth connect plus invocation of an authentication sequence for each lock to be reconnected. </remarks>
/// <param name="locksInfo">Locks to reconnect.</param>
/// <param name="connectTimeout">Timeout for connect operation.</param>
public static async Task CheckReconnect (
IEnumerable < ILockService > deviceList ,
IEnumerable < LockInfoAuthTdo > locksInfo ,
TimeSpan connectTimeout )
{
// Get list of target locks without invalid entries.
var validLocksInfo = locksInfo
. Where ( x = > x . IsIdValid | | x . IsGuidValid )
. Where ( x = > x . K_seed . Length > 0 & & x . K_u . Length > 0 )
. ToList ( ) ;
foreach ( var device in deviceList )
{
if ( device . GetDeviceState ( ) = = DeviceState . Connected )
{
// No need to reconnect device because device is already connected.
continue ;
}
var lockInfo = validLocksInfo . FirstOrDefault ( x = > x . Id = = device . Name . GetBluetoothLockId ( ) | | x . Guid = = device . Guid ) ;
if ( lockInfo = = null )
{
// Current lock from deviceList is not of interest detected, no need to reconnect.
continue ;
}
try
{
await device . ReconnectAsync ( lockInfo , connectTimeout ) ;
}
catch ( System . Exception exception )
{
// Member is called for background update of missing devices.
// Do not display any error messages.
Log . ForContext < LockItServiceBase > ( ) . Error ( $"Reconnect failed. {exception.Message}" ) ;
continue ;
}
}
}
/// <summary> Gets the state for locks of interest.</summary>
/// <remarks>
/// Might require a
/// - connect to lock
/// - reconnect to lock
/// </remarks>
/// <param name="locksInfo">Locks toget info for.</param>
/// <param name="connectTimeout">Timeout for connect operation of a single lock.</param>
public async Task < IEnumerable < LockInfoTdo > > GetLocksStateAsync (
IEnumerable < LockInfoAuthTdo > locksInfo ,
TimeSpan connectTimeout )
{
if ( locksInfo . Count ( ) = = 0 )
{
// Nothing to do.
return new List < LockInfoTdo > ( ) ;
}
// Reconnect locks.
await CheckReconnect ( DeviceList , locksInfo , connectTimeout ) ;
// Connect to locks which were not yet discovered.
DeviceList . AddRange ( await CheckConnectMissing ( locksInfo , connectTimeout ) ) ;
// Get devices for which to update state.
var locksInfoState = new List < LockInfoTdo > ( ) ;
foreach ( var device in DeviceList )
{
var lockInfoAuth = locksInfo . FirstOrDefault ( x = > x . Guid = = device . Guid | | x . Id = = device . Id ) ;
if ( lockInfoAuth = = null )
{
// Device is not of interest.
continue ;
}
var state = await device . GetLockStateAsync ( ) ;
locksInfoState . Add ( new LockInfoTdo . Builder
{
Id = lockInfoAuth . Id ,
Guid = lockInfoAuth . IsGuidValid ? lockInfoAuth . Guid : device . Guid ,
State = state ? . State
} . Build ( ) ) ;
}
return locksInfoState ;
}
/// <remarks> Consists of a bluetooth connect plus invocation of an authentication sequence. </remarks>
/// <param name="locksInfo">Locks to reconnect.</param>
/// <param name="connectTimeout">Timeout for connect operation of a single lock.</param>
protected abstract Task < IEnumerable < ILockService > > CheckConnectMissing (
IEnumerable < LockInfoAuthTdo > locksInfo ,
TimeSpan connectTimeout ) ;
/// <summary>Gets a lock by bike Id.</summary>
/// <param name="bikeId"></param>
/// <returns>Lock object</returns>
public ILockService this [ int bikeId ]
{
get
{
var device = DeviceList . FirstOrDefault ( x = > x . Name . GetBluetoothLockId ( ) = = bikeId ) ;
if ( device = = null )
{
return new NullLock ( bikeId ) ;
}
return device ;
}
}
/// <summary> Connects to lock.</summary>
/// <remarks> Consists of a bluetooth connect plus invocation of an authentication sequence. </remarks>
/// <param name="authInfo"> Info required to connect to lock.</param>
/// <param name="connectTimeout">Timeout for connect operation.</param>
public async Task < LockingState > DisconnectAsync ( int bikeId , Guid bikeGuid )
{
if ( DeviceInfo . Platform ! = DevicePlatform . Unknown & & MainThread . IsMainThread = = false )
{
throw new System . Exception ( "Can not disconnect from lock. Bluetooth code must be run on main thread" ) ;
}
Log . ForContext < LockItByScanServiceBase > ( ) . Debug ( $"Request to disconnect from device {bikeId}/ {bikeGuid}." ) ;
var lockIt = DeviceList . FirstOrDefault ( x = > x . Name . GetBluetoothLockId ( ) = = bikeId | | x . Guid = = bikeGuid ) ;
if ( lockIt = = null )
{
// Nothing to do
2022-04-10 17:38:34 +02:00
return LockingState . UnknownDisconnected ;
2021-05-13 17:25:46 +02:00
}
DeviceList . Remove ( lockIt ) ;
if ( lockIt . GetDeviceState ( ) = = DeviceState . Disconnected )
{
// Nothing to do
2022-04-10 17:38:34 +02:00
return LockingState . UnknownDisconnected ;
2021-05-13 17:25:46 +02:00
}
await lockIt . Disconnect ( ) ;
2022-04-10 17:38:34 +02:00
return LockingState . UnknownDisconnected ;
2021-05-13 17:25:46 +02:00
}
}
}