2022-08-30 15:42:25 +02:00
using System ;
using System.Collections.Generic ;
using System.Linq ;
using System.Threading ;
2021-05-13 17:25:46 +02:00
using System.Threading.Tasks ;
2022-08-30 15:42:25 +02:00
using LockItShared.Services.BluetoothLock ;
2021-05-13 17:25:46 +02:00
using Plugin.BLE ;
using Plugin.BLE.Abstractions ;
2022-08-30 15:42:25 +02:00
using Plugin.BLE.Abstractions.Contracts ;
2021-05-13 17:25:46 +02:00
using Serilog ;
2022-08-30 15:42:25 +02:00
using TINK.Model.Connector ;
using TINK.Model.Device ;
2021-05-13 17:25:46 +02:00
using TINK.Services.BluetoothLock.Exception ;
2022-08-30 15:42:25 +02:00
using TINK.Services.BluetoothLock.Tdo ;
2021-05-13 17:25:46 +02:00
using Xamarin.Essentials ;
namespace TINK.Services.BluetoothLock.BLE
{
2022-09-06 16:08:19 +02:00
public class LockItByGuidService : LockItServiceBase , ILocksService
{
/// <summary> Constructs a LockItByGuidService object.</summary>
/// <param name="cipher">Encrpyting/ decrypting object.</param>
public LockItByGuidService ( ICipher cipher ) : base ( cipher )
{
}
/// <summary> Checks for locks which have not yet been discoverted and connects them. </summary>
/// <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 override async Task < IEnumerable < ILockService > > CheckConnectMissing ( IEnumerable < LockInfoAuthTdo > locksInfo , TimeSpan connectTimeout )
{
if ( DeviceInfo . Platform ! = DevicePlatform . Unknown & & MainThread . IsMainThread = = false )
{
throw new System . Exception ( "Can not connect to locks by guid. Platform must not be unknown and bluetooth code must be run on main thread." ) ;
}
// Get list of target locks without invalid entries.
var validLocksInfo = locksInfo
. Where ( x = > x . IsGuidValid )
. Where ( x = > x . K_seed . Length > 0 & & x . K_u . Length > 0 )
. ToList ( ) ;
var locksList = new List < ILockService > ( ) ;
// Connect to
foreach ( var lockInfo in validLocksInfo )
{
if ( DeviceList . Any ( x = > x . Name . GetBluetoothLockId ( ) = = lockInfo . Id | | x . Guid = = lockInfo . Guid ) )
{
// Device is already connected.
continue ;
}
// Connect to device and authenticate.
ILockService lockIt = null ;
try
{
lockIt = await ConnectByGuid ( lockInfo , connectTimeout ) ;
}
catch ( System . Exception exception )
{
// Member is called for background update of missing devices.
// Do not display any error messages.
Log . ForContext < LockItByGuidService > ( ) . Error ( $"Authentication failed. {exception.Message}" ) ;
continue ;
}
if ( lockIt = = null )
{
continue ;
}
locksList . Add ( lockIt ) ;
}
return locksList ;
}
/// <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 < LockInfoTdo > ConnectAsync ( LockInfoAuthTdo authInfo , TimeSpan connectTimeout )
{
if ( DeviceInfo . Platform ! = DevicePlatform . Unknown & & MainThread . IsMainThread = = false )
{
throw new System . Exception ( "Can not connect to lock by guid. Platform must not be unknown and bluetooth code must be run on main thread." ) ;
}
// Connect to device and authenticate.
var lockIt = await ConnectByGuid ( authInfo , connectTimeout ) ;
if ( lockIt = = null )
{
return new LockInfoTdo . Builder { Id = authInfo . Id , Guid = authInfo . Guid , State = null } . Build ( ) ;
}
DeviceList . Add ( lockIt ) ;
return await lockIt . GetLockStateAsync ( ) ;
}
/// <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>
private async Task < ILockService > ConnectByGuid ( LockInfoAuthTdo authInfo , TimeSpan connectTimeout )
{
if ( authInfo . Guid = = TextToLockItTypeHelper . INVALIDLOCKGUID )
{
Log . ForContext < LockItByGuidService > ( ) . Error ( $"Can not connect to lock {authInfo.Id}. Guid is unknown." ) ;
throw new GuidUnknownException ( ) ;
}
var lockIt = DeviceList . FirstOrDefault ( x = > x . Name . GetBluetoothLockId ( ) = = authInfo . Id | | x . Guid = = authInfo . Guid ) ;
if ( lockIt ! = null & & lockIt . GetDeviceState ( ) = = DeviceState . Connected )
{
// Device is already connected.
return lockIt ;
}
var adapter = CrossBluetoothLE . Current . Adapter ;
Log . ForContext < LockItByGuidService > ( ) . Debug ( $"Request connect to device {authInfo.Id}." ) ;
if ( LockItByGuidServiceHelper . DevelGuids . ContainsKey ( authInfo . Id ) & & LockItByGuidServiceHelper . DevelGuids [ authInfo . Id ] ! = authInfo . Guid )
throw new System . Exception ( $"Invalid Guid {authInfo.Guid} for lock with id {authInfo.Id} detected. Guid should be {LockItByGuidServiceHelper.DevelGuids[authInfo.Id]}." ) ;
IDevice device ;
var cts = new CancellationTokenSource ( connectTimeout ) ;
// Step 1: Perform bluetooth connect.
try
{
device = await adapter . ConnectToKnownDeviceAsync (
authInfo . Guid ,
new ConnectParameters ( forceBleTransport : true ) , // Force BLE transport
cts . Token ) ;
}
catch ( System . Exception exception )
{
if ( exception is TaskCanceledException )
{
// A timeout occurred.
throw new System . Exception ( $"Can not connect to lock by guid.\r\nTimeout of {connectTimeout.TotalMilliseconds} [ms] elapsed." , exception ) ;
}
Log . ForContext < LockItByGuidService > ( ) . Error ( "Bluetooth connect to known device request failed. {Exception}" , exception ) ;
throw new System . Exception ( $"Can not connect to lock by guid.\r\n{exception.Message}" , exception ) ;
}
// Step 2: Authenticate.
lockIt = await LockItEventBased . Authenticate ( device , authInfo , CrossBluetoothLE . Current . Adapter , Cipher ) ;
if ( lockIt = = null )
{
Log . ForContext < LockItByGuidService > ( ) . Error ( "Connect to device failed." ) ;
return null ;
}
return lockIt ;
}
}
2021-05-13 17:25:46 +02:00
}