Version 3.0.338

This commit is contained in:
Anja Müller-Meißner 2022-09-06 16:08:19 +02:00 committed by Anja
parent 573fe77e12
commit 0468955d49
751 changed files with 62747 additions and 60672 deletions

File diff suppressed because it is too large Load diff

View file

@ -16,147 +16,147 @@ using Xamarin.Essentials;
namespace TINK.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)
{
}
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.");
}
/// <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();
// 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>();
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
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;
}
// 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);
}
locksList.Add(lockIt);
}
return locksList;
}
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.");
}
/// <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);
// 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();
}
if (lockIt == null)
{
return new LockInfoTdo.Builder { Id = authInfo.Id, Guid = authInfo.Guid, State = null }.Build();
}
DeviceList.Add(lockIt);
DeviceList.Add(lockIt);
return await lockIt.GetLockStateAsync();
}
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();
}
/// <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 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}.");
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]}.");
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);
}
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);
}
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);
// 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;
}
if (lockIt == null)
{
Log.ForContext<LockItByGuidService>().Error("Connect to device failed.");
return null;
}
return lockIt;
}
}
return lockIt;
}
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@ -15,291 +15,291 @@ using Xamarin.Essentials;
namespace TINK.Services.BluetoothLock.BLE
{
/// <summary> Manages ILockIt- Locks.</summary>
public abstract class LockItByScanServiceBase : LockItServiceBase
{
/// <summary> Service to manage bluetooth stack. </summary>
private IBluetoothLE BluetoothService { get; }
/// <summary> Manages ILockIt- Locks.</summary>
public abstract class LockItByScanServiceBase : LockItServiceBase
{
/// <summary> Service to manage bluetooth stack. </summary>
private IBluetoothLE BluetoothService { get; }
/// <summary> Returns true if location permission is required (Android) but on given. </summary>
private Func<Task<bool>> IsLocationPermissionMissingDelegate { get; }
/// <summary> Returns true if location permission is required (Android) but on given. </summary>
private Func<Task<bool>> IsLocationPermissionMissingDelegate { get; }
/// <summary> Returns true if location service is required (Android) and off. </summary>
private Func<bool> IsLocationRequiredAndOffDelegate { get; }
/// <summary> Returns true if location service is required (Android) and off. </summary>
private Func<bool> IsLocationRequiredAndOffDelegate { get; }
private Func<IDevice, LockInfoAuthTdo, IAdapter, Task<LockItBase>> AuthenticateDelegate { get; set; }
private Func<IDevice, LockInfoAuthTdo, IAdapter, Task<LockItBase>> AuthenticateDelegate { get; set; }
public LockItByScanServiceBase(
ICipher cipher,
Func<IDevice, LockInfoAuthTdo, IAdapter, Task<LockItBase>> authenticateDelegate,
IBluetoothLE bluetoothLE,
Func<Task<bool>> isLocationPermissionMissingDelegate,
Func<bool> isLocationRequiredAndOffDelegate) : base(cipher)
{
BluetoothService = bluetoothLE
?? throw new ArgumentException($"Can not instantiate {nameof(LockItByScanServiceBase)}- object. No bluetooth service available.");
public LockItByScanServiceBase(
ICipher cipher,
Func<IDevice, LockInfoAuthTdo, IAdapter, Task<LockItBase>> authenticateDelegate,
IBluetoothLE bluetoothLE,
Func<Task<bool>> isLocationPermissionMissingDelegate,
Func<bool> isLocationRequiredAndOffDelegate) : base(cipher)
{
BluetoothService = bluetoothLE
?? throw new ArgumentException($"Can not instantiate {nameof(LockItByScanServiceBase)}- object. No bluetooth service available.");
IsLocationPermissionMissingDelegate = isLocationPermissionMissingDelegate
?? throw new ArgumentException($"Can not instantiate {nameof(LockItByScanServiceBase)}- object. No location permission missing delegate available.");
IsLocationPermissionMissingDelegate = isLocationPermissionMissingDelegate
?? throw new ArgumentException($"Can not instantiate {nameof(LockItByScanServiceBase)}- object. No location permission missing delegate available.");
IsLocationRequiredAndOffDelegate = isLocationRequiredAndOffDelegate
?? throw new ArgumentException($"Can not instantiate {nameof(LockItByScanServiceBase)}- object. No location .");
IsLocationRequiredAndOffDelegate = isLocationRequiredAndOffDelegate
?? throw new ArgumentException($"Can not instantiate {nameof(LockItByScanServiceBase)}- object. No location .");
AuthenticateDelegate = authenticateDelegate;
}
AuthenticateDelegate = authenticateDelegate;
}
/// <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 name. Platform must not be unknown and bluetooth code must be run on main thread.");
}
/// <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 name. Platform must not be unknown and bluetooth code must be run on main thread.");
}
Log.ForContext<LockItByScanServiceBase>().Debug($"Request to connect to device {authInfo.Id}.");
Log.ForContext<LockItByScanServiceBase>().Debug($"Request to connect to device {authInfo.Id}.");
var lockIt = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == authInfo.Id || x.Guid == authInfo.Guid);
var lockIt = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == authInfo.Id || x.Guid == authInfo.Guid);
if (lockIt != null && lockIt.GetDeviceState() == DeviceState.Connected)
{
// Nothing to do
Log.ForContext<LockItByScanServiceBase>().Debug($"Nothing to do. Lock is already connected.");
return await lockIt.GetLockStateAsync();
}
if (lockIt != null && lockIt.GetDeviceState() == DeviceState.Connected)
{
// Nothing to do
Log.ForContext<LockItByScanServiceBase>().Debug($"Nothing to do. Lock is already connected.");
return await lockIt.GetLockStateAsync();
}
if (lockIt != null)
{
// Reconnect required
Log.ForContext<LockItByScanServiceBase>().Debug($"Device {lockIt.Name} has been already connected but needs reconnect.");
await lockIt.ReconnectAsync(authInfo, connectTimeout);
return await lockIt.GetLockStateAsync();
}
if (lockIt != null)
{
// Reconnect required
Log.ForContext<LockItByScanServiceBase>().Debug($"Device {lockIt.Name} has been already connected but needs reconnect.");
await lockIt.ReconnectAsync(authInfo, connectTimeout);
return await lockIt.GetLockStateAsync();
}
// Device has not yet been discovered.
Log.ForContext<LockItByScanServiceBase>().Verbose("Starting scan for new devices...");
var newlyDiscovertedDevice = await ScanForNewDevices(new List<int> { authInfo.Id }, connectTimeout);
// Device has not yet been discovered.
Log.ForContext<LockItByScanServiceBase>().Verbose("Starting scan for new devices...");
var newlyDiscovertedDevice = await ScanForNewDevices(new List<int> { authInfo.Id }, connectTimeout);
var bleDevice = newlyDiscovertedDevice.FirstOrDefault(x => x.Name.GetBluetoothLockId() == authInfo.Id);
var bleDevice = newlyDiscovertedDevice.FirstOrDefault(x => x.Name.GetBluetoothLockId() == authInfo.Id);
if (bleDevice == null)
{
// Try to check why lock was not found.
var bluetoothState = await BluetoothService.GetBluetoothState();
switch (bluetoothState)
{
case BluetoothState.On:
break;
if (bleDevice == null)
{
// Try to check why lock was not found.
var bluetoothState = await BluetoothService.GetBluetoothState();
switch (bluetoothState)
{
case BluetoothState.On:
break;
case BluetoothState.Off:
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because bluetooth is off.");
throw new ConnectBluetoothNotOnException();
case BluetoothState.Off:
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because bluetooth is off.");
throw new ConnectBluetoothNotOnException();
default:
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because unexpected bluetooth state {state} detected.", bluetoothState);
throw new ConnectBluetoothNotOnException(bluetoothState);
}
default:
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because unexpected bluetooth state {state} detected.", bluetoothState);
throw new ConnectBluetoothNotOnException(bluetoothState);
}
if (await IsLocationPermissionMissingDelegate())
{
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because missing location permission.");
throw new ConnectLocationPermissionMissingException();
}
if (IsLocationRequiredAndOffDelegate())
{
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because location is off.");
throw new ConnectLocationOffException();
}
if (await IsLocationPermissionMissingDelegate())
{
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because missing location permission.");
throw new ConnectLocationPermissionMissingException();
}
if (IsLocationRequiredAndOffDelegate())
{
Log.ForContext<LockItByScanServiceBase>().Debug("Lock probable not found because location is off.");
throw new ConnectLocationOffException();
}
Log.ForContext<LockItByScanServiceBase>().Debug("Can not connect because device was not discovered. List of discovered devices {deviceList}.", String.Join(";", newlyDiscovertedDevice?.Select(x => x?.Id)));
throw new OutOfReachException();
}
Log.ForContext<LockItByScanServiceBase>().Debug("Can not connect because device was not discovered. List of discovered devices {deviceList}.", String.Join(";", newlyDiscovertedDevice?.Select(x => x?.Id)));
throw new OutOfReachException();
}
var adapter = CrossBluetoothLE.Current.Adapter;
var cts = new CancellationTokenSource(connectTimeout);
var adapter = CrossBluetoothLE.Current.Adapter;
var cts = new CancellationTokenSource(connectTimeout);
// Connect to device and authenticate.
Log.ForContext<LockItByScanServiceBase>().Debug($"Device sucessfully discovered. Request connect to device {bleDevice?.Name}. Connect state is {bleDevice?.State}.");
try
{
await adapter.ConnectToDeviceAsync(
bleDevice,
new ConnectParameters(forceBleTransport: true /* Force BLE transport */),
cts.Token);
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Can not connect to device by name. {Exception}", exception);
if (exception is TaskCanceledException)
{
// A timeout occurred.
throw new System.Exception($"Can not connect to lock by name.\r\nTimeout is {connectTimeout.TotalMilliseconds} [ms].\r\n{exception.Message}", exception);
}
// Connect to device and authenticate.
Log.ForContext<LockItByScanServiceBase>().Debug($"Device sucessfully discovered. Request connect to device {bleDevice?.Name}. Connect state is {bleDevice?.State}.");
try
{
await adapter.ConnectToDeviceAsync(
bleDevice,
new ConnectParameters(forceBleTransport: true /* Force BLE transport */),
cts.Token);
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Can not connect to device by name. {Exception}", exception);
if (exception is TaskCanceledException)
{
// A timeout occurred.
throw new System.Exception($"Can not connect to lock by name.\r\nTimeout is {connectTimeout.TotalMilliseconds} [ms].\r\n{exception.Message}", exception);
}
Log.ForContext<LockItByScanServiceBase>().Error("Bluetooth connect to known device request failed. {Exception}", exception);
throw new System.Exception($"Can not connect to lock by name. {exception.Message}", exception);
}
Log.ForContext<LockItByScanServiceBase>().Error("Bluetooth connect to known device request failed. {Exception}", exception);
throw new System.Exception($"Can not connect to lock by name. {exception.Message}", exception);
}
lockIt = await AuthenticateDelegate(bleDevice, authInfo, CrossBluetoothLE.Current.Adapter);
if (lockIt == null)
{
await adapter.DisconnectDeviceAsync(bleDevice);
return await Task.FromResult<LockInfoTdo>(null);
}
lockIt = await AuthenticateDelegate(bleDevice, authInfo, CrossBluetoothLE.Current.Adapter);
if (lockIt == null)
{
await adapter.DisconnectDeviceAsync(bleDevice);
return await Task.FromResult<LockInfoTdo>(null);
}
DeviceList.Add(lockIt);
DeviceList.Add(lockIt);
return await lockIt.GetLockStateAsync();
}
return await lockIt.GetLockStateAsync();
}
/// <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 for each lock to be connected. </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)
{
// Get list of target locks without invalid entries.
var validLocksInfo = locksInfo
.Where(x => x.IsIdValid)
.Where(x => x.K_seed.Length > 0 && x.K_u.Length > 0)
.ToList();
/// <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 for each lock to be connected. </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)
{
// Get list of target locks without invalid entries.
var validLocksInfo = locksInfo
.Where(x => x.IsIdValid)
.Where(x => x.K_seed.Length > 0 && x.K_u.Length > 0)
.ToList();
// Get Locks to scan for
var locksInfoToScanFor = validLocksInfo.Where(x => !DeviceList.Any(y => y.Name.GetBluetoothLockId() == x.Id)).ToList();
// Get Locks to scan for
var locksInfoToScanFor = validLocksInfo.Where(x => !DeviceList.Any(y => y.Name.GetBluetoothLockId() == x.Id)).ToList();
// Check if bluetooth scan is required
if (locksInfoToScanFor.Count <= 0)
{
// No Scan required.
return new List<LockItBase>();
}
// Check if bluetooth scan is required
if (locksInfoToScanFor.Count <= 0)
{
// No Scan required.
return new List<LockItBase>();
}
// Do bluetooth scan
var scanTargets = locksInfoToScanFor.Select(x => x.Id).ToList();
var newlyDiscovertedDevicesList = await ScanForNewDevices(scanTargets, TimeSpan.FromSeconds(connectTimeout.TotalSeconds * scanTargets.Count));
// Do bluetooth scan
var scanTargets = locksInfoToScanFor.Select(x => x.Id).ToList();
var newlyDiscovertedDevicesList = await ScanForNewDevices(scanTargets, TimeSpan.FromSeconds(connectTimeout.TotalSeconds * scanTargets.Count));
var locksList = new List<LockItBase>();
var locksList = new List<LockItBase>();
// Connect to newly discovered devices.
foreach (var lockInfo in locksInfoToScanFor)
{
var device = newlyDiscovertedDevicesList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == lockInfo.Id);
if (device == null)
{
// No device found for given lock.
continue;
}
// Connect to newly discovered devices.
foreach (var lockInfo in locksInfoToScanFor)
{
var device = newlyDiscovertedDevicesList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == lockInfo.Id);
if (device == null)
{
// No device found for given lock.
continue;
}
Log.ForContext<LockItByScanServiceBase>().Debug($"Request connect to device {device?.Name}. Connect state is {device?.State}.");
Log.ForContext<LockItByScanServiceBase>().Debug($"Request connect to device {device?.Name}. Connect state is {device?.State}.");
var cts = new CancellationTokenSource(connectTimeout);
var cts = new CancellationTokenSource(connectTimeout);
try
{
// Connect to device and authenticate.
await CrossBluetoothLE.Current.Adapter.ConnectToDeviceAsync(
device,
new ConnectParameters(forceBleTransport: true), // Force BLE transport
cts.Token);
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Can not connect to lock. {Exception}", exception);
continue;
}
try
{
// Connect to device and authenticate.
await CrossBluetoothLE.Current.Adapter.ConnectToDeviceAsync(
device,
new ConnectParameters(forceBleTransport: true), // Force BLE transport
cts.Token);
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Can not connect to lock. {Exception}", exception);
continue;
}
LockItBase lockIt = null;
try
{
lockIt = await AuthenticateDelegate(device, lockInfo, CrossBluetoothLE.Current.Adapter);
}
catch (System.Exception exception)
{
// Member is called for background update of missing devices.
// Do not display any error messages.
Log.ForContext<LockItByScanServiceBase>().Error($"Authentication failed. {exception.Message}");
continue;
}
if (lockIt == null)
{
// connectiong to device succeded.
continue;
}
LockItBase lockIt = null;
try
{
lockIt = await AuthenticateDelegate(device, lockInfo, CrossBluetoothLE.Current.Adapter);
}
catch (System.Exception exception)
{
// Member is called for background update of missing devices.
// Do not display any error messages.
Log.ForContext<LockItByScanServiceBase>().Error($"Authentication failed. {exception.Message}");
continue;
}
if (lockIt == null)
{
// connectiong to device succeded.
continue;
}
Log.ForContext<LockItByScanServiceBase>().Debug($"Auth succeeded for device {device}.");
locksList.Add(lockIt);
}
Log.ForContext<LockItByScanServiceBase>().Debug($"Auth succeeded for device {device}.");
locksList.Add(lockIt);
}
return locksList;
}
return locksList;
}
/// <summary> Scans for a given set of devices.</summary>
/// <param name="targetLocks">Locks to scan for.</param>
/// <returns></returns>
private static async Task<IEnumerable<IDevice>> ScanForNewDevices(
IEnumerable<int> targetLocks,
TimeSpan connectTimeout)
{
var adapter = CrossBluetoothLE.Current.Adapter;
/// <summary> Scans for a given set of devices.</summary>
/// <param name="targetLocks">Locks to scan for.</param>
/// <returns></returns>
private static async Task<IEnumerable<IDevice>> ScanForNewDevices(
IEnumerable<int> targetLocks,
TimeSpan connectTimeout)
{
var adapter = CrossBluetoothLE.Current.Adapter;
var newlyDiscovertedDevicesList = new List<IDevice>();
var newlyDiscovertedDevicesList = new List<IDevice>();
var cts = new CancellationTokenSource(connectTimeout);
var cts = new CancellationTokenSource(connectTimeout);
// Not all devices already connected.
// Start scanning
adapter.DeviceDiscovered += (s, a) =>
{
var name = a.Device.Name;
if (string.IsNullOrEmpty(name))
{
// Lock without name detected.
Log.ForContext<LockItByScanServiceBase>().Debug($"Lock without name discovered.");
return;
}
// Not all devices already connected.
// Start scanning
adapter.DeviceDiscovered += (s, a) =>
{
var name = a.Device.Name;
if (string.IsNullOrEmpty(name))
{
// Lock without name detected.
Log.ForContext<LockItByScanServiceBase>().Debug($"Ble decvice without name discovered. RSSI={a.Device.Rssi}, GUID={a.Device.Id}, State={a.Device.State}.");
return;
}
if (targetLocks.Where(x => x != TextToLockItTypeHelper.INVALIDLOCKID).Contains(name.GetBluetoothLockId()))
{
Log.ForContext<LockItByScanServiceBase>().Debug($"New LOCKIT device {name} discovered.");
if (targetLocks.Where(x => x != TextToLockItTypeHelper.INVALIDLOCKID).Contains(name.GetBluetoothLockId()))
{
Log.ForContext<LockItByScanServiceBase>().Debug($"New LOCKIT device {name} discovered. RSSI={a.Device.Rssi}, GUID={a.Device.Id}, State={a.Device.State}.");
newlyDiscovertedDevicesList.Add(a.Device);
newlyDiscovertedDevicesList.Add(a.Device);
if (newlyDiscovertedDevicesList.Count() >= targetLocks.Count())
{
cts.Cancel();
}
if (newlyDiscovertedDevicesList.Count() >= targetLocks.Count())
{
cts.Cancel();
}
return;
}
return;
}
Log.ForContext<LockItByScanServiceBase>().Verbose($"Device of unknown advertisement name {name} discovered.");
};
Log.ForContext<LockItByScanServiceBase>().Verbose($"Device of unknown advertisement name {name} discovered. RSSI={a.Device.Rssi}, GUID={a.Device.Id}, State={a.Device.State}.");
};
try
{
await adapter.StartScanningForDevicesAsync(cancellationToken: cts.Token);
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Bluetooth scan failed. {Exception}", exception);
throw;
}
finally
{
try
{
await adapter.StopScanningForDevicesAsync();
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Stop scanning failed. {Excpetion}", exception);
throw;
}
}
try
{
await adapter.StartScanningForDevicesAsync(cancellationToken: cts.Token);
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Bluetooth scan failed. {Exception}", exception);
throw;
}
finally
{
try
{
await adapter.StopScanningForDevicesAsync();
}
catch (System.Exception exception)
{
Log.ForContext<LockItByScanServiceBase>().Error("Stop scanning failed. {Excpetion}", exception);
throw;
}
}
return newlyDiscovertedDevicesList;
}
}
return newlyDiscovertedDevicesList;
}
}
}

