Version 3.0.381

This commit is contained in:
Anja 2024-04-09 12:53:23 +02:00
parent f963c0a219
commit 3a363acf3a
1525 changed files with 60589 additions and 125098 deletions

View file

@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Serilog;
using ShareeBike.Model.Connector;
using ShareeBike.Repository.Exception;
using ShareeBike.Repository.Request;
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 CloseCommand
{
/// <summary>
/// Possible steps of closing a lock.
/// </summary>
public enum Step
{
StartStopingPolling,
StartingQueryingLocation,
ClosingLock,
WaitStopPollingQueryLocation,
/// <summary>
/// Notifies back end about modified lock state.
/// </summary>
UpdateLockingState,
QueryLocationTerminated
}
/// <summary>
/// Possible states of closing a lock.
/// </summary>
public enum State
{
OutOfReachError,
CouldntCloseMovingError,
CouldntCloseBoltBlockedError,
GeneralCloseError,
StartGeolocationException,
WaitGeolocationException,
WebConnectFailed,
ResponseIsInvalid,
BackendUpdateFailed
}
/// <summary>
/// Interface to notify view model about steps/ state changes of closing process.
/// </summary>
public interface ICloseCommandListener
{
/// <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>
/// Closes the lock and updates copri.
/// </summary>
/// <param name="listener">Interface to notify view model about steps/ state changes of closing process.</param>
/// <param name="stopPolling">Task which stops polling.</param>
public static async Task InvokeAsync<T>(
IBikeInfoMutable bike,
IGeolocationService geolocation,
ILocksService lockService,
Func<bool> isConnectedDelegate,
Func<bool, IConnector> connectorFactory,
ICloseCommandListener 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 for geolocation and polling task to stop (on finished or on canceled).
async Task<IGeolocation> WaitForPendingTasks(Task<IGeolocation> locationTask)
{
// Step: Wait until getting geolocation has completed.
InvokeCurrentStep(Step.WaitStopPollingQueryLocation);
Log.ForContext<T>().Information($"Waiting on steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} to finish...");
try
{
await Task.WhenAll(new List<Task> { locationTask, stopPollingTask ?? Task.CompletedTask });
}
catch (Exception exception)
{
// No location information available.
Log.ForContext<T>().Information("Canceling query location/ wait for polling task to finish failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.WaitGeolocationException, exception.Message);
InvokeCurrentStep(Step.QueryLocationTerminated);
return null;
}
Log.ForContext<T>().Information($"Steps {Step.StartingQueryingLocation} and {Step.StartStopingPolling} finished.");
InvokeCurrentStep(Step.QueryLocationTerminated);
return locationTask.Result;
}
// Updates locking state
async Task UpdateLockingState(IGeolocation location, DateTime timeStamp)
{
// Step: Update backend.
InvokeCurrentStep(Step.UpdateLockingState);
try
{
await connectorFactory(true).Command.UpdateLockingStateAsync(
bike,
location != null
? new LocationDto.Builder
{
Latitude = location.Latitude,
Longitude = location.Longitude,
Accuracy = location.Accuracy ?? double.NaN,
Age = timeStamp.Subtract(location.Timestamp.DateTime),
}.Build()
: null);
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: Start query geolocation data.
Log.ForContext<T>().Debug($"Starting step {Step.StartingQueryingLocation}...");
InvokeCurrentStep(Step.StartingQueryingLocation);
var ctsLocation = new CancellationTokenSource();
Task<IGeolocation> currentLocationTask = Task.FromResult<IGeolocation>(null);
var timeStampNow = DateTime.Now;
try
{
currentLocationTask = geolocation.GetAsync(ctsLocation.Token, timeStampNow);
Log.ForContext<T>().Information("Starting query location successful.");
}
catch (Exception exception)
{
// No location information available.
Log.ForContext<T>().Information("Starting query location failed. {@exception}", exception);
await InvokeCurrentStateAsync(State.StartGeolocationException, exception.Message);
}
//// Step: Close lock.
IGeolocation currentLocation;
Log.ForContext<T>().Information($"Starting step {Step.ClosingLock}...");
InvokeCurrentStep(Step.ClosingLock);
LockitLockingState? lockingState;
try
{
lockingState = await lockService[bike.LockInfo.Id].CloseAsync();
Log.ForContext<T>().Information("Lock of bike {bikeId} closed successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock of bike {bikeId} can not be closed.", 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 CouldntCloseMovingException)
{
Log.ForContext<T>().Debug("Lock is moving.");
await InvokeCurrentStateAsync(State.CouldntCloseMovingError, exception.Message);
}
else if (exception is CouldntCloseBoltBlockedException)
{
Log.ForContext<T>().Debug("Bold is blocked.}");
await InvokeCurrentStateAsync(State.CouldntCloseBoltBlockedError, exception.Message);
}
else
{
Log.ForContext<T>().Debug("{@exception}", exception);
await InvokeCurrentStateAsync(State.GeneralCloseError, exception.Message);
}
// Signal cts to cancel getting geolocation.
ctsLocation.Cancel();
// Wait until getting geolocation and stop polling has completed.
currentLocation = await WaitForPendingTasks(currentLocationTask);
// 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;
}
// Update backend.
// Do this even if current lock state is open (lock state must not necessarily be open before try to open, i.e. something undefined between open and closed).
await UpdateLockingState(currentLocation, timeStampNow);
throw;
}
//// Step: Wait until getting geolocation and stop polling has completed.
currentLocation = await WaitForPendingTasks(currentLocationTask);
bike.LockInfo.State = lockingState?.GetLockingState() ?? LockingState.UnknownDisconnected;
// Keep geolocation where closing action occurred.
bike.LockInfo.Location = currentLocation;
if (!isConnectedDelegate())
{
return;
}
//// Step: Update backend.
await UpdateLockingState(currentLocation, timeStampNow);
}
}
}

View file

@ -0,0 +1,183 @@
using System;
using System.Threading.Tasks;
using Serilog;
using ShareeBike.MultilingualResources;
using ShareeBike.Services.BluetoothLock;
using ShareeBike.Services.BluetoothLock.Exception;
using ShareeBike.Services.BluetoothLock.Tdo;
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
using ShareeBike.ViewModel.Bikes;
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
public static class ConnectAndGetStateCommand
{
/// <summary>
/// Possible steps of connecting or disconnecting a lock.
/// </summary>
public enum Step
{
ConnectLock,
GetLockingState,
}
/// <summary>
/// Possible steps of connecting or disconnecting a lock.
/// </summary>
public enum State
{
BluetoothOff,
NoLocationPermission,
LocationServicesOff,
OutOfReachError,
GeneralConnectLockError,
}
/// <summary>
/// Interface to notify view model about steps/ state changes of connecting or disconnecting lock process.
/// </summary>
public interface IConnectAndGetStateCommandListener
{
/// <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>
/// Connect or disconnect lock.
/// </summary>
/// <param name="listener"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task InvokeAsync<T>(
IBikeInfoMutable bike,
ILocksService lockService,
IConnectAndGetStateCommandListener listener = null)
{
// 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);
}
}
// Connect lock
InvokeCurrentStep(Step.ConnectLock);
LockInfoTdo result = null;
var continueConnect = true;
var retryCount = 1;
while (continueConnect && result == null)
{
try
{
result = await lockService.ConnectAsync(
new LockInfoAuthTdo.Builder { Id = bike.LockInfo.Id, Guid = bike.LockInfo.Guid, K_seed = bike.LockInfo.Seed, K_u = bike.LockInfo.UserKey }.Build(),
lockService.TimeOut.GetSingleConnect(retryCount));
Log.ForContext<T>().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", bike.Id, bike.LockInfo.State);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Connection to lock of bike {bikeId} failed. ", bike.Id);
if (exception is ConnectBluetoothNotOnException)
{
continueConnect = false;
Log.ForContext<T>().Error("Bluetooth not on.");
await InvokeCurrentStateAsync(State.BluetoothOff, exception.Message);
}
else if (exception is ConnectLocationPermissionMissingException)
{
continueConnect = false;
Log.ForContext<T>().Error("Location permission missing.");
await InvokeCurrentStateAsync(State.NoLocationPermission, exception.Message);
}
else if (exception is ConnectLocationOffException)
{
continueConnect = false;
Log.ForContext<T>().Error("Location services not on.");
await InvokeCurrentStateAsync(State.LocationServicesOff, exception.Message);
}
else if (exception is OutOfReachException)
{
continueConnect = false;
Log.ForContext<T>().Error("Lock is out of reach.");
await InvokeCurrentStateAsync(State.OutOfReachError, exception.Message);
}
else
{
Log.ForContext<T>().Error("{@exception}", exception);
string message;
if (retryCount < 2)
{
message = AppResources.ErrorConnectLock;
}
else if (retryCount < 3)
{
message = AppResources.ErrorConnectLockEscalationLevel1;
}
else
{
message = AppResources.ErrorConnectLockEscalationLevel2;
continueConnect = false;
}
await InvokeCurrentStateAsync(State.GeneralConnectLockError, message);
}
if (continueConnect)
{
retryCount++;
continue;
}
throw;
}
}
// Get locking state
InvokeCurrentStep(Step.GetLockingState);
bike.LockInfo.State = result?.State?.GetLockingState() ?? LockingState.UnknownDisconnected;
if (bike.LockInfo.State == LockingState.UnknownDisconnected)
{
// Do not display any messages here, because search is implicit.
Log.ForContext<T>().Error("Lock is still not connected.");
}
bike.LockInfo.Guid = result?.Guid ?? new Guid();
}
}
}

