sharee.bike-App/LockIt.BLE/Services/BluetoothLock/BLE/LockItByGuidService.cs
2024-04-09 12:53:23 +02:00

163 lines
5.8 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using LockIt.BusinessLogic.Services.BluetoothLock;
using Plugin.BLE;
using Plugin.BLE.Abstractions;
using Plugin.BLE.Abstractions.Contracts;
using Serilog;
using ShareeBike.Model.Connector;
using ShareeBike.Model.Device;
using ShareeBike.Services.BluetoothLock.Exception;
using ShareeBike.Services.BluetoothLock.Tdo;
using Xamarin.Essentials;
namespace ShareeBike.Services.BluetoothLock.BLE
{
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;
}
}
}