View file

@ -5,14 +5,14 @@ using TINK.Model.Device;
namespace TINK.Services.BluetoothLock.BLE
{
public class LockItByScanServiceEventBased : LockItByScanServiceBase, ILocksService
{
public LockItByScanServiceEventBased(ICipher cipher, IBluetoothLE bluetoothLE, Func<Task<bool>> isLocationPermissionMissingDelegate, Func<bool> isLocationRequiredAndOffDelegate) : base(
cipher,
(bleDevice, authInfo, adapter) => LockItEventBased.Authenticate(bleDevice, authInfo, adapter, cipher),
bluetoothLE,
isLocationPermissionMissingDelegate,
isLocationRequiredAndOffDelegate)
{ }
}
public class LockItByScanServiceEventBased : LockItByScanServiceBase, ILocksService
{
public LockItByScanServiceEventBased(ICipher cipher, IBluetoothLE bluetoothLE, Func<Task<bool>> isLocationPermissionMissingDelegate, Func<bool> isLocationRequiredAndOffDelegate) : base(
cipher,
(bleDevice, authInfo, adapter) => LockItEventBased.Authenticate(bleDevice, authInfo, adapter, cipher),
bluetoothLE,
isLocationPermissionMissingDelegate,
isLocationRequiredAndOffDelegate)
{ }
}
}

