mirror of
https://dev.azure.com/TeilRad/sharee.bike%20App/_git/Code
synced 2025-04-21 20:46:28 +02:00
Version 3.0.381
This commit is contained in:
parent
f963c0a219
commit
3a363acf3a
1525 changed files with 60589 additions and 125098 deletions
112
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BC/BikeInfo.cs
Normal file
112
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BC/BikeInfo.cs
Normal file
|
@ -0,0 +1,112 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BC
|
||||
{
|
||||
public abstract class BikeInfo : IBikeInfo
|
||||
{
|
||||
/// <summary> Default value of demo property. </summary>
|
||||
public const bool DEFAULTVALUEISDEMO = false;
|
||||
|
||||
/// <summary> Holds the info about the bike state. </summary>
|
||||
private readonly IStateInfo _StateInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the bike object.
|
||||
/// </summary>
|
||||
public Bike Bike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the drive object.
|
||||
/// </summary>
|
||||
public DriveMutable Drive { get; }
|
||||
|
||||
/// <summary> Gets the information where the data origins from. </summary>
|
||||
public DataSource DataSource { get; }
|
||||
|
||||
/// <summary> Constructs a bike object.</summary>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
protected BikeInfo(
|
||||
IStateInfo stateInfo,
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
string stationId = null,
|
||||
Uri operatorUri = null,
|
||||
RentalDescription tariffDescription = null)
|
||||
{
|
||||
Bike = bike ?? throw new ArgumentNullException(nameof(bike));
|
||||
Drive = drive ?? throw new ArgumentNullException(nameof(drive));
|
||||
DataSource = dataSource;
|
||||
_StateInfo = stateInfo;
|
||||
|
||||
IsDemo = isDemo ?? DEFAULTVALUEISDEMO;
|
||||
Group = group ?? new List<string>();
|
||||
StationId = stationId;
|
||||
OperatorUri = operatorUri;
|
||||
TariffDescription = tariffDescription;
|
||||
}
|
||||
|
||||
public BikeInfo(BikeInfo bikeInfo) : this(
|
||||
bikeInfo != null ? bikeInfo?.State : throw new ArgumentNullException(nameof(bikeInfo)),
|
||||
bikeInfo.Bike,
|
||||
bikeInfo.Drive,
|
||||
bikeInfo.DataSource,
|
||||
bikeInfo.IsDemo,
|
||||
bikeInfo.Group,
|
||||
bikeInfo.StationId,
|
||||
bikeInfo.OperatorUri,
|
||||
bikeInfo.TariffDescription)
|
||||
{ }
|
||||
|
||||
/// <summary> True if device is demo device, false otherwise. </summary>
|
||||
public bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (ShareeBike, Citybike, ...). </summary>
|
||||
public IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
public string StationId { get; }
|
||||
|
||||
/// <summary> Holds description about the tariff. </summary>
|
||||
public RentalDescription TariffDescription { get; }
|
||||
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
public IStateInfo State
|
||||
{
|
||||
get { return _StateInfo; }
|
||||
}
|
||||
|
||||
public string Id => Bike.Id;
|
||||
|
||||
public WheelType? WheelType => Bike.WheelType;
|
||||
|
||||
public TypeOfBike? TypeOfBike => Bike.TypeOfBike;
|
||||
|
||||
/// <summary> Gets the model of the lock. </summary>
|
||||
public LockModel LockModel => Bike.LockModel;
|
||||
|
||||
public string Description => Bike.Description;
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
public Uri OperatorUri { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Converts the instance to text.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Id={Bike.Id}{(Bike.WheelType != null ? $", wheel(s)={Bike.WheelType}" : string.Empty)}{(Bike.TypeOfBike != null ? $"type={Bike.TypeOfBike}" : "")}, state={State}, location={(!string.IsNullOrEmpty(StationId) ? $"Station {StationId}" : "On the road")}, is demo={IsDemo}.";
|
||||
}
|
||||
}
|
||||
}
|
157
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BC/BikeInfoMutable.cs
Normal file
157
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BC/BikeInfoMutable.cs
Normal file
|
@ -0,0 +1,157 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Runtime.Serialization;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BC
|
||||
{
|
||||
[DataContract]
|
||||
public abstract class BikeInfoMutable : IBikeInfoMutable, INotifyPropertyChanged
|
||||
{
|
||||
/// <summary> Holds the bike. </summary>
|
||||
private readonly Bike _Bike;
|
||||
|
||||
/// <summary> Holds the drive of the bike. </summary>
|
||||
private readonly DriveMutable _Drive;
|
||||
|
||||
/// <summary> Holds the state info of the bike. </summary>
|
||||
private readonly StateInfoMutable _StateInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object.
|
||||
/// </summary>
|
||||
/// <param name="isDemo">True if device is demo device, false otherwise.</param>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remaining time on demand for state of type reserved.</param>
|
||||
/// <param name="stationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="stateInfo">Bike state info.</param>
|
||||
protected BikeInfoMutable(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
bool isDemo = BikeInfo.DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
string stationId = null,
|
||||
string stationName = null,
|
||||
Uri operatorUri = null,
|
||||
IRentalDescription tariffDescription = null,
|
||||
Func<DateTime> dateTimeProvider = null,
|
||||
IStateInfo stateInfo = null)
|
||||
{
|
||||
IsDemo = isDemo;
|
||||
Group = group;
|
||||
_Bike = bike;
|
||||
_Drive = drive;
|
||||
DataSource = dataSource;
|
||||
_StateInfo = new StateInfoMutable(dateTimeProvider, stateInfo);
|
||||
_StateInfo.PropertyChanged += (sender, eventargs) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(eventargs.PropertyName));
|
||||
StationId = stationId;
|
||||
StationName = stationName;
|
||||
OperatorUri = operatorUri;
|
||||
TariffDescription = tariffDescription;
|
||||
}
|
||||
|
||||
/// <summary> Id of station a which bike is located, null otherwise.</summary>
|
||||
[DataMember]
|
||||
public string StationId { get; private set; }
|
||||
|
||||
/// <summary> Name of station a which bike is located, null otherwise. </summary>
|
||||
[DataMember]
|
||||
public string StationName { get; }
|
||||
|
||||
/// <summary> Holds description about the tariff. </summary>
|
||||
[DataMember]
|
||||
public IRentalDescription TariffDescription { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
[DataMember]
|
||||
public StateInfoMutable State
|
||||
{
|
||||
get { return _StateInfo; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
public Uri OperatorUri { get; }
|
||||
|
||||
/// <summary> Unused member. </summary>
|
||||
IStateInfoMutable IBikeInfoMutable.State => _StateInfo;
|
||||
|
||||
public string Id => _Bike.Id;
|
||||
|
||||
public bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (ShareeBike, Citybike, ...). </summary>
|
||||
public IEnumerable<string> Group { get; }
|
||||
|
||||
public WheelType? WheelType => _Bike.WheelType;
|
||||
|
||||
public TypeOfBike? TypeOfBike => _Bike.TypeOfBike;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
|
||||
/// </summary>
|
||||
public AaRideType? AaRideType => _Bike.AaRideType;
|
||||
|
||||
|
||||
public LockModel LockModel => _Bike.LockModel;
|
||||
|
||||
public string Description => _Bike.Description;
|
||||
|
||||
public DriveMutable Drive => _Drive;
|
||||
|
||||
/// <summary>
|
||||
/// Fired whenever property of bike changes.
|
||||
/// </summary>
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
private DataSource _DataSource = DataSource.Copri;
|
||||
|
||||
/// <summary> Gets or sets the information where the data origins from. </summary>
|
||||
public DataSource DataSource
|
||||
{
|
||||
get => _DataSource;
|
||||
set
|
||||
{
|
||||
if (_DataSource == value)
|
||||
return;
|
||||
|
||||
_DataSource = value;
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(DataSource)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Loads a bike object from copri server booking_cancel (cancel reservation)/ booking_update (return bike) response.</summary>
|
||||
/// <param name="bike">Bike object to load response into.</param>
|
||||
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
|
||||
/// <param name="stationId">Id of the station if bike station changed, null otherwise.</param>
|
||||
public void Load(
|
||||
NotifyPropertyChangedLevel notifyLevel,
|
||||
string stationId = null)
|
||||
{
|
||||
State.Load(InUseStateEnum.Disposable, notifyLevel: notifyLevel);
|
||||
if (stationId == null)
|
||||
{
|
||||
// Station did not change.
|
||||
return;
|
||||
}
|
||||
|
||||
StationId = stationId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts the instance to text.
|
||||
/// </summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $", type={TypeOfBike}" : "")}, demo={IsDemo}, state={State.ToString()}, location={(!string.IsNullOrEmpty(StationId) ? $"Station {StationId}" : "On the road")}.";
|
||||
}
|
||||
}
|
||||
}
|
65
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BC/IBikeInfo.cs
Normal file
65
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BC/IBikeInfo.cs
Normal file
|
@ -0,0 +1,65 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BC
|
||||
{
|
||||
/// <summary>
|
||||
/// Allows to access bike info.
|
||||
/// </summary>
|
||||
public interface IBikeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the bike object.
|
||||
/// </summary>
|
||||
BikeNS.Bike Bike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the drive.
|
||||
/// </summary>
|
||||
DriveMutable Drive { get; }
|
||||
|
||||
/// <summary> Gets or sets the information where the data origins from. </summary>
|
||||
DataSource DataSource { get; }
|
||||
|
||||
/// <summary> True if bike is a demo bike. </summary>
|
||||
bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (ShareeBike, Citybike, ...). </summary>
|
||||
IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
string StationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
Uri OperatorUri { get; }
|
||||
|
||||
/// <summary> Holds description about the tariff. </summary>
|
||||
RentalDescription TariffDescription { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
IStateInfo State { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Origin of the data.
|
||||
/// </summary>
|
||||
public enum DataSource
|
||||
{
|
||||
/// <summary>
|
||||
/// Data source copri.
|
||||
/// </summary>
|
||||
Copri,
|
||||
/// <summary>
|
||||
/// Data source cache.
|
||||
/// </summary>
|
||||
Cache
|
||||
}
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BC
|
||||
{
|
||||
public interface IBikeInfoMutable
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the unique id of the bike;
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary> True if bike is a demo bike. </summary>
|
||||
bool IsDemo { get; }
|
||||
|
||||
/// <summary> Returns the group (ShareeBike, Citybike, ...). </summary>
|
||||
IEnumerable<string> Group { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of wheels.
|
||||
/// </summary>
|
||||
WheelType? WheelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the type of bike.
|
||||
/// </summary>
|
||||
TypeOfBike? TypeOfBike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
|
||||
/// </summary>
|
||||
AaRideType? AaRideType { get; }
|
||||
|
||||
/// <summary> Gets the model of the lock. </summary>
|
||||
LockModel LockModel { get; }
|
||||
|
||||
/// <summary> Holds the description of the bike. </summary>
|
||||
string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Station a which bike is located, null otherwise.
|
||||
/// </summary>
|
||||
string StationId { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the rent state of the bike.
|
||||
/// </summary>
|
||||
IStateInfoMutable State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Uri of the operator or null, in case of single operator setup.
|
||||
/// </summary>
|
||||
Uri OperatorUri { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Hold the drive object.
|
||||
/// </summary>
|
||||
DriveMutable Drive { get; }
|
||||
|
||||
/// <summary> Gets or sets the information where the data origins from. </summary>
|
||||
DataSource DataSource { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the rental description.
|
||||
/// </summary>
|
||||
IRentalDescription TariffDescription { get; }
|
||||
|
||||
/// <summary> Loads a bike object from copri server booking_cancel (cancel reservation)/ booking_update (return bike) response.</summary>
|
||||
/// <param name="notifyLevel">Controls whether notify property changed events are fired or not.</param>
|
||||
/// <param name="stationId">Id of the station if bike station changed, null otherwise.</param>
|
||||
void Load(
|
||||
NotifyPropertyChangedLevel notifyLevel,
|
||||
string stationId = null);
|
||||
|
||||
event PropertyChangedEventHandler PropertyChanged;
|
||||
}
|
||||
|
||||
public enum NotifyPropertyChangedLevel
|
||||
{
|
||||
/// <summary> Notify about all property changes.</summary>
|
||||
All,
|
||||
|
||||
/// <summary> Notify about no property changes.</summary>
|
||||
None
|
||||
}
|
||||
}
|
159
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BikeNS/Bike.cs
Normal file
159
SharedBusinessLogic/Model/Bikes/BikeInfoNS/BikeNS/Bike.cs
Normal file
|
@ -0,0 +1,159 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS
|
||||
{
|
||||
/// <summary> Count of wheels. </summary>
|
||||
/// <remarks> Numeric values of enum must match count of wheels</remarks>
|
||||
public enum WheelType
|
||||
{
|
||||
Mono = 1,
|
||||
Two = 2,
|
||||
Trike = 3,
|
||||
Quad = 4
|
||||
}
|
||||
|
||||
/// <summary> Type of bike. </summary>
|
||||
public enum TypeOfBike
|
||||
{
|
||||
Allround = 0,
|
||||
Cargo = 1,
|
||||
City = 2,
|
||||
}
|
||||
|
||||
/// <summary> Holds whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).</summary>
|
||||
public enum AaRideType
|
||||
{
|
||||
NoAaRide = 0,
|
||||
AaRide = 1,
|
||||
}
|
||||
|
||||
/// <summary> Holds the model of lock. </summary>
|
||||
public enum LockModel
|
||||
{
|
||||
ILockIt, // haveltec GbmH Brandenburg, Germany bluetooth lock
|
||||
BordComputer, // TeilRad BC
|
||||
Sigo, // Sigo GmbH Darmstadt, Germany bike lock
|
||||
}
|
||||
|
||||
/// <summary> Holds the type of lock. </summary>
|
||||
public enum LockType
|
||||
{
|
||||
Backend, // Backend, i.e. COPRI controls lock (open, close, ...)
|
||||
Bluethooth, // Lock is controlled.
|
||||
}
|
||||
|
||||
public class Bike : IEquatable<Bike>
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a bike.
|
||||
/// </summary>
|
||||
/// <param name="id">Unique id of bike.</param>
|
||||
public Bike(
|
||||
string id,
|
||||
LockModel lockModel,
|
||||
WheelType? wheelType = null,
|
||||
TypeOfBike? typeOfBike = null,
|
||||
AaRideType? aaRideType = null,
|
||||
string description = null)
|
||||
{
|
||||
WheelType = wheelType;
|
||||
TypeOfBike = typeOfBike;
|
||||
AaRideType = aaRideType;
|
||||
LockModel = lockModel;
|
||||
Id = id;
|
||||
Description = description;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Holds the unique id of the bike;
|
||||
/// </summary>
|
||||
public string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the count of wheels.
|
||||
/// </summary>
|
||||
public WheelType? WheelType { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the type of bike.
|
||||
/// </summary>
|
||||
public TypeOfBike? TypeOfBike { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether bike is a AA bike (bike must be always returned a the same station) or AB bike (start and end stations can be different stations).
|
||||
/// </summary>
|
||||
public AaRideType? AaRideType { get; }
|
||||
|
||||
/// <summary> Gets the model of the lock. </summary>
|
||||
public LockModel LockModel { get; private set; }
|
||||
|
||||
/// <summary> Holds the description of the bike. </summary>
|
||||
public string Description { get; }
|
||||
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
var l_oBike = obj as Bike;
|
||||
if (l_oBike == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return Equals(l_oBike);
|
||||
}
|
||||
|
||||
/// <summary> Converts the instance to text.</summary>
|
||||
public override string ToString()
|
||||
{
|
||||
return WheelType == null || TypeOfBike == null
|
||||
? $"Id={Id}{(!string.IsNullOrEmpty(Description) ? $", {Description}" : "")}"
|
||||
: $"Id={Id}{(WheelType != null ? $", wheel(s)={WheelType}" : string.Empty)}{(TypeOfBike != null ? $"type={TypeOfBike}" : "")}.";
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public bool Equals(Bike other)
|
||||
{
|
||||
return other != null &&
|
||||
Id == other.Id &&
|
||||
WheelType == other.WheelType &&
|
||||
TypeOfBike == other.TypeOfBike
|
||||
&& Description == other.Description;
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public static bool operator ==(Bike bike1, Bike bike2)
|
||||
{
|
||||
return EqualityComparer<Bike>.Default.Equals(bike1, bike2);
|
||||
}
|
||||
|
||||
/// <summary> Compares two bike object.</summary>
|
||||
/// <param name="obj">Object to compare with.</param>
|
||||
/// <returns>True if bikes are equal.</returns>
|
||||
public static bool operator !=(Bike bike1, Bike bike2)
|
||||
{
|
||||
return !(bike1 == bike2);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates hash code for bike object.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hashCode = -390870100;
|
||||
hashCode = hashCode * -1521134295 + Id.GetHashCode();
|
||||
hashCode = hashCode * -1521134295 + WheelType?.GetHashCode() ?? 0;
|
||||
hashCode = hashCode * -1521134295 + TypeOfBike?.GetHashCode() ?? 0;
|
||||
hashCode = hashCode * -1521134295 + Description?.GetHashCode() ?? 0;
|
||||
return hashCode;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS
|
||||
{
|
||||
public static class BikeExtension
|
||||
{
|
||||
public static LockType GetLockType(this LockModel model)
|
||||
{
|
||||
switch (model)
|
||||
{
|
||||
case LockModel.ILockIt:
|
||||
return LockType.Bluethooth;
|
||||
|
||||
case LockModel.Sigo:
|
||||
return LockType.Backend;
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Unsupported lock model {model} detected.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to get the locked bike location.
|
||||
/// </summary>
|
||||
public static class AuthCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
Authenticate,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
WebConnectFailed,
|
||||
GeneralAuthError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IAuthCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// Get current location.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IAuthCommandListener listener)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
//// Start Action
|
||||
//// Step: Authenticate user
|
||||
InvokeCurrentStep(Step.Authenticate);
|
||||
|
||||
try
|
||||
{
|
||||
// Repeat reservation to get a new seed/ k_user value.
|
||||
await connectorFactory(true).Command.CalculateAuthKeys(bike);
|
||||
Log.ForContext<T>().Information("Calculation of AuthKeys successfully.");
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Calculation of AuthKeys failed (Copri server not reachable).");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("Calculation of AuthKeys failed. {@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralAuthError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.ViewModel.Bikes.Bike.BluetoothLock.RequestHandler;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
public static class CancelReservationCommand
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
CancelReservation,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
WebConnectFailed,
|
||||
InvalidResponse,
|
||||
GeneralCancelReservationError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of returning bike process.
|
||||
/// </summary>
|
||||
public interface ICancelReservationCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// Get current location.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
ICancelReservationCommandListener listener)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
//// Start Action
|
||||
// Cancel Reservation
|
||||
InvokeCurrentStep(Step.CancelReservation);
|
||||
|
||||
try
|
||||
{
|
||||
await connectorFactory(true).Command.DoCancelReservation(bike);
|
||||
Log.ForContext<T>().Information("User canceled reservation of bike {bikeId} successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<ReservedDisconnected>().Information("User selected reserved bike {bikeId} but cancel reservation failed.", bike.Id);
|
||||
if (exception is InvalidAuthorizationResponseException)
|
||||
{
|
||||
// Copri response is invalid.
|
||||
Log.ForContext<T>().Error("Invalid auth. response.");
|
||||
await InvokeCurrentStateAsync(State.InvalidResponse, exception.Message);
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Copri server not reachable.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralCancelReservationError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
using ShareeBike.Repository.Request;
|
||||
using System.Threading;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.MultilingualResources;
|
||||
using Xamarin.Essentials;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
public static class EndRentalCommand
|
||||
{
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
GetLocation,
|
||||
ReturnBike,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of returning a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
GPSNotSupportedException,
|
||||
GPSNotEnabledException,
|
||||
NoGPSPermissionsException,
|
||||
GeneralQueryLocationFailed,
|
||||
|
||||
WebConnectFailed,
|
||||
NotAtStation,
|
||||
NoGPSData,
|
||||
ResponseException,
|
||||
GeneralEndRentalError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of returning bike process.
|
||||
/// </summary>
|
||||
public interface IEndRentalCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// End rental.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task<BookingFinishedModel> InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<DateTime> dateTimeProvider = null,
|
||||
Func<bool> isConnectedDelegate = null,
|
||||
Func<bool, IConnector> connectorFactory = null,
|
||||
IEndRentalCommandListener 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);
|
||||
}
|
||||
}
|
||||
|
||||
//// Start Action
|
||||
|
||||
// Get Location
|
||||
//// Step: Start query geolocation data.
|
||||
InvokeCurrentStep(Step.GetLocation);
|
||||
|
||||
// Get geolocation which was requested when closing lock.
|
||||
var closingLockLocation = bike.LockInfo.Location;
|
||||
|
||||
LocationDto endRentalLocation;
|
||||
if (closingLockLocation != null)
|
||||
{
|
||||
// Location was available when closing bike. No further actions required.
|
||||
endRentalLocation =
|
||||
new LocationDto.Builder
|
||||
{
|
||||
Latitude = closingLockLocation.Latitude,
|
||||
Longitude = closingLockLocation.Longitude,
|
||||
Accuracy = closingLockLocation.Accuracy ?? double.NaN,
|
||||
Age = bike.LockInfo.LastLockingStateChange is DateTime lastLockState1 ? lastLockState1.Subtract(closingLockLocation.Timestamp.DateTime) : TimeSpan.MaxValue,
|
||||
}.Build();
|
||||
}
|
||||
else
|
||||
{
|
||||
IGeolocation newLockLocation = null;
|
||||
|
||||
// Check if bike is around
|
||||
var deviceState = lockService[bike.LockInfo.Id].GetDeviceState();
|
||||
|
||||
// Geolocation can not be queried because bike is not around.
|
||||
if (deviceState != DeviceState.Connected)
|
||||
{
|
||||
Log.ForContext<T>().Information("There is no geolocation information available since lock of bike {bikeId} is not connected", bike.Id);
|
||||
// no GPS data exception.
|
||||
//NoGPSDataException.IsNoGPSData("There is no geolocation information available since lock is not connected", out NoGPSDataException exception);
|
||||
await InvokeCurrentStateAsync(State.NoGPSData, "There is no geolocation information available since lock is not connected");
|
||||
|
||||
return null; // return empty BookingFinishedModel
|
||||
}
|
||||
else
|
||||
{
|
||||
// Bike is around -> Query geolocation.
|
||||
var ctsLocation = new CancellationTokenSource();
|
||||
try
|
||||
{
|
||||
newLockLocation = await geolocation.GetAsync(ctsLocation.Token, DateTime.Now);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// No location information available.
|
||||
Log.ForContext<T>().Information("Geolocation query failed.");
|
||||
if (exception is FeatureNotSupportedException)
|
||||
{
|
||||
Log.ForContext<T>().Error("Location service are not supported on device.");
|
||||
await InvokeCurrentStateAsync(State.GPSNotSupportedException, exception.Message);
|
||||
}
|
||||
if (exception is FeatureNotEnabledException)
|
||||
{
|
||||
Log.ForContext<T>().Error("Location service are off.");
|
||||
await InvokeCurrentStateAsync(State.GPSNotEnabledException, exception.Message);
|
||||
}
|
||||
if (exception is PermissionException)
|
||||
{
|
||||
Log.ForContext<T>().Error("No location service permissions granted.");
|
||||
await InvokeCurrentStateAsync(State.NoGPSPermissionsException, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Information("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralQueryLocationFailed, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
// Update last lock state time
|
||||
// save geolocation data for sending to backend
|
||||
endRentalLocation = newLockLocation != null
|
||||
? new LocationDto.Builder
|
||||
{
|
||||
Latitude = newLockLocation.Latitude,
|
||||
Longitude = newLockLocation.Longitude,
|
||||
Accuracy = newLockLocation.Accuracy ?? double.NaN,
|
||||
Age = (dateTimeProvider != null ? dateTimeProvider() : DateTime.Now).Subtract(newLockLocation.Timestamp.DateTime),
|
||||
}.Build()
|
||||
: null;
|
||||
}
|
||||
|
||||
// Return bike
|
||||
InvokeCurrentStep(Step.ReturnBike);
|
||||
BookingFinishedModel bookingFinished;
|
||||
try
|
||||
{
|
||||
bookingFinished = await connectorFactory(true).Command.DoReturn(
|
||||
bike,
|
||||
endRentalLocation);
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} was terminated successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Rental of bike {bikeId} can not be terminated.", bike.Id);
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// No web.
|
||||
Log.ForContext<T>().Error("Copri server not reachable. No web.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else if (exception is NotAtStationException notAtStationException)
|
||||
{
|
||||
// not at station.
|
||||
Log.ForContext<T>().Error("COPRI returned out of GEO fencing error. Position send to COPRI {position}.", endRentalLocation);
|
||||
await InvokeCurrentStateAsync(State.NotAtStation, string.Format(AppResources.ErrorEndRentalNotAtStation, notAtStationException.StationNr, notAtStationException.Distance));
|
||||
|
||||
// reset location -> at next try query new location data
|
||||
bike.LockInfo.Location = null;
|
||||
}
|
||||
else if (exception is NoGPSDataException)
|
||||
{
|
||||
// no GPS data.
|
||||
Log.ForContext<T>().Error("COPRI returned a no-GPS-data error.");
|
||||
await InvokeCurrentStateAsync(State.NoGPSData, exception.Message);
|
||||
|
||||
// reset location -> at next try query new location data
|
||||
bike.LockInfo.Location = null;
|
||||
}
|
||||
else if (exception is ResponseException copriException)
|
||||
{
|
||||
// COPRI exception.
|
||||
Log.ForContext<T>().Error("COPRI returned an error. {response}", copriException.Response);
|
||||
await InvokeCurrentStateAsync(State.ResponseException, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralEndRentalError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
return bookingFinished;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
public static class StartRentalCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
RentBike,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
WebConnectFailed,
|
||||
GeneralStartRentalError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IStartRentalCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// Get current location.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IStartRentalCommandListener listener)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
//// Start Action
|
||||
//// Step: Book bike
|
||||
InvokeCurrentStep(Step.RentBike);
|
||||
try
|
||||
{
|
||||
if (bike.LockInfo.State != LockingState.Open)
|
||||
{
|
||||
await connectorFactory(true).Command.DoBookAsync(bike, LockingAction.Open);
|
||||
}
|
||||
else
|
||||
{
|
||||
await connectorFactory(true).Command.DoBookAsync(bike);
|
||||
}
|
||||
Log.ForContext<T>().Information("User booked bike {bikeId} successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Booking of bike {bikeId} failed.", bike.Id);
|
||||
if (exception is WebConnectFailureException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Copri server not reachable.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralStartRentalError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Serilog;
|
||||
using ShareeBike.Repository.Exception;
|
||||
using ShareeBike.Services.CopriApi.Exception;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides functionality to get the locked bike location.
|
||||
/// </summary>
|
||||
public static class StartReservationCommand
|
||||
{
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum Step
|
||||
{
|
||||
ReserveBike,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible steps of requesting a bike.
|
||||
/// </summary>
|
||||
public enum State
|
||||
{
|
||||
TooManyBikesError,
|
||||
WebConnectFailed,
|
||||
GeneralStartReservationError,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Interface to notify view model about steps/ state changes of closing process.
|
||||
/// </summary>
|
||||
public interface IStartReservationCommandListener
|
||||
{
|
||||
/// <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>
|
||||
/// Get current location.
|
||||
/// </summary>
|
||||
/// <param name="listener"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="Exception"></exception>
|
||||
public static async Task InvokeAsync<T>(
|
||||
IBikeInfoMutable bike,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
IStartReservationCommandListener listener)
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
//// Start Action
|
||||
//// Step: Reserve Bike
|
||||
InvokeCurrentStep(Step.ReserveBike);
|
||||
|
||||
try
|
||||
{
|
||||
await connectorFactory(true).Command.DoReserve(bike);
|
||||
Log.ForContext<T>().Information("User reserved bike {bikeId} successfully.", bike.Id);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext<T>().Information("Request to reserve bike {bikeId} declined.", bike.Id);
|
||||
if (exception is BookingDeclinedException)
|
||||
{
|
||||
// Too many bikes booked.
|
||||
Log.ForContext<T>().Error("Maximum count of bikes {exception.MaxBikesCount} already requested/ booked.", (exception as BookingDeclinedException).MaxBikesCount);
|
||||
await InvokeCurrentStateAsync(State.TooManyBikesError, (exception as BookingDeclinedException).MaxBikesCount.ToString());
|
||||
}
|
||||
else if (exception is WebConnectFailureException
|
||||
|| exception is RequestNotCachableException)
|
||||
{
|
||||
// Copri server is not reachable.
|
||||
Log.ForContext<T>().Error("Copri server not reachable.");
|
||||
await InvokeCurrentStateAsync(State.WebConnectFailed, exception.Message);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.ForContext<T>().Error("{@exception}", exception);
|
||||
await InvokeCurrentStateAsync(State.GeneralStartReservationError, exception.Message);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,171 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BC;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public class BikeInfo : BC.BikeInfo, IBikeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a available bike.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
/// <param name="lockId">Id of the lock.</param>
|
||||
/// <param name="lockGuid">GUID specifying the lock.</param>
|
||||
/// <param name="currentStationId">Id of station where bike is located.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
public BikeInfo(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
int lockId,
|
||||
Guid lockGuid,
|
||||
string currentStationId,
|
||||
Uri operatorUri = null,
|
||||
RentalDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null) : base(
|
||||
new StateInfo(),
|
||||
bike != null
|
||||
? new Bike(
|
||||
bike.Id,
|
||||
LockModel.ILockIt /* Ensure consistent lock model value */,
|
||||
bike.WheelType,
|
||||
bike.TypeOfBike,
|
||||
bike.AaRideType,
|
||||
bike.Description)
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
drive,
|
||||
dataSource,
|
||||
isDemo,
|
||||
group,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder { Id = lockId, Guid = lockGuid }.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a requested bike.
|
||||
/// </summary>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remaining time on demand for state of type reserved.</param>
|
||||
/// <param name="lockId">Id of the lock.</param>
|
||||
/// <param name="lockGuid">GUID specifying the lock.</param>
|
||||
/// <param name="requestedAt">Date time when bike was requested</param>
|
||||
/// <param name="mailAddress">Mail address of user which requested bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="dateTimeProvider">Date time provider to calculate remaining time.</param>
|
||||
public BikeInfo(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
int lockId,
|
||||
Guid lockGuid,
|
||||
byte[] userKey,
|
||||
byte[] adminKey,
|
||||
byte[] seed,
|
||||
DateTime requestedAt,
|
||||
string mailAddress,
|
||||
string currentStationId,
|
||||
Uri operatorUri,
|
||||
RentalDescription tariffDescription,
|
||||
Func<DateTime> dateTimeProvider,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null) : base(
|
||||
new StateInfo(
|
||||
dateTimeProvider,
|
||||
requestedAt,
|
||||
tariffDescription?.MaxReservationTimeSpan ?? StateRequestedInfo.UNKNOWNMAXRESERVATIONTIMESPAN,
|
||||
mailAddress,
|
||||
"" ), // BC code
|
||||
bike != null
|
||||
? new Bike(
|
||||
bike.Id,
|
||||
LockModel.ILockIt /* Ensure consistent lock model value */,
|
||||
bike.WheelType,
|
||||
bike.TypeOfBike,
|
||||
bike.AaRideType,
|
||||
bike.Description)
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
drive,
|
||||
dataSource,
|
||||
isDemo,
|
||||
group,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder { Id = lockId, Guid = lockGuid, UserKey = userKey, AdminKey = adminKey, Seed = seed }.Build();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a booked bike.
|
||||
/// </summary>
|
||||
/// <param name="bike">Unique id of bike.</param>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
/// <param name="lockId">Id of the lock.</param>
|
||||
/// <param name="lockGuid">GUID specifying the lock.</param>
|
||||
/// <param name="bookedAt">Date time when bike was booked</param>
|
||||
/// <param name="mailAddress">Mail address of user which booked bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="wheelType"></param>
|
||||
public BikeInfo(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
int lockId,
|
||||
Guid lockGuid,
|
||||
byte[] userKey,
|
||||
byte[] adminKey,
|
||||
byte[] seed,
|
||||
DateTime bookedAt,
|
||||
string mailAddress,
|
||||
string currentStationId,
|
||||
Uri operatorUri,
|
||||
RentalDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null) : base(
|
||||
new StateInfo(
|
||||
bookedAt,
|
||||
mailAddress,
|
||||
""),
|
||||
bike != null
|
||||
? new Bike(
|
||||
bike.Id,
|
||||
LockModel.ILockIt /* Ensure consistent lock model value */,
|
||||
bike.WheelType,
|
||||
bike.TypeOfBike,
|
||||
bike.AaRideType,
|
||||
bike.Description)
|
||||
: throw new ArgumentNullException(),
|
||||
drive,
|
||||
dataSource,
|
||||
isDemo,
|
||||
group,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder { Id = lockId, Guid = lockGuid, UserKey = userKey, AdminKey = adminKey, Seed = seed }.Build();
|
||||
}
|
||||
|
||||
public BikeInfo(BC.BikeInfo bikeInfo, LockInfo lockInfo) : base(
|
||||
bikeInfo ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source bike info must not be null."))
|
||||
{
|
||||
LockInfo = lockInfo
|
||||
?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source lock object must not be null.");
|
||||
}
|
||||
|
||||
public LockInfo LockInfo { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,240 @@
|
|||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using ShareeBike.Model.Connector;
|
||||
using ShareeBike.Repository.Request;
|
||||
using ShareeBike.Services.BluetoothLock;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
using ShareeBike.ViewModel;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.ConnectAndGetStateCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.OpenCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartReservationCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.CancelReservationCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.AuthCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public class BikeInfoMutable : BC.BikeInfoMutable, IBikeInfoMutable
|
||||
{
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
private Func<bool, IConnector> ConnectorFactory { get; }
|
||||
|
||||
/// <summary> Provides geolocation information.</summary>
|
||||
private IGeolocationService GeolocationService { get; }
|
||||
|
||||
/// <summary> Provides geolocation for end rental.</summary>
|
||||
private LocationDto EndRentalLocation { get; }
|
||||
|
||||
/// <summary> Provides a connector object.</summary>
|
||||
private ILocksService LockService { get; }
|
||||
|
||||
/// <summary> Delegate to retrieve connected state. </summary>
|
||||
private Func<bool> IsConnectedDelegate { get; }
|
||||
|
||||
/// <summary>Object to manage update of view model objects from Copri.</summary>
|
||||
protected Func<IPollingUpdateTaskManager> ViewUpdateManager { get; }
|
||||
|
||||
/// <summary> Constructs a bike object from source. </summary>
|
||||
/// <param name="viewUpdateManager">Manages update of view model objects from Copri.</param>
|
||||
public BikeInfoMutable(
|
||||
IGeolocationService geolocation,
|
||||
ILocksService lockService,
|
||||
Func<bool> isConnectedDelegate,
|
||||
Func<bool, IConnector> connectorFactory,
|
||||
Func<IPollingUpdateTaskManager> viewUpdateManager,
|
||||
BikeInfo bike,
|
||||
string stationName) : base(
|
||||
bike != null
|
||||
? bike.Bike
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
bike.Drive,
|
||||
bike.DataSource,
|
||||
bike.IsDemo,
|
||||
bike.Group,
|
||||
bike.StationId,
|
||||
stationName,
|
||||
bike.OperatorUri,
|
||||
bike.TariffDescription,
|
||||
() => DateTime.Now,
|
||||
bike.State)
|
||||
{
|
||||
LockInfo = new LockInfoMutable(
|
||||
bike.LockInfo.Id,
|
||||
bike.LockInfo.Guid,
|
||||
bike.LockInfo.UserKey,
|
||||
bike.LockInfo.AdminKey,
|
||||
bike.LockInfo.Seed,
|
||||
bike.LockInfo.State);
|
||||
|
||||
GeolocationService = geolocation
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No geolocation object available.");
|
||||
|
||||
LockService = lockService
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No lock service object available.");
|
||||
|
||||
IsConnectedDelegate = isConnectedDelegate
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No is connected delegate available.");
|
||||
|
||||
ConnectorFactory = connectorFactory
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No connector available.");
|
||||
|
||||
ViewUpdateManager = viewUpdateManager
|
||||
?? throw new ArgumentException($"Can not instantiate {nameof(BikeInfoMutable)}- object. No update manger available.");
|
||||
}
|
||||
|
||||
public LockInfoMutable LockInfo { get; }
|
||||
|
||||
ILockInfoMutable IBikeInfoMutable.LockInfo => LockInfo;
|
||||
|
||||
/// <summary>
|
||||
/// Connects the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
public async Task ConnectAsync(
|
||||
IConnectAndGetStateCommandListener listener)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
LockService,
|
||||
listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Opens the lock and updates copri.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
/// <param name="stopPollingTask">Task which stops polling.</param>
|
||||
public async Task OpenLockAsync(
|
||||
IOpenCommandListener listener,
|
||||
Task stopPollingTask)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
LockService,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener,
|
||||
stopPollingTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Closes the lock and updates copri.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
/// <param name="stopPollingTask">Task which stops polling.</param>
|
||||
public async Task CloseLockAsync(
|
||||
ICloseCommandListener listener,
|
||||
Task stopPollingTask)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener,
|
||||
stopPollingTask);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
public async Task DisconnectAsync(
|
||||
IDisconnectCommandListener listener)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
LockService,
|
||||
listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reserves the bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
public async Task ReserveBikeAsync(
|
||||
IStartReservationCommandListener listener)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the reservation.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
public async Task CancelReservationAsync(
|
||||
ICancelReservationCommandListener listener)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the user.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
public async Task AuthAsync(
|
||||
IAuthCommandListener listener)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Rents the bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
public async Task RentBikeAsync(
|
||||
IStartRentalCommandListener listener)
|
||||
{
|
||||
await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
public async Task<BookingFinishedModel> ReturnBikeAsync(
|
||||
IEndRentalCommandListener listener)
|
||||
{
|
||||
var bookingFinished = await InvokeAsync<BikeInfoMutable>(
|
||||
this,
|
||||
GeolocationService,
|
||||
LockService,
|
||||
() => DateTime.Now,
|
||||
IsConnectedDelegate,
|
||||
ConnectorFactory,
|
||||
listener);
|
||||
|
||||
return bookingFinished;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Id={Id}{(TypeOfBike != null ? $";type={TypeOfBike}" : "")};state={State.ToString()};Lock id={LockInfo.Id}";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public interface IBikeInfo : BC.IBikeInfo
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
using System.Threading.Tasks;
|
||||
using ShareeBike.Repository.Request;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.ConnectAndGetStateCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.DisconnectCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.OpenCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock.Command.CloseCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartReservationCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.CancelReservationCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.AuthCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.StartRentalCommand;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.BikeNS.Command.EndRentalCommand;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public interface IBikeInfoMutable : BC.IBikeInfoMutable
|
||||
{
|
||||
ILockInfoMutable LockInfo { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Connects the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process connect notifications.</param>
|
||||
Task ConnectAsync(IConnectAndGetStateCommandListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Disconnects the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process disconnect notifications.</param>
|
||||
Task DisconnectAsync(IDisconnectCommandListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Opens the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process opening notifications.</param>
|
||||
/// <param name="stopPollingTask">Task which stops polling to be awaited before updating bike object and copri communication.</param>
|
||||
Task OpenLockAsync(IOpenCommandListener listener, Task stopPollingTask);
|
||||
|
||||
/// <summary>
|
||||
/// Closes the lock.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process closing notifications.</param>
|
||||
/// <param name="stopPollingTask">Task which stops polling to be awaited before updating bike object and copri communication.</param>
|
||||
Task CloseLockAsync(ICloseCommandListener listener, Task stopPollingTask);
|
||||
|
||||
/// <summary>
|
||||
/// Reserves the bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process reservation notifications.</param>
|
||||
Task ReserveBikeAsync(IStartReservationCommandListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Cancels the reservation.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process reservation notifications.</param>
|
||||
Task CancelReservationAsync(ICancelReservationCommandListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Authenticates the user.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process reservation notifications.</param>
|
||||
Task AuthAsync(IAuthCommandListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Rents the bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process renting notifications.</param>
|
||||
Task RentBikeAsync(IStartRentalCommandListener listener);
|
||||
|
||||
/// <summary>
|
||||
/// Returns the bike.
|
||||
/// </summary>
|
||||
/// <param name="listener">View model to process notifications.</param>
|
||||
/// <returns></returns>
|
||||
Task<BookingFinishedModel> ReturnBikeAsync(IEndRentalCommandListener listener);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
using System;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public interface ILockInfoMutable
|
||||
{
|
||||
/// <summary> Identification number of bluetooth lock.</summary>
|
||||
int Id { get; }
|
||||
|
||||
/// <summary> Gets the user key.</summary>
|
||||
byte[] UserKey { get; }
|
||||
|
||||
LockingState State { get; set; }
|
||||
|
||||
/// <summary> Holds the percentage of lock battery.</summary>
|
||||
double BatteryPercentage { get; set; }
|
||||
|
||||
/// <summary> Changes during runtime: Can be unknown when set from copri and chang to a valid value when set from lock.</summary>
|
||||
Guid Guid { get; set; }
|
||||
|
||||
byte[] Seed { get; }
|
||||
|
||||
/// <summary> Timestamp of the last locking state change.</summary>
|
||||
DateTime? LastLockingStateChange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current location of the bike, null if location is unknown.
|
||||
/// </summary>
|
||||
IGeolocation Location { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version info of the locks.
|
||||
/// </summary>
|
||||
IVersionInfo VersionInfo { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public interface IVersionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the firmware version of the lock.
|
||||
/// </summary>
|
||||
int FirmwareVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the hardware version (revision) of the lock.
|
||||
/// </summary>
|
||||
int HardwareVersion { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds lock version (2 – classic, 3 – plus, 4 – GPS).
|
||||
/// </summary>
|
||||
int LockVersion { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
using System;
|
||||
using ShareeBike.Services.Geolocation;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public class LockInfoMutable : ILockInfoMutable
|
||||
{
|
||||
/// <summary> Lock info object. </summary>
|
||||
private LockInfo LockInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Delegate to create time stamp.
|
||||
/// </summary>
|
||||
private Func<DateTime> _nowDelegate;
|
||||
|
||||
/// <summary> Constructs a bluetooth lock info object. </summary>
|
||||
/// <param name="id">Id of lock must always been known when constructing an lock info object.</param>
|
||||
/// <param name="nowDelegate">Delegate to create time stamp if null DateTime.Now is used.</param>
|
||||
public LockInfoMutable(
|
||||
int id,
|
||||
Guid guid,
|
||||
byte[] userKey,
|
||||
byte[] adminKey,
|
||||
byte[] seed,
|
||||
LockingState state,
|
||||
Func<DateTime> nowDelegate = null)
|
||||
{
|
||||
_nowDelegate = nowDelegate ?? (() => DateTime.Now);
|
||||
LockInfo = new LockInfo.Builder() { Id = id, Guid = guid, UserKey = userKey, AdminKey = adminKey, Seed = seed, State = state }.Build();
|
||||
}
|
||||
|
||||
public int Id => LockInfo.Id;
|
||||
|
||||
/// <summary> Changes during runtime: Can be unknown when set from copri and chang to a valid value when set from lock.</summary>
|
||||
public Guid Guid
|
||||
{
|
||||
get => LockInfo.Guid;
|
||||
set => LockInfo = new LockInfo.Builder(LockInfo) { Guid = value }.Build();
|
||||
}
|
||||
|
||||
public byte[] Seed => LockInfo.Seed;
|
||||
|
||||
public byte[] UserKey => LockInfo.UserKey;
|
||||
|
||||
public byte[] AdminKey => LockInfo.AdminKey;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the locking state.
|
||||
/// </summary>
|
||||
public LockingState State
|
||||
{
|
||||
get => LockInfo.State;
|
||||
set
|
||||
{
|
||||
if (LockInfo.State == value)
|
||||
{
|
||||
// State does not change, nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
Location = null; // Invalidate location.
|
||||
LastLockingStateChange = _nowDelegate(); // Get time stamp when state change happened.
|
||||
LockInfo = new LockInfo.Builder(LockInfo) { State = value }.Build();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary> Gets the time stamp of the last locking state change.</summary>
|
||||
public DateTime? LastLockingStateChange { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current location of the bike, null if location is unknown.
|
||||
/// </summary>
|
||||
public IGeolocation Location { get; set; }
|
||||
|
||||
/// <summary> Holds the percentage of lock battery.</summary>
|
||||
public double BatteryPercentage { get; set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the version info of the lock.
|
||||
/// </summary>
|
||||
public IVersionInfo VersionInfo { get; set; } = new VersionInfo.Builder().Build();
|
||||
|
||||
/// <summary> Loads lock info object from values. </summary>
|
||||
public void Load(int id, Guid guid, byte[] seed, byte[] userKey, byte[] adminKey)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder(LockInfo) { Id = id, Guid = guid, Seed = seed, UserKey = userKey, AdminKey = adminKey }.Build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using Newtonsoft.Json;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
|
||||
{
|
||||
public class VersionInfo : IVersionInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds info about firmware- and hardware version of a lock and the type of lock (lock version).
|
||||
/// </summary>
|
||||
private VersionInfo() { }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the firmware version of the lock.
|
||||
/// </summary>
|
||||
public int FirmwareVersion { get; private set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the hardware version (revision) of the lock.
|
||||
/// </summary>
|
||||
public int HardwareVersion { get; private set; } = 0;
|
||||
|
||||
/// <summary>
|
||||
/// Holds lock version (2 – classic, 3 – plus, 4 – GPS).
|
||||
/// </summary>
|
||||
public int LockVersion { get; private set; } = 0;
|
||||
|
||||
public override bool Equals(object obj)
|
||||
=> Equals(obj as VersionInfo);
|
||||
|
||||
public bool Equals(VersionInfo other)
|
||||
{
|
||||
if (ReferenceEquals(other, null)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (GetType() != other.GetType()) return false;
|
||||
|
||||
return ToString() == other.ToString();
|
||||
}
|
||||
|
||||
public override int GetHashCode() => ToString().GetHashCode();
|
||||
|
||||
public override string ToString() => JsonConvert.SerializeObject(this);
|
||||
|
||||
public static bool operator ==(VersionInfo lhs, VersionInfo rhs)
|
||||
{
|
||||
if (ReferenceEquals(lhs, null))
|
||||
return ReferenceEquals(rhs, null) ? true /*null == null = true*/: false;
|
||||
|
||||
return lhs.Equals(rhs);
|
||||
}
|
||||
|
||||
public static bool operator !=(VersionInfo lhs, VersionInfo rhs)
|
||||
=> !(lhs == rhs);
|
||||
public class Builder
|
||||
{
|
||||
private VersionInfo versionInfo = new VersionInfo();
|
||||
|
||||
public int FirmwareVersion { get => versionInfo.FirmwareVersion; set => versionInfo.FirmwareVersion = value; }
|
||||
|
||||
public int HardwareVersion { get => versionInfo.HardwareVersion; set => versionInfo.HardwareVersion = value; }
|
||||
|
||||
public int LockVersion { get => versionInfo.LockVersion; set => versionInfo.LockVersion = value; }
|
||||
|
||||
public VersionInfo Build()
|
||||
{
|
||||
return versionInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
174
SharedBusinessLogic/Model/Bikes/BikeInfoNS/CopriLock/BikeInfo.cs
Normal file
174
SharedBusinessLogic/Model/Bikes/BikeInfoNS/CopriLock/BikeInfo.cs
Normal file
|
@ -0,0 +1,174 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BC;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.BikeNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS;
|
||||
using ShareeBike.Model.MiniSurvey;
|
||||
using ShareeBike.Model.State;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
|
||||
{
|
||||
public class BikeInfo : BC.BikeInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a available bike or bike for which feed back is pending.
|
||||
/// </summary>
|
||||
/// <param name="bike">Bike object.</param>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
/// <param name="currentStationId">Id of station where bike is located.</param>
|
||||
/// <param name="lockInfo">Lock info.</param>
|
||||
/// <param name="isFeedbackPending">If true user has not yet given feedback after returning bike.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
public BikeInfo(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
string currentStationId,
|
||||
LockInfo lockInfo,
|
||||
bool isFeedbackPending = false,
|
||||
Uri operatorUri = null,
|
||||
RentalDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null,
|
||||
IMiniSurveyModel miniSurvey = null,
|
||||
string co2Saving = null) : base(
|
||||
new StateInfo(isFeedbackPending),
|
||||
bike != null
|
||||
? new Bike(
|
||||
bike.Id,
|
||||
LockModel.Sigo,
|
||||
bike.WheelType /* Ensure consistent lock model value */,
|
||||
bike.TypeOfBike,
|
||||
bike.AaRideType,
|
||||
bike.Description)
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
drive,
|
||||
dataSource,
|
||||
isDemo,
|
||||
group,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = lockInfo;
|
||||
MiniSurvey = miniSurvey;
|
||||
Co2Saving = co2Saving;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a requested bike.
|
||||
/// </summary>
|
||||
/// <param name="bike">Bike object.</param>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
/// <param name="requestedAt">Date time when bike was requested</param>
|
||||
/// <param name="mailAddress">Mail address of user which requested bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="lockInfo">Lock info.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
/// <param name="dateTimeProvider">Provider for current date time to calculate remaining time on demand for state of type reserved.</param>
|
||||
public BikeInfo(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
DateTime requestedAt,
|
||||
string mailAddress,
|
||||
string currentStationId,
|
||||
LockInfo lockInfo,
|
||||
Uri operatorUri,
|
||||
RentalDescription tariffDescription,
|
||||
Func<DateTime> dateTimeProvider,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null) : base(
|
||||
new StateInfo(
|
||||
dateTimeProvider,
|
||||
requestedAt,
|
||||
tariffDescription?.MaxReservationTimeSpan ?? StateRequestedInfo.UNKNOWNMAXRESERVATIONTIMESPAN,
|
||||
mailAddress,
|
||||
""), // BC code
|
||||
bike != null
|
||||
? new Bike(
|
||||
bike.Id,
|
||||
LockModel.Sigo /* Ensure consistent lock model value */,
|
||||
bike.WheelType,
|
||||
bike.TypeOfBike,
|
||||
bike.AaRideType,
|
||||
bike.Description)
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
drive,
|
||||
dataSource,
|
||||
isDemo,
|
||||
group,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = lockInfo;
|
||||
MiniSurvey = new MiniSurveyModel();
|
||||
Co2Saving = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a bike info object for a booked bike.
|
||||
/// </summary>
|
||||
/// <param name="bike">Bike object.</param>
|
||||
/// <param name="dataSource">Specified the source of the data.</param>
|
||||
/// <param name="bookedAt">Date time when bike was booked</param>
|
||||
/// <param name="mailAddress">Mail address of user which booked bike.</param>
|
||||
/// <param name="currentStationId">Name of station where bike is located, null if bike is on the road.</param>
|
||||
/// <param name="lockInfo">Lock info.</param>
|
||||
/// <param name="operatorUri">Holds the uri of the operator or null, in case of single operator setup.</param>
|
||||
/// <param name="tariffDescription">Hold tariff description of bike.</param>
|
||||
public BikeInfo(
|
||||
Bike bike,
|
||||
DriveMutable drive,
|
||||
DataSource dataSource,
|
||||
DateTime bookedAt,
|
||||
string mailAddress,
|
||||
string currentStationId,
|
||||
LockInfo lockInfo,
|
||||
Uri operatorUri,
|
||||
RentalDescription tariffDescription = null,
|
||||
bool? isDemo = DEFAULTVALUEISDEMO,
|
||||
IEnumerable<string> group = null) : base(
|
||||
new StateInfo(
|
||||
bookedAt,
|
||||
mailAddress,
|
||||
""),
|
||||
bike != null
|
||||
? new Bike(
|
||||
bike.Id,
|
||||
LockModel.Sigo /* Ensure consistent lock model value */,
|
||||
bike.WheelType,
|
||||
bike.TypeOfBike,
|
||||
bike.AaRideType,
|
||||
bike.Description)
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
drive,
|
||||
dataSource,
|
||||
isDemo,
|
||||
group,
|
||||
currentStationId,
|
||||
operatorUri,
|
||||
tariffDescription)
|
||||
{
|
||||
LockInfo = lockInfo;
|
||||
MiniSurvey = new MiniSurveyModel();
|
||||
Co2Saving = string.Empty;
|
||||
}
|
||||
public BikeInfo(BC.BikeInfo bikeInfo, LockInfo lockInfo) : base(
|
||||
bikeInfo ?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source bike info must not be null."))
|
||||
{
|
||||
LockInfo = lockInfo
|
||||
?? throw new ArgumentException($"Can not copy-construct {typeof(BikeInfo).Name}-object. Source lock object must not be null.");
|
||||
}
|
||||
|
||||
/// <summary> Holds the lock info.</summary>
|
||||
public LockInfo LockInfo { get; private set; }
|
||||
|
||||
public IMiniSurveyModel MiniSurvey { get; private set; }
|
||||
|
||||
public string Co2Saving { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
using System;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
|
||||
{
|
||||
public class BikeInfoMutable : BC.BikeInfoMutable, IBikeInfoMutable
|
||||
{
|
||||
/// <summary> Constructs a bike object from source. </summary>
|
||||
public BikeInfoMutable(BikeInfo bike, string stationName) : base(
|
||||
bike != null
|
||||
? bike.Bike
|
||||
: throw new ArgumentNullException(nameof(bike)),
|
||||
bike.Drive,
|
||||
bike.DataSource,
|
||||
bike.IsDemo,
|
||||
bike.Group,
|
||||
bike.StationId,
|
||||
stationName,
|
||||
bike.OperatorUri,
|
||||
bike.TariffDescription,
|
||||
() => DateTime.Now,
|
||||
bike.State)
|
||||
{
|
||||
LockInfo = new LockInfoMutable(bike.LockInfo.State);
|
||||
|
||||
BookingFinishedModel = new BookingFinishedModel
|
||||
{
|
||||
Co2Saving = bike.Co2Saving,
|
||||
MiniSurvey = new MiniSurvey.MiniSurveyModel()
|
||||
};
|
||||
|
||||
if ((bike?.MiniSurvey?.Questions) == null
|
||||
|| bike.MiniSurvey.Questions.Count <= 0)
|
||||
{
|
||||
// No querries to add.
|
||||
return;
|
||||
}
|
||||
|
||||
// Add a dummy query. Querries are not yet read from COPRI but compiled into the app.
|
||||
BookingFinishedModel.MiniSurvey.Questions.Add("q1", new MiniSurvey.QuestionModel());
|
||||
}
|
||||
|
||||
public LockInfoMutable LockInfo { get; }
|
||||
|
||||
ILockInfoMutable IBikeInfoMutable.LockInfo => LockInfo;
|
||||
|
||||
public IBookingFinishedModel BookingFinishedModel { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
|
||||
{
|
||||
public interface IBikeInfoMutable : BikeInfoNS.BC.IBikeInfoMutable
|
||||
{
|
||||
ILockInfoMutable LockInfo { get; }
|
||||
|
||||
IBookingFinishedModel BookingFinishedModel { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
|
||||
{
|
||||
public interface ILockInfoMutable
|
||||
{
|
||||
LockingState State { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,30 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
|
||||
{
|
||||
public class LockInfoMutable : ILockInfoMutable
|
||||
{
|
||||
/// <summary> Lock info object. </summary>
|
||||
private LockInfo LockInfo { get; set; }
|
||||
|
||||
/// <summary> Constructs a bluetooth lock info object. </summary>
|
||||
/// <param name="id">Id of lock must always been known when constructing an lock info object.</param>
|
||||
public LockInfoMutable(LockingState state)
|
||||
{
|
||||
LockInfo = new LockInfo.Builder() { State = state }.Build();
|
||||
}
|
||||
|
||||
public LockingState State
|
||||
{
|
||||
get => LockInfo.State;
|
||||
set => LockInfo = new LockInfo.Builder(LockInfo) { State = value }.Build();
|
||||
}
|
||||
|
||||
/// <summary> Holds the percentage of lock battery.</summary>
|
||||
public double BatteryPercentage { get; set; } = double.NaN;
|
||||
|
||||
/// <summary> Loads lock info object from values. </summary>
|
||||
public void Load()
|
||||
{
|
||||
LockInfo = new LockInfo.Builder(LockInfo) { }.Build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
using Serilog;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the state of a chargeable battery.
|
||||
/// </summary>
|
||||
public class Battery : IBattery
|
||||
{
|
||||
private Battery() { }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current charging level of the battery in percent, double.NaN if unknown.
|
||||
/// </summary>
|
||||
public double CurrentChargePercent { get; private set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
public int? CurrentChargeBars { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
public int? MaxChargeBars { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether backend is aware of battery charging level.
|
||||
/// </summary>
|
||||
public bool? IsBackendAccessible { get; private set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to display battery level or not.
|
||||
/// </summary>
|
||||
public bool? IsHidden { get; private set; } = null;
|
||||
|
||||
public class Builder
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the current charging level of the battery in bars.
|
||||
/// </summary>
|
||||
public int? CurrentChargeBars { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the maximum charging level of the battery in bars.
|
||||
/// </summary>
|
||||
public int? MaxChargeBars { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current charging level of the battery in percent.
|
||||
/// </summary>
|
||||
public double CurrentChargePercent { get; set; } = double.NaN;
|
||||
|
||||
/// <summary>
|
||||
/// Holds whether backend is aware of battery charging level.
|
||||
/// </summary>
|
||||
public bool? IsBackendAccessible { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// Holds whether to display battery level or not.
|
||||
/// </summary>
|
||||
public bool? IsHidden { get; set; } = null;
|
||||
|
||||
public Battery Build()
|
||||
{
|
||||
if (!double.IsNaN(CurrentChargePercent)
|
||||
&& (CurrentChargePercent < 0 || 100 < CurrentChargePercent))
|
||||
{
|
||||
// Invalid filling level detected
|
||||
CurrentChargePercent = double.NaN;
|
||||
}
|
||||
|
||||
if (CurrentChargeBars < 0)
|
||||
{
|
||||
// Current value of bars must never be smaller zero.
|
||||
CurrentChargeBars = null;
|
||||
}
|
||||
|
||||
if (MaxChargeBars < 0)
|
||||
{
|
||||
// Max value of bars must never be smaller zero.
|
||||
MaxChargeBars = null;
|
||||
}
|
||||
|
||||
if (CurrentChargeBars != null
|
||||
&& MaxChargeBars == null)
|
||||
{
|
||||
// If current charge bars is set, max charge must be set as well.
|
||||
Log.ForContext<Battery>().Error($"Current bars value can not be set to {CurrentChargeBars} if max bars is not set.");
|
||||
CurrentChargeBars = null;
|
||||
}
|
||||
|
||||
if (CurrentChargeBars != null
|
||||
&& MaxChargeBars != null
|
||||
&& CurrentChargeBars > MaxChargeBars)
|
||||
{
|
||||
// If current charge bars must never be larger than max charge bars.
|
||||
Log.ForContext<Battery>().Error($"Invalid current bars value {CurrentChargeBars} detected. Value must never be larger than max value bars {MaxChargeBars}.");
|
||||
CurrentChargeBars = null;
|
||||
}
|
||||
|
||||
return new Battery
|
||||
{
|
||||
CurrentChargeBars = CurrentChargeBars,
|
||||
MaxChargeBars = MaxChargeBars,
|
||||
CurrentChargePercent = CurrentChargePercent,
|
||||
IsBackendAccessible = IsBackendAccessible,
|
||||
IsHidden = IsHidden
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of a chargeable battery.
|
||||
/// </summary>
|
||||
public class BatteryMutable : IBatteryMutable, INotifyPropertyChanged
|
||||
{
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
|
||||
IBattery _battery;
|
||||
|
||||
public BatteryMutable(IBattery battery)
|
||||
{
|
||||
_battery = battery;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the current charging level of the battery in percent, double.NaN if unknown.
|
||||
/// </summary>
|
||||
public double CurrentChargePercent => _battery.CurrentChargePercent;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
public int? CurrentChargeBars
|
||||
{
|
||||
get => _battery.CurrentChargeBars;
|
||||
set
|
||||
{
|
||||
double GetCurrentChargePercent()
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
// Filling level is unknown.
|
||||
return double.NaN;
|
||||
}
|
||||
|
||||
if (_battery.MaxChargeBars == null || _battery.MaxChargeBars == 0)
|
||||
{
|
||||
// Percentage filling level can not be calculated.
|
||||
return _battery.CurrentChargePercent;
|
||||
}
|
||||
|
||||
return (int)(100 * value / _battery.MaxChargeBars);
|
||||
}
|
||||
|
||||
if (_battery.CurrentChargeBars == value)
|
||||
{
|
||||
// Nothing to do.
|
||||
return;
|
||||
}
|
||||
|
||||
_battery = new Battery.Builder
|
||||
{
|
||||
MaxChargeBars = _battery.MaxChargeBars,
|
||||
IsBackendAccessible = _battery.IsBackendAccessible,
|
||||
IsHidden = _battery.IsHidden,
|
||||
CurrentChargeBars = value,
|
||||
CurrentChargePercent = GetCurrentChargePercent(),
|
||||
}.Build();
|
||||
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentChargeBars)));
|
||||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentChargePercent)));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
public int? MaxChargeBars => _battery.MaxChargeBars;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether backend is aware of battery charging level.
|
||||
/// </summary>
|
||||
public bool? IsBackendAccessible => _battery.IsBackendAccessible;
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to display battery level or not.
|
||||
/// </summary>
|
||||
public bool? IsHidden => _battery.IsHidden;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS
|
||||
{
|
||||
public interface IBattery
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds the current charging level of the battery in percent, double.NaN if unknown.
|
||||
/// </summary>
|
||||
double CurrentChargePercent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the current charging level of the battery in bars. Must not be larger than MaxChargeBars, null if unknown.
|
||||
/// </summary>
|
||||
int? CurrentChargeBars { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the maximum charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
int? MaxChargeBars { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds whether backend is aware of battery charging level.
|
||||
/// </summary>
|
||||
bool? IsBackendAccessible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds whether to display battery level or not.
|
||||
/// </summary>
|
||||
bool? IsHidden { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
using System.ComponentModel;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages the state of a chargeable battery.
|
||||
/// </summary>
|
||||
public interface IBatteryMutable : INotifyPropertyChanged
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the current charging level of the battery in percent, double.NaN if unknown.
|
||||
/// </summary>
|
||||
double CurrentChargePercent { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the current charging level of the battery in bars. Must not be larger than MaxChargeBars, null if unknown.
|
||||
/// </summary>
|
||||
int? CurrentChargeBars { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the maximum charging level of the battery in bars, null if unknown.
|
||||
/// </summary>
|
||||
int? MaxChargeBars { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether backend is aware of battery charging level.
|
||||
/// </summary>
|
||||
bool? IsBackendAccessible { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets whether to display battery level or not.
|
||||
/// </summary>
|
||||
bool? IsHidden { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.EngineNS;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS
|
||||
{
|
||||
public enum DriveType
|
||||
{
|
||||
/// <summary>
|
||||
/// Bike without pedaling aid.
|
||||
/// </summary>
|
||||
SoleHumanPowered,
|
||||
|
||||
/// <summary>
|
||||
/// pedal electric cycle: Pedaling is assisted by an electric engine.
|
||||
/// </summary>
|
||||
Pedelec
|
||||
}
|
||||
|
||||
public class DriveMutable
|
||||
{
|
||||
public DriveMutable(
|
||||
IEngine engine = null,
|
||||
IBattery battery = null)
|
||||
{
|
||||
if (engine == null)
|
||||
{
|
||||
Engine = new Engine();
|
||||
Battery = new BatteryMutable(new Battery.Builder().Build());
|
||||
Type = DriveType.SoleHumanPowered;
|
||||
return;
|
||||
}
|
||||
|
||||
Engine = engine;
|
||||
Battery = new BatteryMutable(battery ?? new Battery.Builder().Build());
|
||||
Type = DriveType.Pedelec;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the type of the drive.
|
||||
/// </summary>
|
||||
public DriveType Type { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Engine driving the bike.
|
||||
/// </summary>
|
||||
public IEngine Engine { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Battery powering the engine.
|
||||
/// </summary>
|
||||
public IBatteryMutable Battery { get; private set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.EngineNS
|
||||
{
|
||||
public class Engine : IEngine
|
||||
{
|
||||
public Engine(string manufacturer = null)
|
||||
=> Manufacturer = !string.IsNullOrEmpty(manufacturer) ? manufacturer : null;
|
||||
|
||||
/// <summary>
|
||||
/// Manufacturer of the engine.
|
||||
/// </summary>
|
||||
public string Manufacturer { get; private set; } = null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
namespace ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.EngineNS
|
||||
{
|
||||
public interface IEngine
|
||||
{
|
||||
/// <summary>
|
||||
/// Manufacturer of the engine.
|
||||
/// </summary>
|
||||
string Manufacturer { get; }
|
||||
}
|
||||
}
|
23
SharedBusinessLogic/Model/Bikes/BikeInfoNS/DriveNS/IDrive.cs
Normal file
23
SharedBusinessLogic/Model/Bikes/BikeInfoNS/DriveNS/IDrive.cs
Normal file
|
@ -0,0 +1,23 @@
|
|||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.BatteryNS;
|
||||
using ShareeBike.Model.Bikes.BikeInfoNS.DriveNS.EngineNS;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS
|
||||
{
|
||||
public interface IDrive
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the type of the drive.
|
||||
/// </summary>
|
||||
DriveNS.DriveType Type { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Engine driving the bike.
|
||||
/// </summary>
|
||||
IEngine Engine { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Battery powering the engine.
|
||||
/// </summary>
|
||||
IBattery Battery { get; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using static ShareeBike.Model.Bikes.BikeInfoNS.RentalDescription;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS
|
||||
{
|
||||
public interface IRentalDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the time span for which a bike can be reserved.
|
||||
/// </summary>
|
||||
TimeSpan MaxReservationTimeSpan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic language aware tariff elements to be displayed to user.
|
||||
/// </summary>
|
||||
Dictionary<string, TariffElement> TariffEntries { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Well known language aware elements (AGB, tracking info, ...) to be displayed to user.
|
||||
/// </summary>
|
||||
Dictionary<string, InfoElement> InfoEntries { get; set; }
|
||||
}
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS
|
||||
{
|
||||
/// <summary>
|
||||
/// Successor of TarifDescription- object.
|
||||
/// Manages tariff- and rental info.
|
||||
/// </summary>
|
||||
public class RentalDescription : IRentalDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// The different elements of a tariff (example: "Max Gebühr", ) to be displayed by sharee.bike without processing
|
||||
/// </summary>
|
||||
public class TariffElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Describes the tariff element (language aware). To be displayed to user (example of elements: "Gratis Mietzeit", "Mietgebühr", "Max Gebühr").
|
||||
/// </summary>
|
||||
public string Description { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Holds the tariff element value (language aware, i.e. value from backend might be english, german, ... depending on smart phone value). To be displayed to user (example: "9.00 € / Tag").
|
||||
/// </summary>
|
||||
public string Value { get; set; } = string.Empty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Info element of general purpose (AGB, tracking info, ...)
|
||||
/// </summary>
|
||||
public class InfoElement
|
||||
{
|
||||
/// <summary>
|
||||
/// Key which identifies the value (required for special processing)
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Text (language aware) to be displayed to user.
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
public string Name { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Number of the tariff.
|
||||
/// </summary>
|
||||
public int? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Holds the time span for which a bike can be reserved.
|
||||
/// </summary>
|
||||
public TimeSpan MaxReservationTimeSpan { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Dynamic language aware tariff elements to be displayed to user.
|
||||
/// </summary>
|
||||
public Dictionary<string, TariffElement> TariffEntries { get; set; } = new Dictionary<string, TariffElement>();
|
||||
|
||||
/// <summary>
|
||||
/// Well known language aware elements (AGB, tracking info, ...) to be displayed to user.
|
||||
/// </summary>
|
||||
public Dictionary<string, InfoElement> InfoEntries { get; set; } = new Dictionary<string, InfoElement>();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
using System;
|
||||
|
||||
namespace ShareeBike.Model.Bikes.BikeInfoNS
|
||||
{
|
||||
/// <summary>
|
||||
/// Holds tariff info for a single bike.
|
||||
/// </summary>
|
||||
#if USCSHARP9
|
||||
public record TariffDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
public string Name { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of the tariff.
|
||||
/// </summary>
|
||||
public int? Number { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
public double FeeEuroPerHour { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs of the abo per month.
|
||||
/// </summary>
|
||||
public double AboEuroPerMonth { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
public TimeSpan FreeTimePerSession { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Max. costs per day in euro.
|
||||
/// </summary>
|
||||
public double MaxFeeEuroPerDay { get; init; }
|
||||
}
|
||||
#else
|
||||
public class TariffDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// Name of the tariff.
|
||||
/// </summary>
|
||||
public string Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Number of the tariff.
|
||||
/// </summary>
|
||||
public int? Number { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
public double FeeEuroPerHour { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs of the abo per month.
|
||||
/// </summary>
|
||||
public double AboEuroPerMonth { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Costs per hour in euro.
|
||||
/// </summary>
|
||||
public TimeSpan FreeTimePerSession { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Max. costs per day in euro.
|
||||
/// </summary>
|
||||
public double MaxFeeEuroPerDay { get; set; }
|
||||
|
||||
/// <summary> Info about operator agb as HTML (i.g. text and hyperlink). </summary>
|
||||
public string OperatorAgb { get; set; }
|
||||
|
||||
/// <summary> Text which informs users about GPS tracking if tracking is on. </summary>
|
||||
public string TrackingInfo { get; set; }
|
||||
|
||||
}
|
||||
#endif
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue