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