View file

@ -5,14 +5,14 @@ using TINK.Model.Device;
namespace TINK.Services.BluetoothLock.BLE
{
public class LockItByScanServicePolling : LockItByScanServiceBase, ILocksService
{
public LockItByScanServicePolling(ICipher cipher, IBluetoothLE bluetoothLE, Func<Task<bool>> isLocationPermissionMissingDelegate, Func<bool> isLocationRequiredAndOffDelegate) : base(
cipher,
(bleDevice, authInfo, adapter) => LockItPolling.Authenticate(bleDevice, authInfo, adapter, cipher),
bluetoothLE,
isLocationPermissionMissingDelegate,
isLocationRequiredAndOffDelegate)
{ }
}
public class LockItByScanServicePolling : LockItByScanServiceBase, ILocksService
{
public LockItByScanServicePolling(ICipher cipher, IBluetoothLE bluetoothLE, Func<Task<bool>> isLocationPermissionMissingDelegate, Func<bool> isLocationRequiredAndOffDelegate) : base(
cipher,
(bleDevice, authInfo, adapter) => LockItPolling.Authenticate(bleDevice, authInfo, adapter, cipher),
bluetoothLE,
isLocationPermissionMissingDelegate,
isLocationRequiredAndOffDelegate)
{ }
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Threading;
using System.Threading.Tasks;
using Plugin.BLE.Abstractions.Contracts;
@ -12,304 +12,312 @@ using Xamarin.Essentials;
namespace TINK.Services.BluetoothLock.BLE
{
public class LockItEventBased : LockItBase
{
private LockItEventBased(IDevice device, IAdapter adapter, ICipher cipher) : base(device, adapter, cipher)
{
}
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> 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> 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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
/// <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. Platform must not be unknown and 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);
}
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);
}
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}.");
Log.ForContext<LockItEventBased>().Debug($"Request to open lock. Current locking state is {lockingState}, counter value {ActivateLockWriteCounter}.");
double batteryPercentage = double.NaN;
double batteryPercentage = double.NaN;
var lockStateCharacteristic = await GetStateCharacteristicAsync();
var batteryStateCharacteristic = await GetBatteryCharacteristicAsync();
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);
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);
}
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
{
await lockStateCharacteristic.StartUpdatesAsync();
await batteryStateCharacteristic.StartUpdatesAsync();
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Starting updates when opening lock failed.{Exception}", ex);
return await Task.FromResult((LockitLockingState?)null);
}
try
{
var result = await OpenCloseLock(true); // Close lock;
try
{
var result = await OpenCloseLockAsync(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);
}
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();
// 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);
}
}
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;
}
if (lockingState == null)
{
return null;
}
switch (lockingState.Value)
{
case LockitLockingState.Open:
return lockingState.Value;
switch (lockingState.Value)
{
case LockitLockingState.Open:
Log.ForContext<LockItEventBased>().Information($"Lock was opened successfully.");
return lockingState.Value;
case LockitLockingState.CouldntOpenBoldBlocked:
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
throw new CouldntOpenBoldIsBlockedException();
case LockitLockingState.CouldntOpenBoldBlocked:
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
Log.ForContext<LockItEventBased>().Debug($"Opening lock failed. Bold is blocked.");
throw new CouldntOpenBoldIsBlockedException();
case LockitLockingState.Unknown:
// Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....)
throw new CouldntOpenBoldWasBlockedException();
case LockitLockingState.Unknown:
// Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....)
Log.ForContext<LockItEventBased>().Debug($"Opening lock failed. Bold was blocked.");
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());
}
}
default:
// Comprises values
// - LockitLockingState.Closed
// - LockitLockingState.Unknown
// - LockitLockingState.CouldntOpenBoldBlocked
// Internal error which sould never occure. Lock refuses to open but connection is ok.
Log.ForContext<LockItEventBased>().Debug($"Opening lock failed. Unexpected lock state {lockingState.Value.GetLockingState()} detected.");
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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
/// <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. Platform must not be unknown and 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);
}
// 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);
}
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();
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);
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> 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
{
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);
}
try
{
Log.ForContext<LockItEventBased>().Debug($"Request to close lock. Current state is {lockingState}, counter value {ActivateLockWriteCounter}.");
var result = await OpenCloseLockAsync(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();
// 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);
}
}
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;
}
if (lockingState == null)
{
return null;
}
switch (lockingState.Value)
{
case LockitLockingState.CouldntCloseBoldBlocked:
// Expected error. ILockIt could not be closed (Spoke blocks lock, ....)
throw new CouldntCloseBoldBlockedException();
switch (lockingState.Value)
{
case LockitLockingState.CouldntCloseBoldBlocked:
// Expected error. ILockIt could not be closed (Spoke blocks lock, ....)
Log.ForContext<LockItEventBased>().Debug($"Closing lock failed. Bold is blocked.");
throw new CouldntCloseBoldBlockedException();
case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving)
throw new CouldntCloseMovingException();
case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving)
Log.ForContext<LockItEventBased>().Debug($"Closing lock failed. Bike is moving.");
throw new CouldntCloseMovingException();
case LockitLockingState.Closed:
return lockingState;
case LockitLockingState.Closed:
Log.ForContext<LockItEventBased>().Information($"Lock was closed successfully.");
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());
}
}
default:
// Comprises values
// - LockitLockingState.Open
// - LockitLockingState.Unknown
// - LockitLockingState.CouldntOpenBoldBlocked
// Internal error which sould never occurre. Lock refuses to close but connection is ok.
Log.ForContext<LockItEventBased>().Debug($"Opening lock failed. Unexpected lock state {lockingState.Value.GetLockingState()} detected.");
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;
}
/// <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);
}
};
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;
}
/// <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;
}
};
}
return value[0];
}
catch (System.Exception ex)
{
Log.ForContext<LockItEventBased>().Error("Error on sinking battery state characteristic on opening.{Exception}", ex);
return double.NaN;
}
};
}
}

