using Serilog; using System; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using TINK.Model; using TINK.Model.Bikes.Bike.CopriLock; using TINK.Model.Connector; using TINK.Model.Device; using TINK.Model.Services.CopriApi; using TINK.Repository; using TINK.Repository.Response; namespace TINK.Services.CopriApi { public static class Polling { /// Timeout for open/ close operations. private const int OPEN_CLOSE_TIMEOUT_MS = 50000; /// /// Returns a bike and closes the lock. /// /// Instance to communicate with backend. /// Bike to open. public static async Task OpenAync( this ICopriServerBase corpiServer, IBikeInfoMutable bike) { if (!(corpiServer is ICachedCopriServer cachedServer)) throw new ArgumentNullException(nameof(corpiServer)); // Send command to close lock await corpiServer.UpdateLockingStateAsync( bike.Id, Repository.Request.lock_state.unlocking, bike.OperatorUri); var lockingState = await cachedServer.GetLockStateAsync(bike.Id); var watch = new Stopwatch(); watch.Start(); while (lockingState != LockingState.Open && lockingState != LockingState.UnknownDisconnected && watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS)) { // Delay a litte to reduce load on backend. await Task.Delay(3000); lockingState = await cachedServer.GetLockStateAsync(bike.Id); Log.Information($"Current lock state is {lockingState}."); } // Update locking state. bike.LockInfo.State = lockingState; } /// /// Books a bike and opens the lock. /// /// Instance to communicate with backend. /// Bike to book and open. /// Mail address of user which books bike. public static async Task BookAndOpenAync( this ICopriServerBase corpiServer, IBikeInfoMutable bike, string mailAddress) { if (bike == null) { throw new ArgumentNullException(nameof(bike), "Can not book bike and open lock. No bike object available."); } if (!(corpiServer is ICachedCopriServer cachedServer)) throw new ArgumentNullException(nameof(corpiServer)); // Send command to open lock var response = (await corpiServer.BookAndStartOpeningAsync(bike.Id, bike.OperatorUri)).GetIsBookingResponseOk(bike.Id); // Upate booking state bike.Load( response, mailAddress, Model.Bikes.Bike.BC.NotifyPropertyChangedLevel.None); // Upated locking state. var lockingState = await cachedServer.GetLockStateAsync(bike.Id); var watch = new Stopwatch(); watch.Start(); while (lockingState!= LockingState.Open && lockingState != LockingState.UnknownDisconnected && watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS)) { // Delay a litte to reduce load on backend. await Task.Delay(3000); lockingState = await cachedServer.GetLockStateAsync(bike.Id); Log.Information($"Current lock state is {lockingState}."); } // Update locking state. bike.LockInfo.State = lockingState; } /// /// Returns a bike and closes the lock. /// /// Instance to communicate with backend. /// Bike to close. public static async Task CloseAync( this ICopriServerBase corpiServer, IBikeInfoMutable bike) { if (!(corpiServer is ICachedCopriServer cachedServer)) throw new ArgumentNullException(nameof(corpiServer)); // Send command to close lock await corpiServer.UpdateLockingStateAsync( bike.Id, Repository.Request.lock_state.locking, bike.OperatorUri); var lockingState = await cachedServer.GetLockStateAsync(bike.Id); var watch = new Stopwatch(); watch.Start(); while (lockingState != LockingState.Closed && lockingState != LockingState.UnknownDisconnected && watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS)) { // Delay a litte to reduce load on backend. await Task.Delay(3000); lockingState = await cachedServer.GetLockStateAsync(bike.Id); Log.Information($"Current lock state is {lockingState}."); } // Update locking state. bike.LockInfo.State = lockingState; } /// /// Returns a bike and closes the lock. /// /// Instance to communicate with backend. /// Smart device on which app runs on. /// Mail address of user which books bike. public static async Task ReturnAndCloseAync( this ICopriServerBase corpiServer, ISmartDevice smartDevice, IBikeInfoMutable bike) { if (!(corpiServer is ICachedCopriServer cachedServer)) throw new ArgumentNullException(nameof(corpiServer)); // Send command to open lock DoReturnResponse response = await corpiServer.ReturnAndStartClosingAsync(bike.Id, smartDevice, bike.OperatorUri); // Upate booking state bike.Load(Model.Bikes.Bike.BC.NotifyPropertyChangedLevel.None); var lockingState = await cachedServer.GetLockStateAsync(bike.Id); var watch = new Stopwatch(); watch.Start(); while (lockingState != LockingState.Closed && lockingState != LockingState.UnknownDisconnected && watch.Elapsed < TimeSpan.FromMilliseconds(OPEN_CLOSE_TIMEOUT_MS)) { // Delay a litte to reduce load on backend. await Task.Delay(3000); lockingState = await cachedServer.GetLockStateAsync(bike.Id); Log.Information($"Current lock state is {lockingState}."); } // Update locking state. bike.LockInfo.State = lockingState; return response?.Create() ?? new BookingFinishedModel(); } /// /// Queries the locking state from copri. /// /// Service to use. /// Bike id to query lock state for. /// Locking state private static async Task GetLockStateAsync( this ICachedCopriServer corpiServer, string bikeId) { var bike = (await corpiServer.GetBikesOccupied(false))?.Response.bikes_occupied?.Values?.FirstOrDefault(x => x.bike == bikeId); if (bike == null) return LockingState.UnknownDisconnected; return bike.GetCopriLockingState(); } } }