View file

@ -0,0 +1,106 @@
using System;
using System.Threading.Tasks;
using Serilog;
using ShareeBike.Services.BluetoothLock;
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command
{
public static class DisconnectCommand
{
/// <summary>
/// Possible steps of connecting or disconnecting a lock.
/// </summary>
public enum Step
{
DisconnectLock,
}
/// <summary>
/// Possible steps of connecting or disconnecting a lock.
/// </summary>
public enum State
{
GeneralDisconnectError,
}
/// <summary>
/// Interface to notify view model about steps/ state changes of connecting or disconnecting lock process.
/// </summary>
public interface IDisconnectCommandListener
{
/// <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>
/// Connect or disconnect lock.
/// </summary>
/// <param name="listener"></param>
/// <returns></returns>
/// <exception cref="Exception"></exception>
public static async Task InvokeAsync<T>(
IBikeInfoMutable bike,
ILocksService lockService,
IDisconnectCommandListener listener = null)
{
// 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);
}
}
// Disconnect lock
InvokeCurrentStep(Step.DisconnectLock);
if (bike.LockInfo.State != LockingState.UnknownDisconnected)
{
try
{
bike.LockInfo.State = await lockService.DisconnectAsync(bike.LockInfo.Id, bike.LockInfo.Guid);
Log.ForContext<T>().Information("Lock from bike {bikeId} disconnected successfully.", bike.Id);
}
catch (Exception exception)
{
Log.ForContext<T>().Information("Lock from bike {bikeId} can not be disconnected. {@exception}", bike.Id, exception);
await InvokeCurrentStateAsync(State.GeneralDisconnectError, exception.Message);
}
}
}
}
}

View file

@ -0,0 +1,291 @@
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();
}
}
}