View file

@ -1,4 +1,4 @@
using System;
using System;
using System.Diagnostics;
using System.Threading.Tasks;
using Plugin.BLE.Abstractions.Contracts;
@ -11,208 +11,218 @@ using Xamarin.Essentials;
namespace TINK.Services.BluetoothLock.BLE
{
public class LockItPolling : LockItBase
{
public LockItPolling(IDevice device, IAdapter adapter, ICipher cipher) : base(device, adapter, cipher)
{
}
public class LockItPolling : LockItBase
{
public LockItPolling(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 override async Task ReconnectAsync(
LockInfoAuthTdo authInfo,
TimeSpan connectTimeout)
=> await ReconnectAsync(
authInfo,
connectTimeout,
() => new LockItPolling(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 override async Task ReconnectAsync(
LockInfoAuthTdo authInfo,
TimeSpan connectTimeout)
=> await ReconnectAsync(
authInfo,
connectTimeout,
() => new LockItPolling(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 LockItPolling(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 LockItPolling(device, adapter, cipher));
/// <summary> Opens the lock. </summary>
public async override Task<LockitLockingState?> OpenAsync()
{
if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false)
{
throw new System.Exception("Can not open lock. Platform must not be unknown and bluetooth code must be run on main thread.");
}
/// <summary> Opens the lock. </summary>
public async override Task<LockitLockingState?> OpenAsync()
{
if (DeviceInfo.Platform != DevicePlatform.Unknown && MainThread.IsMainThread == false)
{
throw new System.Exception("Can not open lock. Platform must not be unknown and bluetooth code must be run on main thread.");
}
var info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Information("Can not open lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
var info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Information("Can not open lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
if (info.State.Value.GetLockingState() == LockingState.Open)
{
// Lock is already open.
Log.ForContext<LockItPolling>().Information("No need to open lock. Lock is already open.");
return await Task.FromResult((LockitLockingState?)null);
}
if (info.State.Value.GetLockingState() == LockingState.Open)
{
// Lock is already open.
Log.ForContext<LockItPolling>().Information("No need to open lock. Lock is already open.");
return await Task.FromResult((LockitLockingState?)null);
}
Log.ForContext<LockItPolling>().Debug($"Request to closed lock. Current lockikng state is {info}, counter value {ActivateLockWriteCounter}.");
Log.ForContext<LockItPolling>().Debug($"Request to open lock. Current locking state is {info}, counter value {ActivateLockWriteCounter}.");
var result = await OpenCloseLock(
true); // Close lock);
var result = await OpenCloseLockAsync(
true); // Close lock);
if (!result)
{
// State did not change. Return previous state.
Log.ForContext<LockItPolling>().Information($"Opening lock failed.");
return await Task.FromResult(info.State.Value);
}
if (!result)
{
// State did not change. Return previous state.
Log.ForContext<LockItPolling>().Information($"Opening lock failed.");
return await Task.FromResult(info.State.Value);
}
info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Information($"State after open command unknown. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Information($"State after open command unknown. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
var watch = new Stopwatch();
watch.Start();
var watch = new Stopwatch();
watch.Start();
while (info?.State != null
&& info.State.Value != LockitLockingState.CouldntOpenBoldBlocked
&& info.State.Value != LockitLockingState.Open
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
info = await GetLockStateAsync(true); // While opening lock seems not always respond to reading operations.
Log.ForContext<LockItPolling>().Information($"Current lock state is {info?.State.Value}.");
}
while (info?.State != null
&& info.State.Value != LockitLockingState.CouldntOpenBoldBlocked
&& info.State.Value != LockitLockingState.Open
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
info = await GetLockStateAsync(true); // While opening lock seems not always respond to reading operations.
Log.ForContext<LockItPolling>().Debug($"Waiting for lock to open. Current lock state is {info?.State.Value}.");
}
if (info == null)
{
return null;
}
if (info == null)
{
Log.ForContext<LockItPolling>().Fatal($"Opening lock failed. State object is null.");
return null;
}
switch (info.State.Value)
{
case LockitLockingState.Open:
return info.State.Value;
switch (info.State.Value)
{
case LockitLockingState.Open:
Log.ForContext<LockItPolling>().Information($"Lock was opened successfully.");
return info.State.Value;
case LockitLockingState.CouldntOpenBoldBlocked:
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
throw new CouldntOpenBoldIsBlockedException();
case LockitLockingState.CouldntOpenBoldBlocked:
// Expected error. ILockIt count not be opened (Spoke blocks lock, ....)
Log.ForContext<LockItPolling>().Debug($"Opening lock failed. Bold is blocked.");
throw new CouldntOpenBoldIsBlockedException();
case LockitLockingState.Unknown:
// Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....)
throw new CouldntOpenBoldWasBlockedException();
case LockitLockingState.Unknown:
// Expected error. ILockIt count not be opened (Spoke has blocked/ blocks lock, ....)
Log.ForContext<LockItPolling>().Debug($"Opening lock failed. Bold was blocked.");
throw new CouldntOpenBoldWasBlockedException();
default:
// Comprises values
// - LockitLockingState.Closed
// - LockitLockingState.CouldntCloseMoving (should never happen because command open was send)
// - LockitLockingState.CouldntCloseBoldBlocked (should never happen because command open was send)
// Internal error which sould never occure. Lock refuses to open but connection is ok.
throw new CouldntOpenInconsistentStateExecption(info.State.Value.GetLockingState());
}
}
default:
// Comprises values
// - LockitLockingState.Closed
// - LockitLockingState.CouldntCloseMoving (should never happen because command open was send)
// - LockitLockingState.CouldntCloseBoldBlocked (should never happen because command open was send)
// Internal error which sould never occure. Lock refuses to open but connection is ok.
Log.ForContext<LockItPolling>().Debug($"Opening lock failed. Unexpected lock state {info.State.Value.GetLockingState()} detected.");
throw new CouldntOpenInconsistentStateExecption(info.State.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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
/// <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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
// Get current state
var info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Error("Can not close lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
// Get current state
var info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Error("Can not close lock. Device is not reachable (get state).");
return await Task.FromResult((LockitLockingState?)null);
}
if (info.State.Value.GetLockingState() == LockingState.Closed)
{
// Lock is already closed.
Log.ForContext<LockItPolling>().Error("No need to close lock. Lock is already closed.");
return await Task.FromResult(info.State.Value);
}
if (info.State.Value.GetLockingState() == LockingState.Closed)
{
// Lock is already closed.
Log.ForContext<LockItPolling>().Error("No need to close lock. Lock is already closed.");
return await Task.FromResult(info.State.Value);
}
Log.ForContext<LockItPolling>().Debug($"Request to closed lock. Current state is {info}, counter value {ActivateLockWriteCounter}.");
Log.ForContext<LockItPolling>().Debug($"Request to close lock. Current locking state is {info}, counter value {ActivateLockWriteCounter}.");
var result = await OpenCloseLock(false); // Close lock
if (!result)
{
// State did not change. Return previous state.
Log.ForContext<LockItPolling>().Information($"Closing lock failed.");
return await Task.FromResult(info.State.Value);
}
var result = await OpenCloseLockAsync(false); // Close lock
if (!result)
{
// State did not change. Return previous state.
Log.ForContext<LockItPolling>().Information($"Closing lock failed.");
return await Task.FromResult(info.State.Value);
}
// Get lock state until either lock state chaneges or until log gets unreachable.
info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Information($"Lock state after close command unknown.");
return await Task.FromResult((LockitLockingState?)null);
}
// Get lock state until either lock state chaneges or until log gets unreachable.
info = await GetLockStateAsync();
if (info?.State == null)
{
// Device not reachable.
Log.ForContext<LockItPolling>().Information($"Lock state after close command unknown.");
return await Task.FromResult((LockitLockingState?)null);
}
var watch = new Stopwatch();
watch.Start();
var watch = new Stopwatch();
watch.Start();
while (info.State != null
&& info.State.Value != LockitLockingState.CouldntCloseBoldBlocked
&& info.State.Value != LockitLockingState.CouldntCloseMoving
&& info.State.Value != LockitLockingState.Closed
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
info = await GetLockStateAsync(true); ; // While closing lock seems not always respond to reading operations.
Log.ForContext<LockItPolling>().Information($"Current lock state is {info?.State.Value}.");
}
while (info.State != null
&& info.State.Value != LockitLockingState.CouldntCloseBoldBlocked
&& info.State.Value != LockitLockingState.CouldntCloseMoving
&& info.State.Value != LockitLockingState.Closed
&& watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS))
{
info = await GetLockStateAsync(true); ; // While closing lock seems not always respond to reading operations.
Log.ForContext<LockItPolling>().Debug($"Waiting for lock to close. Current lock state is {info?.State.Value}.");
}
if (info == null)
{
return null;
}
if (info == null)
{
Log.ForContext<LockItPolling>().Fatal($"Closing lock failed. State object is null.");
return null;
}
switch (info.State.Value)
{
case LockitLockingState.CouldntCloseBoldBlocked:
// Expected error. ILockIt could not be closed (Spoke blocks lock, ....)
throw new CouldntCloseBoldBlockedException();
switch (info.State.Value)
{
case LockitLockingState.CouldntCloseBoldBlocked:
// Expected error. ILockIt could not be closed (Spoke blocks lock, ....)
Log.ForContext<LockItPolling>().Debug($"Closing lock failed. Bold is blocked.");
throw new CouldntCloseBoldBlockedException();
case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving)
throw new CouldntCloseMovingException();
case LockitLockingState.CouldntCloseMoving:
// Expected error. ILockIt could not be closed (bike is moving)
Log.ForContext<LockItPolling>().Debug($"Closing lock failed. Bike is moving.");
throw new CouldntCloseMovingException();
case LockitLockingState.Closed:
// Everything is ok.
return info.State.Value;
case LockitLockingState.Closed:
// Everything is ok.
Log.ForContext<LockItPolling>().Information($"Lock was closed successfully.");
return info.State.Value;
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(info.State.Value.GetLockingState());
}
}
}
default:
// Comprises values
// - LockitLockingState.Open
// - LockitLockingState.Unknown
// - LockitLockingState.CouldntOpenBoldBlocked
// Internal error which sould never occurre. Lock refuses to close but connection is ok.
Log.ForContext<LockItPolling>().Debug($"Closing lock failed. Unexpected lock state {info.State.Value.GetLockingState()} detected.");
throw new CouldntCloseInconsistentStateExecption(info.State.Value.GetLockingState());
}
}
}
}

View file

@ -11,172 +11,172 @@ using Xamarin.Essentials;
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;
}
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; }
/// <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; }
protected ICipher Cipher { get; }
/// <summary> List of available or connected bluetooth devices. </summary>
protected List<ILockService> DeviceList = new List<ILockService>();
/// <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();
/// <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;
}
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);
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;
}
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;
}
}
}
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>();
}
/// <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);
// Reconnect locks.
await CheckReconnect(DeviceList, locksInfo, connectTimeout);
// Connect to locks which were not yet discovered.
DeviceList.AddRange(await CheckConnectMissing(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>();
// 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;
}
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());
}
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;
}
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);
/// <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);
}
/// <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;
}
}
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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
/// <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. Platform must not be unknown and bluetooth code must be run on main thread.");
}
Log.ForContext<LockItByScanServiceBase>().Debug($"Request to disconnect from device {bikeId}/ {bikeGuid}.");
Log.ForContext<LockItByScanServiceBase>().Debug($"Request to disconnect from device {bikeId}/ {bikeGuid}.");
var lockIt = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == bikeId || x.Guid == bikeGuid);
var lockIt = DeviceList.FirstOrDefault(x => x.Name.GetBluetoothLockId() == bikeId || x.Guid == bikeGuid);
if (lockIt == null)
{
// Nothing to do
return LockingState.UnknownDisconnected;
}
if (lockIt == null)
{
// Nothing to do
return LockingState.UnknownDisconnected;
}
DeviceList.Remove(lockIt);
DeviceList.Remove(lockIt);
if (lockIt.GetDeviceState() == DeviceState.Disconnected)
{
// Nothing to do
return LockingState.UnknownDisconnected;
}
if (lockIt.GetDeviceState() == DeviceState.Disconnected)
{
// Nothing to do
return LockingState.UnknownDisconnected;
}
await lockIt.Disconnect();
return LockingState.UnknownDisconnected;
}
}
await lockIt.Disconnect();
return LockingState.UnknownDisconnected;
}
}
}

