using System; using System.Threading.Tasks; using Serilog; using TINK.Model.Bikes.BikeInfoNS.BluetoothLock; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.User; using TINK.MultilingualResources; using TINK.Repository.Exception; using TINK.Services.BluetoothLock; using TINK.Services.BluetoothLock.Exception; using TINK.Services.BluetoothLock.Tdo; using TINK.Services.CopriApi.Exception; using TINK.Services.Geolocation; using TINK.View; namespace TINK.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler { public class BookedDisconnected : Base, IRequestHandler { /// Provides info about the smart device (phone, tablet, ...) /// View model to be used for progress report and unlocking/ locking view. public BookedDisconnected( IBikeInfoMutable selectedBike, Func isConnectedDelegate, Func connectorFactory, IGeolocationService geolocation, ILocksService lockService, Func viewUpdateManager, ISmartDevice smartDevice, IViewService viewService, IBikesViewModel bikesViewModel, IUser activeUser) : base( selectedBike, nameof(BookedDisconnected), false, isConnectedDelegate, connectorFactory, geolocation, lockService, viewUpdateManager, smartDevice, viewService, bikesViewModel, activeUser) { LockitButtonText = AppResources.ActionSearchLock; // Search Lock IsLockitButtonVisible = true; // show button "Search Lock" } public async Task HandleRequestOption1() => await UnsupportedRequest(); /// Scan for lock. /// public async Task HandleRequestOption2() => await ConnectLock(); /// Request is not supported, button should be disabled. /// public async Task UnsupportedRequest() { Log.ForContext().Error("Click of unsupported button click detected."); return await Task.FromResult(this); } /// Scan for lock. /// public async Task ConnectLock() { // Lock list to avoid multiple taps while copri action is pending. BikesViewModel.IsIdle = false; Log.ForContext().Information("User request to connect to lock of bike {bikeId}.", SelectedBike.Id); // Stop polling before getting new auth-values. BikesViewModel.ActionText = AppResources.ActivityTextOneMomentPlease; await ViewUpdateManager().StopAsync(); BikesViewModel.ActionText = AppResources.ActivityTextQuerryServer; IsConnected = IsConnectedDelegate(); try { // Repeat booking to get a new seed/ k_user value. await ConnectorFactory(IsConnected).Command.CalculateAuthKeys(SelectedBike); Log.ForContext().Information("Calculation of AuthKeys successfully."); } catch (Exception exception) { Log.ForContext().Information("Calculation of AuthKeys failed."); BikesViewModel.ActionText = string.Empty; if (exception is WebConnectFailureException || exception is RequestNotCachableException) { // Copri server is not reachable. Log.ForContext().Error("Copri server not reachable."); await ViewService.DisplayAlert( AppResources.ErrorNoConnectionTitle, AppResources.ErrorNoWeb, AppResources.MessageAnswerOk); } else { Log.ForContext().Error("{@exception}", exception); await ViewService.DisplayAlert( AppResources.ErrorConnectLockTitle, exception.Message, AppResources.MessageAnswerOk); } // Restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return this; } // Reconnect to lock LockInfoTdo result = null; var continueConnect = true; var retryCount = 1; while (continueConnect && result == null) { BikesViewModel.ActionText = AppResources.ActivityTextSearchingLock; try { result = await LockService.ConnectAsync( new LockInfoAuthTdo.Builder { Id = SelectedBike.LockInfo.Id, Guid = SelectedBike.LockInfo.Guid, K_seed = SelectedBike.LockInfo.Seed, K_u = SelectedBike.LockInfo.UserKey }.Build(), LockService.TimeOut.GetSingleConnect(retryCount)); Log.ForContext().Information("Connected to lock of bike {bikeId} successfully. Value is {lockState}.", SelectedBike.Id, SelectedBike.LockInfo.State); } catch (Exception exception) { Log.ForContext().Information("Connection to lock of bike {bikeId} failed. ", SelectedBike.Id); BikesViewModel.ActionText = string.Empty; if (exception is ConnectBluetoothNotOnException) { continueConnect = false; Log.ForContext().Error("Bluetooth not on."); await ViewService.DisplayAlert( AppResources.ErrorConnectLockTitle, AppResources.ErrorLockBluetoothNotOn, AppResources.MessageAnswerOk); } else if (exception is ConnectLocationPermissionMissingException) { continueConnect = false; Log.ForContext().Error("Location permission missing."); await ViewService.DisplayAlert( AppResources.ErrorConnectLockTitle, AppResources.ErrorNoLocationPermission, AppResources.MessageAnswerOk); } else if (exception is ConnectLocationOffException) { continueConnect = false; Log.ForContext().Error("Location services not on."); await ViewService.DisplayAlert( AppResources.ErrorConnectLockTitle, AppResources.ErrorLockLocationOff, AppResources.MessageAnswerOk); } else if (exception is OutOfReachException) { continueConnect = false; Log.ForContext().Error("Lock is out of reach."); await ViewService.DisplayAlert( AppResources.ErrorConnectLockTitle, AppResources.ErrorLockOutOfReach, AppResources.MessageAnswerOk); } else { Log.ForContext().Error("{@exception}", exception); string message; if (retryCount < 2) { message = AppResources.ErrorConnectLock; } else if (retryCount < 3) { message = AppResources.ErrorConnectLockEscalationLevel1; } else { message = AppResources.ErrorConnectLockEscalationLevel2; } continueConnect = await ViewService.DisplayAdvancedAlert( AppResources.ErrorConnectLockTitle, message, string.Empty, AppResources.MessageAnswerRetry, AppResources.MessageAnswerCancel); } if (continueConnect) { retryCount++; continue; } // Quit and restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return this; } } if (!(result?.State is LockitLockingState lockingState)) { Log.ForContext().Error("Locking state of bike {bikeId} not found.", SelectedBike.Id); BikesViewModel.ActionText = string.Empty; await ViewService.DisplayAlert( AppResources.ErrorConnectLockTitle, AppResources.ErrorLockNoStatus, AppResources.MessageAnswerOk); // Restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return this; } // get current locking state var state = lockingState.GetLockingState(); SelectedBike.LockInfo.State = state; SelectedBike.LockInfo.Guid = result?.Guid ?? new Guid(); // get current lock charging level ILockService btLock = LockService[SelectedBike.LockInfo.Id]; BikesViewModel.ActionText = AppResources.ActivityTextReadingChargingLevel; try { SelectedBike.LockInfo.BatteryPercentage = await btLock.GetBatteryPercentageAsync(); Log.ForContext().Information("Battery state of lock from {bikeId} read successfully.", SelectedBike.Id); } catch (Exception exception) { Log.ForContext().Information("Battery state of lock from {bikeId} can not be read.", SelectedBike.Id); if (exception is OutOfReachException) { Log.ForContext().Debug("Lock is out of reach."); BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelOutOfReach; } else { Log.ForContext().Debug("{@exception}", exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorReadingChargingLevelGeneral; } } // get lock infos BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdatingLockingState; var versionTdo = btLock.VersionInfo; if (versionTdo != null) { SelectedBike.LockInfo.VersionInfo = new VersionInfo.Builder { FirmwareVersion = versionTdo.FirmwareVersion, HardwareVersion = versionTdo.HardwareVersion, LockVersion = versionTdo.LockVersion, }.Build(); } // update backend IsConnected = IsConnectedDelegate(); try { await ConnectorFactory(IsConnected).Command.UpdateLockingStateAsync(SelectedBike); Log.ForContext().Information("Backend updated for bike {bikeId} successfully.", SelectedBike.Id); } catch (Exception exception) { Log.ForContext().Information("Updating backend for bike {bikeId} failed.", SelectedBike.Id); if (exception is WebConnectFailureException) { // No web. Log.ForContext().Debug("Copri server not reachable, no web."); BikesViewModel.ActionText = AppResources.ActivityTextErrorNoWebUpdateingLockstate; } else if (exception is ResponseException copriException) { // Copri exception. Log.ForContext().Debug("{response}", copriException.Response); BikesViewModel.ActionText = AppResources.ActivityTextErrorStatusUpdateingLockstate; } else { Log.ForContext().Debug("{@exception}", exception); BikesViewModel.ActionText = AppResources.ActivityTextErrorConnectionUpdateingLockstate; } } // Restart polling again. BikesViewModel.ActionText = AppResources.ActivityTextStartingUpdater; await ViewUpdateManager().StartAsync(); BikesViewModel.ActionText = string.Empty; BikesViewModel.IsIdle = true; return RequestHandlerFactory.Create(SelectedBike, IsConnectedDelegate, ConnectorFactory, GeolocationService, LockService, ViewUpdateManager, SmartDevice, ViewService, BikesViewModel, ActiveUser); } } }