mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-01-05 13:06:27 +01:00
291 lines
8.8 KiB
C#
291 lines
8.8 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Threading.Tasks;
|
|
using Serilog;
|
|
using ShareeBike.Model.Connector;
|
|
using ShareeBike.Repository.Exception;
|
|
using ShareeBike.Services.BluetoothLock;
|
|
using ShareeBike.Services.BluetoothLock.Exception;
|
|
using ShareeBike.Services.BluetoothLock.Tdo;
|
|
using ShareeBike.Services.Geolocation;
|
|
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
|
|
|
|
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command
|
|
{
|
|
public static class OpenCommand
|
|
{
|
|
/// <summary>
|
|
/// Possible steps of opening a lock.
|
|
/// </summary>
|
|
public enum Step
|
|
{
|
|
OpeningLock,
|
|
WaitStopPolling,
|
|
GetLockInfos,
|
|
UpdateLockingState,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Possible states of opening a lock.
|
|
/// </summary>
|
|
public enum State
|
|
{
|
|
StopPollingFailed,
|
|
OutOfReachError,
|
|
CouldntOpenBoldStatusIsUnknownError,
|
|
CouldntOpenBoldIsBlockedError,
|
|
CouldntOpenInconsistentStateError,
|
|
GeneralOpenError,
|
|
WebConnectFailed,
|
|
ResponseIsInvalid,
|
|
BackendUpdateFailed
|
|
}
|
|
|
|
/// <summary>
|
|
/// Interface to notify view model about steps/ state changes of opening process.
|
|
/// </summary>
|
|
public interface IOpenCommandListener
|
|
{
|
|
/// <summary>
|
|
/// Reports current step.
|
|
/// </summary>
|
|
/// <param name="currentStep">Current step to report.</param>
|
|
void ReportStep(Step currentStep);
|
|
|
|
/// <summary>
|
|
/// Reports current state.
|
|
/// </summary>
|
|
/// <param name="currentState">Current state to report.</param>
|
|
/// <param name="message">Message describing the current state.</param>
|
|
/// <returns></returns>
|
|
Task ReportStateAsync(State currentState, string message);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens the lock and updates copri.
|
|
/// </summary>
|
|
/// <param name="listener">Interface to notify view model about steps/ state changes of opening process.</param>
|
|
/// <param name="stopPolling">Task which stops polling.</param>
|
|
public static async Task InvokeAsync<T>(
|
|
IBikeInfoMutable bike,
|
|
ILocksService lockService,
|
|
Func<bool> isConnectedDelegate,
|
|
Func<bool, IConnector> connectorFactory,
|
|
IOpenCommandListener listener,
|
|
Task stopPollingTask)
|
|
{
|
|
// Invokes member to notify about step being started.
|
|
void InvokeCurrentStep(Step step)
|
|
{
|
|
if (listener == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
listener.ReportStep(step);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking step-action for step {step} ", exception, step);
|
|
}
|
|
}
|
|
|
|
// Invokes member to notify about state change.
|
|
async Task InvokeCurrentStateAsync(State state, string message)
|
|
{
|
|
if (listener == null)
|
|
return;
|
|
|
|
try
|
|
{
|
|
await listener.ReportStateAsync(state, message);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.ForContext<T>().Error("An exception {@exception} was thrown invoking state-action for state {state} ", exception, state);
|
|
}
|
|
}
|
|
|
|
// Wait polling task to stop (on finished or on canceled).
|
|
async Task WaitForPendingTasks()
|
|
{
|
|
// Step: Wait until getting geolocation has completed.
|
|
InvokeCurrentStep(Step.WaitStopPolling);
|
|
Log.ForContext<T>().Information($"Waiting on stop polling to finish...");
|
|
try
|
|
{
|
|
await Task.WhenAll(new List<Task> { stopPollingTask ?? Task.CompletedTask });
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.ForContext<T>().Information("Wait for polling task to finish failed. {@exception}", exception);
|
|
await InvokeCurrentStateAsync(State.StopPollingFailed, exception.Message);
|
|
return;
|
|
}
|
|
|
|
Log.ForContext<T>().Information($"Stop polling finished.");
|
|
return;
|
|
}
|
|
|
|
// Get lock infos
|
|
async Task GetLockInfos()
|
|
{
|
|
Log.ForContext<T>().Debug($"Starting step {Step.GetLockInfos}...");
|
|
InvokeCurrentStep(Step.GetLockInfos);
|
|
// get current charging level
|
|
try
|
|
{
|
|
bike.LockInfo.BatteryPercentage = await lockService[bike.LockInfo.Id].GetBatteryPercentageAsync();
|
|
Log.ForContext<T>().Information("Lock infos of bike {bikeId} read successfully.", bike.Id);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.ForContext<T>().Information("Lock infos could not be read.", bike.Id);
|
|
if (exception is OutOfReachException)
|
|
{
|
|
Log.ForContext<T>().Debug("Lock is out of reach");
|
|
}
|
|
else
|
|
{
|
|
Log.ForContext<T>().Debug("{@exception}", exception);
|
|
}
|
|
}
|
|
// get version infos.
|
|
var versionTdo = lockService[bike.LockInfo.Id].VersionInfo;
|
|
if (versionTdo != null)
|
|
{
|
|
bike.LockInfo.VersionInfo = new VersionInfo.Builder
|
|
{
|
|
FirmwareVersion = versionTdo.FirmwareVersion,
|
|
HardwareVersion = versionTdo.HardwareVersion,
|
|
LockVersion = versionTdo.LockVersion,
|
|
}.Build();
|
|
}
|
|
}
|
|
|
|
// Updates locking state
|
|
async Task UpdateLockingState()
|
|
{
|
|
// Step: Update backend.
|
|
InvokeCurrentStep(Step.UpdateLockingState);
|
|
try
|
|
{
|
|
await connectorFactory(true).Command.UpdateLockingStateAsync(bike);
|
|
Log.ForContext<T>().Information("Backend updated for bike {bikeId} successfully.", bike.Id);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.ForContext<ReservedOpen>().Information("Updating backend for bike {bikeId} failed.", bike.Id);
|
|
//BikesViewModel.RentalProcess.Result = CurrentStepStatus.Failed;
|
|
if (exception is WebConnectFailureException)
|
|
{
|
|
// Copri server is not reachable.
|
|
Log.ForContext<T>().Debug("Copri server not reachable.");
|
|
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
|
return;
|
|
}
|
|
else if (exception is ResponseException copriException)
|
|
{
|
|
// Copri server is not reachable.
|
|
Log.ForContext<T>().Debug("Message: {Message} Details: {Details}", copriException.Message, copriException.Response);
|
|
await InvokeCurrentStateAsync(State.ResponseIsInvalid, exception.Message);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
Log.ForContext<T>().Error("User locked bike {bike} in order to pause ride but updating failed. {@l_oException}", bike.Id, exception);
|
|
await InvokeCurrentStateAsync(State.BackendUpdateFailed, exception.Message);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
//// Start Action
|
|
|
|
//// Step: Open lock.
|
|
Log.ForContext<T>().Information($"Starting step {Step.OpeningLock}...");
|
|
InvokeCurrentStep(Step.OpeningLock);
|
|
LockitLockingState? lockingState;
|
|
try
|
|
{
|
|
lockingState = await lockService[bike.LockInfo.Id].OpenAsync();
|
|
Log.ForContext<T>().Information("Lock of bike {bikeId} opened successfully.", bike.Id);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be opened.", bike.Id);
|
|
if (exception is OutOfReachException)
|
|
{
|
|
Log.ForContext<T>().Debug("Lock is out of reach");
|
|
await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message);
|
|
}
|
|
else if (exception is CouldntOpenBoldStatusIsUnknownException)
|
|
{
|
|
Log.ForContext<T>().Debug("Lock status is unknown.");
|
|
await InvokeCurrentStateAsync(State.CouldntOpenBoldStatusIsUnknownError, exception.Message);
|
|
}
|
|
else if (exception is CouldntOpenBoldIsBlockedException)
|
|
{
|
|
Log.ForContext<T>().Debug("Bold is blocked.}");
|
|
await InvokeCurrentStateAsync(State.CouldntOpenBoldIsBlockedError, exception.Message);
|
|
}
|
|
else if (exception is CouldntOpenInconsistentStateExecption inconsistentState
|
|
&& inconsistentState.State == LockingState.Closed)
|
|
{
|
|
Log.ForContext<T>().Debug("Lock reports that it is still closed.}");
|
|
await InvokeCurrentStateAsync(State.CouldntOpenInconsistentStateError, exception.Message);
|
|
}
|
|
else
|
|
{
|
|
Log.ForContext<T>().Debug("{@exception}", exception);
|
|
await InvokeCurrentStateAsync(State.GeneralOpenError, exception.Message);
|
|
}
|
|
|
|
// Update current state from exception
|
|
bike.LockInfo.State = exception is StateAwareException stateAwareException
|
|
? stateAwareException.State
|
|
: LockingState.UnknownDisconnected;
|
|
|
|
if (!isConnectedDelegate())
|
|
{
|
|
// Lock state can not be updated because there is no connected.
|
|
throw;
|
|
}
|
|
|
|
if (exception is OutOfReachException)
|
|
{
|
|
// Locking state can not be updated because lock is not connected.
|
|
throw;
|
|
}
|
|
|
|
//// Step: Get Lock Battery and Version info
|
|
await GetLockInfos();
|
|
|
|
//// Step: Wait for stop polling finished
|
|
await WaitForPendingTasks();
|
|
|
|
//// Step: Update backend.
|
|
//Do this even if current lock state is closed (lock state must not necessarily be closed before try to close, i.e. something undefined between open and opened).
|
|
await UpdateLockingState();
|
|
|
|
throw;
|
|
}
|
|
|
|
bike.LockInfo.State = lockingState?.GetLockingState() ?? LockingState.UnknownDisconnected;
|
|
|
|
if (!isConnectedDelegate())
|
|
{
|
|
return;
|
|
}
|
|
|
|
//// Step: Get Lock Battery and Version info
|
|
await GetLockInfos();
|
|
|
|
//// Step: Wait for stop polling finished
|
|
await WaitForPendingTasks();
|
|
|
|
//// Step: Update backend.
|
|
await UpdateLockingState();
|
|
}
|
|
}
|
|
}
|