View file

@ -2,8 +2,8 @@
namespace TINK.Services.BluetoothLock.BLE
{
public static class LockItServiceHelper
{
public static string Name { get => TextToLockItTypeHelper.ISHAREITADVERTISMENTTITLE; }
}
public static class LockItServiceHelper
{
public static string Name { get => TextToLockItTypeHelper.ISHAREITADVERTISMENTTITLE; }
}
}

View file

@ -2,34 +2,34 @@
namespace TINK.Services.BluetoothLock.BLE
{
public static class PluginBleHelper
{
/// <summary>
/// Maps Plugin.BLE device state to LockItShared device state.
/// </summary>
/// <param name="state">State to convert</param>
/// <returns></returns>
/// <exception cref="ArgumentException">Unexpected state detected.</exception>
public static DeviceState GetDeviceState(this Plugin.BLE.Abstractions.DeviceState state)
{
switch (state)
{
case Plugin.BLE.Abstractions.DeviceState.Disconnected:
return DeviceState.Disconnected;
public static class PluginBleHelper
{
/// <summary>
/// Maps Plugin.BLE device state to LockItShared device state.
/// </summary>
/// <param name="state">State to convert</param>
/// <returns></returns>
/// <exception cref="ArgumentException">Unexpected state detected.</exception>
public static DeviceState GetDeviceState(this Plugin.BLE.Abstractions.DeviceState state)
{
switch (state)
{
case Plugin.BLE.Abstractions.DeviceState.Disconnected:
return DeviceState.Disconnected;
case Plugin.BLE.Abstractions.DeviceState.Connecting:
return DeviceState.Connecting;
case Plugin.BLE.Abstractions.DeviceState.Connecting:
return DeviceState.Connecting;
case Plugin.BLE.Abstractions.DeviceState.Connected:
return DeviceState.Connected;
case Plugin.BLE.Abstractions.DeviceState.Connected:
return DeviceState.Connected;
case Plugin.BLE.Abstractions.DeviceState.Limited:
return DeviceState.Limited;
case Plugin.BLE.Abstractions.DeviceState.Limited:
return DeviceState.Limited;
default:
throw new ArgumentException($"Can not convert state Plugin.BLE-{state}.");
}
}
default:
throw new ArgumentException($"Can not convert state Plugin.BLE-{state}.");
}
}
}
}
}

View file

@ -4,38 +4,38 @@ using Plugin.BLE.Abstractions.Contracts;
namespace TINK.Services.BluetoothLock
{
public static class StateChecker
{
/// <summary>
/// Get current bluetooth state
/// </summary>
/// <remarks>See https://github.com/xabre/xamarin-bluetooth-le/issues/112#issuecomment-380994887.</remarks>
/// <param name="ble">Crossplatform bluetooth implementation object</param>
/// <returns>BluetoothState</returns>
public static Task<BluetoothState> GetBluetoothState(this IBluetoothLE ble)
{
var tcs = new TaskCompletionSource<BluetoothState>();
public static class StateChecker
{
/// <summary>
/// Get current bluetooth state
/// </summary>
/// <remarks>See https://github.com/xabre/xamarin-bluetooth-le/issues/112#issuecomment-380994887.</remarks>
/// <param name="ble">Crossplatform bluetooth implementation object</param>
/// <returns>BluetoothState</returns>
public static Task<BluetoothState> GetBluetoothState(this IBluetoothLE ble)
{
var tcs = new TaskCompletionSource<BluetoothState>();
if (ble.State != BluetoothState.Unknown)
{
// If we can detect state out of box just returning in
tcs.SetResult(ble.State);
}
else
{
// Otherwise let's setup dynamic event handler and wait for first state update
EventHandler<Plugin.BLE.Abstractions.EventArgs.BluetoothStateChangedArgs> handler = null;
handler = (o, e) =>
{
ble.StateChanged -= handler;
// and return it as our state
// we can have an 'Unknown' check here, but in normal situation it should never occur
tcs.SetResult(e.NewState);
};
ble.StateChanged += handler;
}
if (ble.State != BluetoothState.Unknown)
{
// If we can detect state out of box just returning in
tcs.SetResult(ble.State);
}
else
{
// Otherwise let's setup dynamic event handler and wait for first state update
EventHandler<Plugin.BLE.Abstractions.EventArgs.BluetoothStateChangedArgs> handler = null;
handler = (o, e) =>
{
ble.StateChanged -= handler;
// and return it as our state
// we can have an 'Unknown' check here, but in normal situation it should never occur
tcs.SetResult(e.NewState);
};
ble.StateChanged += handler;
}
return tcs.Task;
}
}
return tcs.Task;
}
}
}