Version 3.0.381

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

View file

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup Label="MultilingualAppToolkit">
<MultilingualAppToolkitVersion>4.0</MultilingualAppToolkitVersion>
<MultilingualFallbackLanguage>en-GB</MultilingualFallbackLanguage>
<TranslationReport Condition="'$(Configuration)' == 'Release'">true</TranslationReport>
<SuppressPseudoWarning Condition="'$(Configuration)' == 'Debug'">true</SuppressPseudoWarning>
<Configurations>Debug;Release</Configurations>
<ReleaseVersion>3.0</ReleaseVersion>
</PropertyGroup>
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<RootNamespace>ShareeBike</RootNamespace>
<NeutralLanguage>en-GB</NeutralLanguage>
</PropertyGroup>
<Import Project="$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets" Label="MultilingualAppToolkit" Condition="Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\v$(MultilingualAppToolkitVersion)\Microsoft.Multilingual.ResxResources.targets')" />
<Target Name="MATPrerequisite" BeforeTargets="PrepareForBuild" Condition="!Exists('$(MSBuildExtensionsPath)\Microsoft\Multilingual App Toolkit\Microsoft.Multilingual.ResxResources.targets')" Label="MultilingualAppToolkit">
<Warning Text="$(MSBuildProjectFile) is Multilingual build enabled, but the Multilingual App Toolkit is unavailable during the build. If building with Visual Studio, please check to ensure that toolkit is properly installed." />
</Target>
<ItemGroup>
<Folder Include="Model\Device\" />
<Folder Include="Services\BluetoothLock\Crypto\" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.1.1" />
</ItemGroup>
<ItemGroup>
<Compile Update="MultilingualResources\Resources.Designer.cs">
<DesignTime>True</DesignTime>
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Update="MultilingualResources\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
</EmbeddedResource>
</ItemGroup>
</Project>

View file

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.31229.75
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LockIt.BusinessLogic", "LockIt.BusinessLogic.csproj", "{C4194BC7-22CF-4F1C-B5F8-E75F9F552E34}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C4194BC7-22CF-4F1C-B5F8-E75F9F552E34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C4194BC7-22CF-4F1C-B5F8-E75F9F552E34}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C4194BC7-22CF-4F1C-B5F8-E75F9F552E34}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C4194BC7-22CF-4F1C-B5F8-E75F9F552E34}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9539C371-F0FB-47C0-B1B3-76875B4923FB}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="Serilog" Version="3.0.1" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.7.34031.279
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LockIt.BusinessLogicCore", "LockIt.BusinessLogicCore.csproj", "{44994E9A-4EC5-4E2E-8151-38AA0BE606D2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0E8EB534-0161-4A42-8F19-59BDBAD9C64D}"
ProjectSection(SolutionItems) = preProject
..\.editorconfig = ..\.editorconfig
..\.vsspell = ..\.vsspell
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{44994E9A-4EC5-4E2E-8151-38AA0BE606D2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{44994E9A-4EC5-4E2E-8151-38AA0BE606D2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{44994E9A-4EC5-4E2E-8151-38AA0BE606D2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{44994E9A-4EC5-4E2E-8151-38AA0BE606D2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9539C371-F0FB-47C0-B1B3-76875B4923FB}
EndGlobalSection
EndGlobal

View file

@ -0,0 +1,11 @@
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
{
public interface ILockInfo
{
/// <summary> Identification number of bluetooth lock.</summary>
int Id { get; }
/// <summary> Gets the user key.</summary>
byte[] UserKey { get; }
}
}

View file

@ -0,0 +1,124 @@
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
using ShareeBike.Model.Connector;
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
{
/// <summary> Locking states. </summary>
public enum LockingState
{
/// <summary> App is not connected to lock.</summary>
UnknownDisconnected,
/// <summary> Lock might be open, closed or something in between.</summary>
/// <remarks>
/// Under certain circumstances lock reports/ is known to be in this state (genuine ILOCKIT locking state LockitLockingState.Unknown).
/// Example: If bold is blocked it might happen that opening or closing lock leads to state unknown, i.e. might be open, closed or inbetween.
/// </remarks>
UnknownFromHardwareError,
/// <summary> Lock is closed. </summary>
Closed,
/// <summary> Lock is open. </summary>
Open
}
[DataContract]
public class LockInfo : ILockInfo, IEquatable<LockInfo>
{
/// <summary> Identification number of bluetooth lock, 6-digits, second part of advertisement name.</summary>
[DataMember]
public int Id { get; private set; } = TextToLockItTypeHelper.INVALIDLOCKID;
/// <summary> Identification GUID of bluetooth lock.</summary>
[DataMember]
public Guid Guid { get; private set; } = TextToLockItTypeHelper.INVALIDLOCKGUID;
[DataMember]
public byte[] UserKey { get; private set; }
[DataMember]
public byte[] AdminKey { get; private set; }
[DataMember]
public byte[] Seed { get; private set; }
/// <summary> Locking state of bluetooth lock. </summary>
[DataMember]
public LockingState State { get; private set; } = LockingState.UnknownDisconnected;
public bool IsIdValid => Id != TextToLockItTypeHelper.INVALIDLOCKID;
public bool IsGuidValid => Guid != TextToLockItTypeHelper.INVALIDLOCKGUID;
public override bool Equals(object obj)
=> Equals(obj as LockInfo);
public bool Equals(LockInfo 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 ==(LockInfo lhs, LockInfo rhs)
{
if (ReferenceEquals(lhs, null))
return ReferenceEquals(rhs, null) ? true /*null == null = true*/: false;
return lhs.Equals(rhs);
}
public static bool operator !=(LockInfo lhs, LockInfo rhs)
=> !(lhs == rhs);
public class Builder
{
public Builder(LockInfo lockInfo = null)
{
if (lockInfo == null)
{
return;
}
LockInfo = JsonConvert.DeserializeObject<LockInfo>(JsonConvert.SerializeObject(lockInfo));
}
private readonly LockInfo LockInfo = new LockInfo();
public byte[] UserKey { get => LockInfo.UserKey; set => LockInfo.UserKey = value; }
public byte[] AdminKey { get => LockInfo.AdminKey; set => LockInfo.AdminKey = value; }
public byte[] Seed { get => LockInfo.Seed; set => LockInfo.Seed = value; }
public int Id { get => LockInfo.Id; set => LockInfo.Id = value; }
public Guid Guid { get => LockInfo.Guid; set => LockInfo.Guid = value; }
public LockingState State { get => LockInfo.State; set => LockInfo.State = value; }
public LockInfo Build()
{
// Ensure consistency.
if ((UserKey?.Length > 0 || Seed?.Length > 0)
&& (UserKey?.Length == 0 || Seed?.Length == 0 || !LockInfo.IsIdValid))
throw new ArgumentException($"Can not build {typeof(LockInfo).Name}. Lock parameters must either be all know or all unknown.");
if (UserKey == null) UserKey = new byte[0];
if (AdminKey == null) AdminKey = new byte[0];
if (Seed == null) Seed = new byte[0];
return LockInfo;
}
}
}
}

View file

@ -0,0 +1,8 @@
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
{
public interface ILockInfo
{
/// <summary> Locking state of backend lock. </summary>
LockingState State { get; }
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace ShareeBike.Model.Bikes.BikeInfoNS.CopriLock
{
/// <summary> Locking states. </summary>
public enum LockingState
{
/// <summary> App is not connected to lock.</summary>
UnknownDisconnected,
/// <summary> Lock is closed. </summary>
Closed,
/// <summary> Lock is closing. </summary>
Closing,
/// <summary> Lock is open. </summary>
Open,
/// <summary> Lock is opening. </summary>
Opening
}
[DataContract]
public class LockInfo : ILockInfo
{
/// <summary> Locking state of bluetooth lock. </summary>
[DataMember]
public LockingState State { get; private set; } = LockingState.UnknownDisconnected;
public override bool Equals(object obj) => this.Equals(obj as LockInfo);
public bool Equals(LockInfo other)
{
if (Object.ReferenceEquals(other, null)) return false;
if (Object.ReferenceEquals(this, other)) return true;
if (this.GetType() != other.GetType()) return false;
return ToString() == other.ToString();
}
public override int GetHashCode()
{
return ToString().GetHashCode();
}
public override string ToString()
{
return JsonConvert.SerializeObject(this);
}
public static bool operator ==(LockInfo lhs, LockInfo rhs)
{
if (Object.ReferenceEquals(lhs, null))
return Object.ReferenceEquals(rhs, null) ? true /*null == null = true*/: false;
return lhs.Equals(rhs);
}
public static bool operator !=(LockInfo lhs, LockInfo rhs)
{
return !(lhs == rhs);
}
public class Builder
{
public Builder(LockInfo lockInfo = null)
{
if (lockInfo == null)
{
return;
}
LockInfo = JsonConvert.DeserializeObject<LockInfo>(JsonConvert.SerializeObject(lockInfo));
}
private readonly LockInfo LockInfo = new LockInfo();
public LockingState State { get => LockInfo.State; set => LockInfo.State = value; }
public LockInfo Build() => LockInfo;
}
}
}

View file

@ -0,0 +1,32 @@
using System;
using System.Text.RegularExpressions;
namespace ShareeBike.Model.Connector
{
public static class TextToLockItTypeHelper
{
/// <summary> Lock id which representing a non valid id. </summary>
public const int INVALIDLOCKID = 0;
/// <summary> Lock GUID which representing a non valid id. </summary>
public readonly static Guid INVALIDLOCKGUID = new Guid();
/// <summary> First part of advertisement name.</summary>
public static string ISHAREITADVERTISMENTTITLE = "ISHAREIT";
/// <summary> Gets the ID part from advertisment name. </summary>
/// <remarks> Advertisement name is made up of name plus separator (+ or -) and a ID</remarks>
/// <param name="advertisementName">Advertisment name to extract info from.</param>
/// <returns>From information.</returns>
public static int GetBluetoothLockId(this string advertisementName)
{
var name = advertisementName?.ToUpper();
if (string.IsNullOrEmpty(name))
return INVALIDLOCKID;
return int.TryParse(Regex.Replace(advertisementName, $"{ISHAREITADVERTISMENTTITLE}[\\-,\\+ ]", ""), out int lockId)
? lockId
: INVALIDLOCKID;
}
}
}

View file

@ -0,0 +1,17 @@
namespace ShareeBike.Model.Device
{
public interface ICipher
{
/// <summary> Encrypt data.</summary>
/// <param name="key">Key to encrypt data.</param>
/// <param name="clear">Data to entrycpt.</param>
/// <returns></returns>
byte[] Encrypt(byte[] key, byte[] clear);
/// <summary> Decrypt data. </summary>
/// <param name="key">Key to decrypt data.</param>
/// <param name="encrypted">Encrpyted data to decrypt.</param>
/// <returns></returns>
byte[] Decrypt(byte[] key, byte[] encrypted);
}
}

View file

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:oasis:names:tc:xliff:document:1.2 xliff-core-1.2-transitional.xsd">
<file datatype="xml" source-language="en-GB" target-language="de" original="LOCKITSHARED/MULTILINGUALRESOURCES/RESOURCES.RESX" tool-id="MultilingualAppToolkit" product-name="n/a" product-version="n/a" build-num="n/a">
<header>
<tool tool-id="MultilingualAppToolkit" tool-name="Multilingual App Toolkit" tool-version="4.0.6916.0" tool-company="Microsoft" />
</header>
<body>
<group id="LOCKIT.BUSINESSLOGIC/MULTILINGUALRESOURCES/RESOURCES.RESX">
<trans-unit id="LockItExceptionBluetoothDisconnected" translate="yes" xml:space="preserve">
<source>Bluetooth connection to the lock could not be established. Activate Bluetooth and try again.</source>
<target state="new">Bluetooth connection to the lock could not be established. Activate Bluetooth and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionCloseLockMoving" translate="yes" xml:space="preserve">
<source>The process is motion sensitive. Step close to the lock, do not move, and try again.</source>
<target state="new">The process is motion sensitive. Step close to the lock, do not move, and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionCloseLockBoltBlocked" translate="yes" xml:space="preserve">
<source>Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.</source>
<target state="new">Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionCloseLockUnexpectedLockState" translate="yes" xml:space="preserve">
<source>Unexpected locking state "{0}" detected after sending close command. Please try again or contact customer support.</source>
<target state="new">Unexpected locking state "{0}" detected after sending close command. Please try again or contact customer support.</target>
</trans-unit>
<trans-unit id="LockItExceptionCloseLockUnknownPosition" translate="yes" xml:space="preserve">
<source>Lock reports unknown bold position. Lock could be closed or open. Please try again or contact customer support.</source>
<target state="new">Lock reports unknown bold position. Lock could be closed or open. Please try again or contact customer support.</target>
</trans-unit>
<trans-unit id="LockItExceptionConnectLockBluetoothNotOnException" translate="yes" xml:space="preserve">
<source>Bluetooth connection to the lock could not be established. Current Blutooth status is {0}. Activate Bluetooth and try again.</source>
<target state="new">Bluetooth connection to the lock could not be established. Current Blutooth status is {0}. Activate Bluetooth and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionConnectLockBluetoothOffException" translate="yes" xml:space="preserve">
<source>Bluetooth connection to the lock could not be established. Activate Bluetooth and try again.</source>
<target state="new">Bluetooth connection to the lock could not be established. Activate Bluetooth and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionConnectLockLocationOffException" translate="yes" xml:space="preserve">
<source>Connection to the lock could not be established. Activate Location services and try again.</source>
<target state="new">Connection to the lock could not be established. Activate Location services and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionConnectLockLocationPermissingMissingException" translate="yes" xml:space="preserve">
<source>Connection to the lock could not be established. Grant Location permission, activate Location services and try again.</source>
<target state="new">Connection to the lock could not be established. Grant Location permission, activate Location services and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionOpenLockBoltBlocked" translate="yes" xml:space="preserve">
<source>Lock bolt is blocked. Make sure that no spoke presses against the lock bolt and try again.</source>
<target state="new">Lock bolt is blocked. Make sure that no spoke presses against the lock bolt and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionOpenLockBoldStatusIsUnknown" translate="yes" xml:space="preserve">
<source>Position of lock bolt is unknown. Lock could be closed or open. Please try again or contact customer support.</source>
<target state="new">Position of lock bolt is unknown. Lock could be closed or open. Please try again or contact customer support.</target>
</trans-unit>
<trans-unit id="LockItExceptionOpenLockBoldWasBlocked" translate="yes" xml:space="preserve">
<source>Bolt was blocked. Make sure that no spoke presses against the lock bolt and try again.</source>
<target state="new">Bolt was blocked. Make sure that no spoke presses against the lock bolt and try again.</target>
</trans-unit>
<trans-unit id="LockItExceptionOpenLockUnexpectedState" translate="yes" xml:space="preserve">
<source>Unexpected locking state "{0}" detected after sending open command. Please try again or contact customer support.</source>
<target state="new">Unexpected locking state "{0}" detected after sending open command. Please try again or contact customer support.</target>
</trans-unit>
</group>
</body>
</file>
</xliff>

View file

@ -0,0 +1,180 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
namespace ShareeBike.MultilingualResources {
using System;
/// <summary>
/// A strongly-typed resource class, for looking up localized strings, etc.
/// </summary>
// This class was auto-generated by the StronglyTypedResourceBuilder
// class via a tool like ResGen or Visual Studio.
// To add or remove a member, edit your .ResX file then rerun ResGen
// with the /str option, or rebuild your VS project.
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
internal class Resources {
private static global::System.Resources.ResourceManager resourceMan;
private static global::System.Globalization.CultureInfo resourceCulture;
[global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
internal Resources() {
}
/// <summary>
/// Returns the cached ResourceManager instance used by this class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Resources.ResourceManager ResourceManager {
get {
if (object.ReferenceEquals(resourceMan, null)) {
global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ShareeBike.MultilingualResources.Resources", typeof(Resources).Assembly);
resourceMan = temp;
}
return resourceMan;
}
}
/// <summary>
/// Overrides the current thread's CurrentUICulture property for all
/// resource lookups using this strongly typed resource class.
/// </summary>
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
internal static global::System.Globalization.CultureInfo Culture {
get {
return resourceCulture;
}
set {
resourceCulture = value;
}
}
/// <summary>
/// Looks up a localized string similar to Bluetooth connection to the lock could not be established. Activate Bluetooth and try again..
/// </summary>
internal static string LockItExceptionBluetoothDisconnected {
get {
return ResourceManager.GetString("LockItExceptionBluetoothDisconnected", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again..
/// </summary>
internal static string LockItExceptionCloseLockBoltBlocked {
get {
return ResourceManager.GetString("LockItExceptionCloseLockBoltBlocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to The process is motion sensitive. Step close to the lock, do not move, and try again..
/// </summary>
internal static string LockItExceptionCloseLockMoving {
get {
return ResourceManager.GetString("LockItExceptionCloseLockMoving", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unexpected locking state &quot;{0}&quot; detected after sending close command. Please try again or contact customer support..
/// </summary>
internal static string LockItExceptionCloseLockUnexpectedLockState {
get {
return ResourceManager.GetString("LockItExceptionCloseLockUnexpectedLockState", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock reports unknown bold position. Lock could be closed or open. Please try again or contact customer support..
/// </summary>
internal static string LockItExceptionCloseLockUnknownPosition {
get {
return ResourceManager.GetString("LockItExceptionCloseLockUnknownPosition", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bluetooth connection to the lock could not be established. Current Blutooth status is {0}. Activate Bluetooth and try again..
/// </summary>
internal static string LockItExceptionConnectLockBluetoothNotOnException {
get {
return ResourceManager.GetString("LockItExceptionConnectLockBluetoothNotOnException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bluetooth connection to the lock could not be established. Activate Bluetooth and try again..
/// </summary>
internal static string LockItExceptionConnectLockBluetoothOffException {
get {
return ResourceManager.GetString("LockItExceptionConnectLockBluetoothOffException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection to the lock could not be established. Activate Location services and try again..
/// </summary>
internal static string LockItExceptionConnectLockLocationOffException {
get {
return ResourceManager.GetString("LockItExceptionConnectLockLocationOffException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Connection to the lock could not be established. Grant Location permission, activate Location services and try again..
/// </summary>
internal static string LockItExceptionConnectLockLocationPermissingMissingException {
get {
return ResourceManager.GetString("LockItExceptionConnectLockLocationPermissingMissingException", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Position of lock bolt is unknown. Lock could be closed or open. Please try again or contact customer support..
/// </summary>
internal static string LockItExceptionOpenLockBoldStatusIsUnknown {
get {
return ResourceManager.GetString("LockItExceptionOpenLockBoldStatusIsUnknown", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Bolt was blocked. Make sure that no spoke presses against the lock bolt and try again..
/// </summary>
internal static string LockItExceptionOpenLockBoldWasBlocked {
get {
return ResourceManager.GetString("LockItExceptionOpenLockBoldWasBlocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Lock bolt is blocked. Make sure that no spoke presses against the lock bolt and try again..
/// </summary>
internal static string LockItExceptionOpenLockBoltBlocked {
get {
return ResourceManager.GetString("LockItExceptionOpenLockBoltBlocked", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Unexpected locking state &quot;{0}&quot; detected after sending open command. Please try again or contact customer support..
/// </summary>
internal static string LockItExceptionOpenLockUnexpectedState {
get {
return ResourceManager.GetString("LockItExceptionOpenLockUnexpectedState", resourceCulture);
}
}
}
}

View file

@ -0,0 +1,54 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LockItExceptionCloseLockUnknownPosition" xml:space="preserve">
<value>Das Schloss meldet unbekannte Position des Schlossbügels. Das Schloss könnte geschlossen oder offen sein. Bitte versuchen Sie es erneut oder melden Sie das Fahrrad dem Betreiber!</value>
</data>
<data name="LockItExceptionOpenLockUnexpectedState" xml:space="preserve">
<value>Unerwarteter Schlosszustand "{0}" nach Ausführen des Öffnen-Befehls erkannt. Bitte versuchen Sie es erneut oder kontaktieren Sie den Kundensupport.</value>
</data>
<data name="LockItExceptionBluetoothDisconnected" xml:space="preserve">
<value>Es konnte keine Bluetooth-Verbindung zum Schloss hergestellt werden. Aktivieren Sie Bluetooth und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionCloseLockMoving" xml:space="preserve">
<value>Der Vorgang ist bewegungssensibel. Treten Sie nah an das Schloss heran, bewegen Sie sich nicht und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionOpenLockBoldWasBlocked" xml:space="preserve">
<value>Der Schlossbügel war blockiert. Stellen Sie sicher, dass keine Speiche oder anderer Gegenstand das Schloss blockiert und wiederholen Sie den Vorgang.</value>
</data>
<data name="LockItExceptionConnectLockBluetoothNotOnException" xml:space="preserve">
<value>Es kann keine Bluetooth-Verbindung hergestellt werden. Der aktuelle Blutooth-Status ist {0}. Aktivieren Sie Bluetooth und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionConnectLockBluetoothOffException" xml:space="preserve">
<value>Es kann keine Bluetooth-Verbindung hergestellt werden. Aktivieren Sie Bluetooth und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionConnectLockLocationOffException" xml:space="preserve">
<value>Es konnte keine Verbindung zum Schloss hergestellt werden. Aktivieren Sie die Standortdienste und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionConnectLockLocationPermissingMissingException" xml:space="preserve">
<value>Es konnte keine Verbindung zum Schloss hergestellt werden. Erlauben Sie den Standort-Zugriff, aktivieren Sie die Standortdienste und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionOpenLockBoldStatusIsUnknown" xml:space="preserve">
<value>Position des Schlossbügels ist unbekannt. Schloss könnte geschlossen oder offen sein. Bitte versuchen Sie es erneut oder kontaktieren Sie den Kundensupport.</value>
</data>
<data name="LockItExceptionCloseLockBoltBlocked" xml:space="preserve">
<value>Der Schlossbügel ist blockiert. Stellen Sie sicher, dass keine Speiche oder ein anderes Hindernis das Schließen des Schlosses verhindert, und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionOpenLockBoltBlocked" xml:space="preserve">
<value>Der Schlossbügel ist blockiert. Stellen Sie sicher, dass keine Speiche gegen den Bügel drückt und versuchen Sie es erneut.</value>
</data>
<data name="LockItExceptionCloseLockUnexpectedLockState" xml:space="preserve">
<value>Unerwarteter Schlosszustand "{0}" nach Senden des Schließbefehls festgestellt. Bitte versuchen Sie es erneut oder kontaktieren Sie den Kundensupport.</value>
</data>
</root>

View file

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="LockItExceptionBluetoothDisconnected" xml:space="preserve">
<value>Bluetooth connection to the lock could not be established. Activate Bluetooth and try again.</value>
</data>
<data name="LockItExceptionCloseLockMoving" xml:space="preserve">
<value>The process is motion sensitive. Step close to the lock, do not move, and try again.</value>
</data>
<data name="LockItExceptionCloseLockBoltBlocked" xml:space="preserve">
<value>Lock bolt is blocked. Make sure that no spoke or any other obstacle prevents the lock from closing and try again.</value>
</data>
<data name="LockItExceptionCloseLockUnexpectedLockState" xml:space="preserve">
<value>Unexpected locking state "{0}" detected after sending close command. Please try again or contact customer support.</value>
</data>
<data name="LockItExceptionCloseLockUnknownPosition" xml:space="preserve">
<value>Lock reports unknown bold position. Lock could be closed or open. Please try again or contact customer support.</value>
</data>
<data name="LockItExceptionConnectLockBluetoothNotOnException" xml:space="preserve">
<value>Bluetooth connection to the lock could not be established. Current Blutooth status is {0}. Activate Bluetooth and try again.</value>
</data>
<data name="LockItExceptionConnectLockBluetoothOffException" xml:space="preserve">
<value>Bluetooth connection to the lock could not be established. Activate Bluetooth and try again.</value>
</data>
<data name="LockItExceptionConnectLockLocationOffException" xml:space="preserve">
<value>Connection to the lock could not be established. Activate Location services and try again.</value>
</data>
<data name="LockItExceptionConnectLockLocationPermissingMissingException" xml:space="preserve">
<value>Connection to the lock could not be established. Grant Location permission, activate Location services and try again.</value>
</data>
<data name="LockItExceptionOpenLockBoltBlocked" xml:space="preserve">
<value>Lock bolt is blocked. Make sure that no spoke presses against the lock bolt and try again.</value>
</data>
<data name="LockItExceptionOpenLockBoldStatusIsUnknown" xml:space="preserve">
<value>Position of lock bolt is unknown. Lock could be closed or open. Please try again or contact customer support.</value>
</data>
<data name="LockItExceptionOpenLockBoldWasBlocked" xml:space="preserve">
<value>Bolt was blocked. Make sure that no spoke presses against the lock bolt and try again.</value>
</data>
<data name="LockItExceptionOpenLockUnexpectedState" xml:space="preserve">
<value>Unexpected locking state "{0}" detected after sending open command. Please try again or contact customer support.</value>
</data>
</root>

View file

@ -0,0 +1,82 @@
using Serilog;
using ShareeBike.Model.Device;
namespace ShareeBike.Services.BluetoothLock.Crypto
{
public class AuthCryptoHelper
{
private ICipher Cipher { get; }
/// <summary> Encrypted seed (random number) created inside ILOCKIT and passd to app.</summary>
private byte[] SeedLockEncrypted { get; }
/// <summary> Contstructs a auth crypto helper object.</summary>
/// <param name="seedLockEncrypted">Encrypted seed to deocode using <see cref="KeyCopri"/>.</param>
/// <param name="keyCopri">Key used to to decrypt <see cref="SeedLockEncrypted"/>.</param>
public AuthCryptoHelper(
byte[] seedLockEncrypted,
byte[] keyCopri,
ICipher cipher)
{
KeyCopri = keyCopri;
SeedLockEncrypted = seedLockEncrypted;
Cipher = cipher ?? new Cipher();
}
/// <summary> Public for testing purposes only.</summary>
public byte[] GetSeedLock()
{
byte[] seedLockDecrypted;
var seedLockEncrypted = SeedLockEncrypted;
var keyCopri = KeyCopri;
try
{
seedLockDecrypted = Cipher.Decrypt(
keyCopri,
seedLockEncrypted);
}
catch (System.Exception exception)
{
Log.ForContext<AuthCryptoHelper>().Error("Decrypting seed from lock failed. {Exception}", exception);
throw;
}
Log.ForContext<AuthCryptoHelper>().Verbose($"Lock random number decrypted from {string.Join(",", seedLockEncrypted)} to {string.Join(",", seedLockDecrypted)} using {string.Join(", ", keyCopri)}.");
return seedLockDecrypted;
}
public byte[] GetAccessKeyEncrypted()
{
var accessKey = GetSeedLock();
if (accessKey == null || accessKey.Length <= 0)
{
Log.ForContext<AuthCryptoHelper>().Error("Creating access key failed, Key must not be null or empty.");
throw new System.Exception();
}
accessKey[accessKey.Length - 1] += 1;
var keyCopri = KeyCopri;
byte[] acccessKeyEncrypted;
try
{
acccessKeyEncrypted = Cipher.Encrypt(
keyCopri,
accessKey);
}
catch (System.Exception exception)
{
Log.ForContext<AuthCryptoHelper>().Error("Encrypting access key failed. {Exception}", exception);
throw;
}
Log.ForContext<AuthCryptoHelper>().Verbose($"Access key encrypted from {string.Join(",", accessKey)} to {string.Join(",", acccessKeyEncrypted)} using {string.Join(", ", keyCopri)}.");
return acccessKeyEncrypted;
}
public byte[] KeyCopri { get; }
}
}

View file

@ -0,0 +1,88 @@
using System;
using System.IO;
using System.Security.Cryptography;
using ShareeBike.Model.Device;
namespace ShareeBike.Services.BluetoothLock.Crypto
{
public class Cipher : ICipher
{
/// <summary> Decrypt data. </summary>
/// <remarks>
/// Further info see:
/// https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.aes?view=netcore-3.1 for further info
// https://docs.microsoft.com/en-us/dotnet/standard/security/cryptographic-services
// https://stackoverflow.com/questions/24903575/how-to-return-byte-when-decrypt-using-cryptostream-descryptoserviceprovider/24903689
/// </remarks>
/// <param name="key">Key to decrypt data with.</param>
/// <param name="encrypted">Encrpyted data to decrypt.</param>
/// <returns>Decrypted data.</returns>
public byte[] Decrypt(byte[] key, byte[] encrypted)
{
// Check arguments.
if (encrypted == null || encrypted.Length <= 0)
throw new ArgumentNullException(nameof(encrypted));
if (key == null || key.Length <= 0)
throw new ArgumentNullException(nameof(key));
using (Aes aesAlg = Aes.Create())
{
aesAlg.KeySize = 192;
aesAlg.Mode = CipherMode.ECB;
aesAlg.Padding = PaddingMode.None;
aesAlg.Key = key;
// Create a decryptor to perform the stream transform.
ICryptoTransform decryptor = aesAlg.CreateDecryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for decryption.
using (var msDecrypt = new MemoryStream())
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Write))
{
csDecrypt.Write(encrypted, 0, encrypted.Length);
csDecrypt.FlushFinalBlock();
return msDecrypt.ToArray();
}
}
}
}
public byte[] Encrypt(byte[] key, byte[] clear)
{
// Check arguments.
if (clear == null || clear.Length <= 0)
throw new ArgumentNullException("plainText");
if (key == null || key.Length <= 0)
throw new ArgumentNullException("Key");
// Create an AesCryptoServiceProvider object
// with the specified key and IV.
using (AesCryptoServiceProvider aesAlg = new AesCryptoServiceProvider())
{
aesAlg.KeySize = 192;
aesAlg.Mode = CipherMode.ECB;
aesAlg.Padding = PaddingMode.None;
aesAlg.Key = key;
// Create an encryptor to perform the stream transform.
ICryptoTransform encryptor = aesAlg.CreateEncryptor(aesAlg.Key, aesAlg.IV);
// Create the streams used for encryption.
using (var msEncrypt = new MemoryStream())
{
using (var csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write))
{
csEncrypt.Write(clear, 0, clear.Length);
csEncrypt.FlushFinalBlock();
return msEncrypt.ToArray();
}
}
}
}
}
}

View file

@ -0,0 +1,7 @@
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class AlreadyConnectedException : System.Exception
{
public AlreadyConnectedException() : base("Invalid reconnect call detected. Device is already connected.") { }
}
}

View file

@ -0,0 +1,8 @@
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class AuthKeyException : System.Exception
{
public AuthKeyException(string message) : base(message)
{ }
}
}

View file

@ -0,0 +1,20 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
namespace ShareeBike.Services.BluetoothLock.Exception
{
/// <summary>
/// If fired if app is disconnected from bluetooth.
/// </summary>
/// <remarks>
/// Member to determine if app is disconnected: IDevice.State from server Plugin.BLE. If DeviceState.Disconnected exception is fired.
/// All possible states: Disconnected, Connecting, Connected, Limited.
/// </remarks>
public class BluetoothDisconnectedException : StateAwareException
{
public BluetoothDisconnectedException() : base(
LockingState.UnknownDisconnected,
MultilingualResources.Resources.LockItExceptionBluetoothDisconnected)
{
}
}
}

View file

@ -0,0 +1,17 @@

using ShareeBike.MultilingualResources;
namespace ShareeBike.Services.BluetoothLock.Exception
{
/// <summary> Exception which is fired when bluetooth is not on. </summary>
/// <remarks>
/// Member to determine if app is disconnected: IBluetoothLE.State from server Plugin.BLE. If current value is not BluetoothState.On exception is fired.
/// All possible states: Unknown, Unavailable, Unauthorized, TurningOn, On, TurningOff, Off.
/// </remarks>
public class ConnectBluetoothNotOnException : System.Exception
{
public ConnectBluetoothNotOnException() : base(Resources.LockItExceptionConnectLockBluetoothOffException) { }
public ConnectBluetoothNotOnException(object state) : base(string.Format(Resources.LockItExceptionConnectLockBluetoothNotOnException, state.ToString())) { }
}
}

View file

@ -0,0 +1,9 @@
using ShareeBike.MultilingualResources;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class ConnectLocationOffException : System.Exception
{
public ConnectLocationOffException() : base(Resources.LockItExceptionConnectLockLocationOffException) { }
}
}

View file

@ -0,0 +1,9 @@
using ShareeBike.MultilingualResources;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class ConnectLocationPermissionMissingException : System.Exception
{
public ConnectLocationPermissionMissingException() : base(Resources.LockItExceptionConnectLockLocationPermissingMissingException) { }
}
}

View file

@ -0,0 +1,22 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class CouldntCloseBoltBlockedException : StateAwareException
{
/// <summary>
/// Constructs a bold blocked exception.
/// </summary>
/// <remarks>
/// Lock is closed in most cases, but this is not guaranteed according to haveltec.
/// </remarks>
/// <param name="state">State of the lock while/ after bold blocked event.</param>
/// <remarks>
/// </remarks>
public CouldntCloseBoltBlockedException(LockingState state = LockingState.UnknownFromHardwareError) : base(
state,
MultilingualResources.Resources.LockItExceptionCloseLockBoltBlocked)
{
}
}
}

View file

@ -0,0 +1,18 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
using ShareeBike.MultilingualResources;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class CouldntCloseInconsistentStateExecption : StateAwareException
{
public CouldntCloseInconsistentStateExecption(LockingState state) :
base(
state,
state != LockingState.UnknownFromHardwareError
? string.Format(Resources.LockItExceptionCloseLockUnexpectedLockState, state)
: Resources.LockItExceptionCloseLockUnknownPosition)
{
}
}
}

View file

@ -0,0 +1,13 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class CouldntCloseMovingException : StateAwareException
{
public CouldntCloseMovingException() : base(
LockingState.Open, // Locking bold is probable (according to haveltec) still open.
MultilingualResources.Resources.LockItExceptionCloseLockMoving)
{
}
}
}

View file

@ -0,0 +1,16 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
namespace ShareeBike.Services.BluetoothLock.Exception
{
/// <summary>
/// Lock can not be opened, because bold is blocked. Lock reports that obstacle is still blocking.
/// </summary>
public class CouldntOpenBoldIsBlockedException : StateAwareException
{
public CouldntOpenBoldIsBlockedException() : base(
LockingState.UnknownFromHardwareError,
MultilingualResources.Resources.LockItExceptionOpenLockBoltBlocked)
{
}
}
}

View file

@ -0,0 +1,16 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
namespace ShareeBike.Services.BluetoothLock.Exception
{
/// <summary>
/// Lock can not be opened, because bold is/ was blocked. Obstacle might no more block but lock could not be opened completely to obstacle.
/// </summary>
public class CouldntOpenBoldStatusIsUnknownException : StateAwareException
{
public CouldntOpenBoldStatusIsUnknownException() : base(
LockingState.UnknownFromHardwareError,
MultilingualResources.Resources.LockItExceptionOpenLockBoldStatusIsUnknown)
{
}
}
}

View file

@ -0,0 +1,16 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
using ShareeBike.MultilingualResources;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class CouldntOpenInconsistentStateExecption : StateAwareException
{
public CouldntOpenInconsistentStateExecption(LockingState state) :
base(
state,
string.Format(Resources.LockItExceptionOpenLockUnexpectedState, state))
{
}
}
}

View file

@ -0,0 +1,8 @@
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class CoundntGetCharacteristicException : System.Exception
{
public CoundntGetCharacteristicException(string message) : base(message)
{ }
}
}

View file

@ -0,0 +1,9 @@
namespace ShareeBike.Services.BluetoothLock.Exception
{
public class GuidUnknownException : System.Exception
{
public GuidUnknownException() : base("Can not connect to lock. No Guid available.")
{
}
}
}

View file

@ -0,0 +1,8 @@
namespace ShareeBike.Services.BluetoothLock.Exception
{
/// <summary> Thrown whenever lock is out of reach.</summary>
/// <remarks> If fired when scan for devices does not result in lock being found.</remarks>
public class OutOfReachException : System.Exception
{
}
}

View file

@ -0,0 +1,15 @@
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
namespace ShareeBike.Services.BluetoothLock.Exception
{
public abstract class StateAwareException : System.Exception
{
public StateAwareException(LockingState state, string description) : base(description)
{
State = state;
}
/// <summary> Holds the state reported by lock.</summary>
public LockingState State { get; }
}
}

View file

@ -0,0 +1,71 @@
using System;
using System.Threading.Tasks;
using ShareeBike.Services.BluetoothLock.Tdo;
namespace ShareeBike.Services.BluetoothLock
{
public enum DeviceState
{
Disconnected = 0,
Connecting = 1,
Connected = 2,
Limited = 3 /* Mactches Connecting? */
}
public interface ILockService
{
/// <summary> Reconnect to lock. </summary>
Task ReconnectAsync(
LockInfoAuthTdo authInfo,
TimeSpan connectTimeout);
/// <summary> Opens lock. </summary>
/// <returns> State of lock after performing open operation. </returns>
Task<LockitLockingState?> OpenAsync();
/// <summary> Closes lock. </summary>
/// <returns>State of lock after performing close operation.</returns>
Task<LockitLockingState?> CloseAsync();
string Name { get; }
Guid Guid { get; }
int Id { get; }
/// <summary> Gets the device state.</summary>
DeviceState? GetDeviceState();
/// <summary>
/// Gets the locking state.
/// </summary>
/// <param name="doWaitRetry">True if to wait and retry in case of failures. </param>
/// <returns></returns>
Task<LockInfoTdo> GetLockStateAsync(bool doWaitRetry = false);
Task<bool> SetSoundAsync(SoundSettings settings);
Task<bool> SetAlarmSettingsAsync(AlarmSettings settings);
Task<bool> GetIsAlarmOffAsync();
/// <summary>
/// Get info about lock firmware- hardware- and lock-version.
/// </summary>
/// <returns>Lock versions info.</returns>
Task<VersionInfoTdo> GetVersionInfoAsync();
/// <summary>
/// Holds info about lock firmware- hardware- and lock-version, null if info has not yet been read.
/// </summary>
VersionInfoTdo VersionInfo { get; }
Task SetIsAlarmOffAsync(bool isActivated);
/// <summary>Gets the battery percentage.</summary>
Task<double> GetBatteryPercentageAsync();
/// <summary> Disconnect from lock.</summary>
Task Disconnect();
}
}

View file

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock;
using ShareeBike.Services.BluetoothLock.Tdo;
namespace ShareeBike.Services.BluetoothLock
{
public interface ILocksService
{
/// <summary> Holds timeout values for series of connecting attempts to a lock or multiple locks. </summary>
ITimeOutProvider TimeOut { get; set; }
/// <summary> Gets lock info for all lock in reach./// </summary>
/// <param name="connectTimeout">Timeout for connect operation of a single lock.</param>
Task<IEnumerable<LockInfoTdo>> GetLocksStateAsync(
IEnumerable<LockInfoAuthTdo> locksInfo,
TimeSpan connectTimeout);
/// <summary> Connects to lock.</summary>
/// <param name="authInfo"> Info required to connect to lock.</param>
/// <param name="connectTimeout">Timeout for connect operation.</param>
Task<LockInfoTdo> ConnectAsync(
LockInfoAuthTdo authInfo,
TimeSpan connectTimeout);
/// <summary>Gets a lock by bike Id.</summary>
/// <param name="bikeId"></param>
/// <returns>Lock object</returns>
ILockService this[int bikeId] { get; }
/// <summary> Disconnects lock.</summary>
/// <param name="bikeId"> Id of lock to disconnect.</param>
/// <param name="bikeGuid"> Guid of lock to disconnect.</param>
/// <returns> State disconnected it lock is already disconneced or after successfully disconnecting.</returns>
Task<LockingState> DisconnectAsync(int bikeId, Guid bikeGuid);
}
/// <summary> Alarm types types. </summary>
/// <remarks> Api-doc v29.0 </remarks>
public enum AlarmSettings
{
SmallSensivity = 2,
SmallSensivitySilent = 3,
SmallSensitivityPrealarm = 22,
MediumSensivity = 4,
MediumSensivitySilent = 0x05,
MediumSensivityPrealarm = 0x24,
HightestSensivity = 0x10,
HightestSensivitySilent = 0x11,
HightestSensivityPrealarm = 0x30,
}
/// <summary> Sound types. </summary>
/// <remarks> Api-doc v29.0 </remarks>
public enum SoundSettings
{
OnLockingStartedWarn = 0x00,
Warn = 0x01,
LockingStarted = 0x02,
AllOff = 0x03, // Mute
OpenedSuccessfully = 0x04,
LockingStartedOpenedSuccessfully = 0x05,
AllOn = 0x06, // All sounds on
OpenedSuccessfullyWarn = 0x07,
}
}

View file

@ -0,0 +1,11 @@
using System;
namespace ShareeBike.Services.BluetoothLock
{
public interface ITimeOutProvider
{
TimeSpan MultiConnect { get; }
TimeSpan GetSingleConnect(int countOfTry);
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Collections.Generic;
using System.Linq;
using ShareeBike.Services.BluetoothLock.Tdo;
namespace ShareeBike.Model.Bikes.BikeInfoNS.BluetoothLock
{
public static class LockInfoHelper
{
/// <summary> Update by id.</summary>
/// <param name="locksInfo"> Lock info objects to update by id.</param>
/// <param name="locksInfoTdo">Tdos holding data to updat from</param>
/// <returns></returns>
public static IEnumerable<LockInfo> UpdateById(
this IEnumerable<LockInfo> locksInfo,
IEnumerable<LockInfoTdo> locksInfoTdo)
{
var locksInfoUpdated = new List<LockInfo>();
foreach (var lockInfo in locksInfo)
{
var lockInfoTdo = locksInfoTdo.FirstOrDefault(x => x.Id == lockInfo.Id);
if (lockInfoTdo == null)
{
// No object to update from found.
locksInfoUpdated.Add(lockInfo);
continue;
}
var state = lockInfoTdo.State.HasValue ? lockInfoTdo.State.Value.GetLockingState() : LockingState.UnknownDisconnected;
locksInfoUpdated.Add(state != lockInfo.State || lockInfoTdo.Guid != lockInfo.Guid
? new LockInfo.Builder(lockInfo) { Guid = lockInfoTdo.Guid, State = state }.Build() // State has changed, update required.
: lockInfo);
}
return locksInfoUpdated;
}
public static LockingState GetLockingState(this LockitLockingState lockingState)
{
switch (lockingState)
{
case LockitLockingState.Unknown:
case LockitLockingState.CouldntOpenBoltBlocked: // Lock is closed in most cases, but this is not guaranteed according to haveltec.
return LockingState.UnknownFromHardwareError;
case LockitLockingState.Open:
case LockitLockingState.CouldntCloseMoving:
case LockitLockingState.CouldntCloseBoltBlocked:
return LockingState.Open;
case LockitLockingState.Closed:
return LockingState.Closed;
}
throw new ArgumentException($"Can not convert LockIt specific locking state to logical locking state. Unknown state {lockingState} detected.");
}
/// <summary> Gets one type from another.</summary>
/// <param name="lockInfo"></param>
/// <returns></returns>
public static LockInfoAuthTdo ToLockInfoTdo(this LockInfo lockInfo)
{
return new LockInfoAuthTdo.Builder() { Id = lockInfo.Id, Guid = lockInfo.Guid, K_u = lockInfo.UserKey, K_a = lockInfo.AdminKey, K_seed = lockInfo.Seed }.Build();
}
}
}

View file

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
namespace LockIt.BusinessLogic.Services.BluetoothLock
{
public static class LockItByGuidServiceHelper
{
/// <summary>
/// Holds guids of developent locks to ensure that no wrong guid are provided by COPRI
/// </summary>
public static Dictionary<int, Guid> DevelGuids = new Dictionary<int, Guid>
{
{ 2200537, new Guid("00000000-0000-0000-0000-d589a8023487") },
{ 2200543, new Guid("00000000-0000-0000-0000-cc141a6f68bb") },
{ 2200544, new Guid("00000000-0000-0000-0000-dc969f648732") },
{ 2200545, new Guid("00000000-0000-0000-0000-e38bf9d32234") }
};
}
}

View file

@ -0,0 +1,70 @@
using System;
using System.Threading.Tasks;
using ShareeBike.Services.BluetoothLock.Tdo;
namespace ShareeBike.Services.BluetoothLock
{
/// <summary>
/// Represents a not exisitng lock.
/// </summary>
public class NullLock : ILockService
{
private int BikeId { get; }
public NullLock(int bikeId)
{
BikeId = bikeId;
}
public Task ReconnectAsync(LockInfoAuthTdo authInfo, TimeSpan connectTimeout) =>
throw new NotImplementedException();
public string Name =>
throw new NotImplementedException();
public Guid Guid =>
throw new NotImplementedException();
public int Id =>
throw new NotImplementedException();
public async Task<LockitLockingState?> OpenAsync() =>
await Task.FromResult((LockitLockingState?)null);
public async Task<LockitLockingState?> CloseAsync() =>
await Task.FromResult((LockitLockingState?)null);
public Task<double> GetBatteryPercentageAsync() =>
throw new System.Exception($"Can not get battery percentage. Lock {BikeId} not found.");
public Task<VersionInfoTdo> GetVersionInfoAsync() =>
throw new System.Exception($"Can not get versions. Lock {BikeId} not found.");
public VersionInfoTdo VersionInfo
{
get => throw new System.Exception($"Can not get versions. Lock {BikeId} not found.");
}
public DeviceState? GetDeviceState() =>
throw new NotImplementedException();
public Task<bool> GetIsAlarmOffAsync() =>
throw new System.Exception($"Can not get whether alarm is on or off. Lock {BikeId} not found.");
public Task<LockInfoTdo> GetLockStateAsync(bool doWaitRetry = false) =>
throw new NotImplementedException();
public Task SetIsAlarmOffAsync(bool isActivated) =>
throw new System.Exception($"Can not set alarm {isActivated}. Lock {BikeId} not found.");
public async Task<bool> SetSoundAsync(SoundSettings settings) =>
await Task.FromResult(false);
public async Task<bool> SetAlarmSettingsAsync(AlarmSettings settings) =>
await Task.FromResult(false);
/// <summary> Disconnect from bluetooth lock. </summary>
public Task Disconnect() =>
throw new NotImplementedException();
}
}

View file

@ -0,0 +1,61 @@
using System;
using ShareeBike.Model.Connector;
namespace ShareeBike.Services.BluetoothLock.Tdo
{
/// <summary> Data required to connect to a blutooth lock.</summary>
public class LockInfoAuthTdo
{
/// <summary> Identification number of bluetooth lock, 6-digits, second part of advertisement name.</summary>
public int Id { get; private set; }
/// <summary> GUID to the device. </summary>
public Guid Guid { get; private set; }
/// <summary> Seed used to generate key for connecting to bluetooth lock.</summary>
public byte[] K_seed { get; private set; }
/// <summary> Key for connect to bluetooth lock as user.</summary>
public byte[] K_u { get; private set; }
/// <summary> Key for connect to bluetooth lock as admin.</summary>
public byte[] K_a { get; private set; }
public bool IsIdValid => Id != TextToLockItTypeHelper.INVALIDLOCKID;
public bool IsGuidValid => Guid != TextToLockItTypeHelper.INVALIDLOCKGUID;
public class Builder
{
private LockInfoAuthTdo lockInfoTdo = new LockInfoAuthTdo();
public Builder() { }
/// <summary> Identification number of bluetooth lock, 6-digits, second part of advertisement name.</summary>
public int Id { get => lockInfoTdo.Id; set { lockInfoTdo.Id = value; } }
/// <summary> GUID to the device. </summary>
public Guid Guid { get => lockInfoTdo.Guid; set { lockInfoTdo.Guid = value; } }
/// <summary> Seed used to generate key for connecting to bluetooth lock.</summary>
public byte[] K_seed { get => lockInfoTdo.K_seed; set { lockInfoTdo.K_seed = value; } }
/// <summary> Key for connect to bluetooth lock as user.</summary>
public byte[] K_u { get => lockInfoTdo.K_u; set { lockInfoTdo.K_u = value; } }
/// <summary> Key for connect to bluetooth lock as admin.</summary>
public byte[] K_a { get => lockInfoTdo.K_a; set { lockInfoTdo.K_a = value; } }
public LockInfoAuthTdo Build()
{
if (K_seed == null) K_seed = new byte[0];
if (K_u == null) K_u = new byte[0];
if (K_a == null) K_a = new byte[0];
return lockInfoTdo;
}
}
}
}

View file

@ -0,0 +1,54 @@
using System;
namespace ShareeBike.Services.BluetoothLock.Tdo
{
/// <summary> Genuine ILOCKIT state.</summary>
public enum LockitLockingState
{
Open = 0x00,
Closed = 0x01,
Unknown = 0x02,
CouldntCloseMoving = 0x03,
CouldntOpenBoltBlocked = 0x04,
CouldntCloseBoltBlocked = 0x05
}
/// <summary> Object holding info about bluetooth lock. </summary>
public class LockInfoTdo
{
private LockInfoTdo() { }
/// <summary> Identification number of bluetooth lock, 6-digits, second part of advertisement name.</summary>
public int Id { get; private set; }
/// <summary> Guid for direct connect.</summary>
public Guid Guid { get; private set; }
/// <summary> Gets the current state of the lock.</summary>
public LockitLockingState? State { get; private set; }
public class Builder
{
private LockInfoTdo lockInfoTdo = new LockInfoTdo();
/// <summary> Identification number of bluetooth lock, 6-digits, second part of advertisement name.</summary>
public int Id { get => lockInfoTdo.Id; set => lockInfoTdo.Id = value; }
/// <summary> Guid for direct connect.</summary>
public Guid Guid { get => lockInfoTdo.Guid; set => lockInfoTdo.Guid = value; }
/// <summary> Gets the current state of the lock.</summary>
public LockitLockingState? State { get => lockInfoTdo.State; set => lockInfoTdo.State = value; }
public LockInfoTdo Build()
{
return lockInfoTdo;
}
}
}
}

View file

@ -0,0 +1,41 @@
namespace ShareeBike.Services.BluetoothLock.Tdo
{
public class VersionInfoTdo
{
/// <summary>
/// Holds info about firmware- and hardware version of a lock and the type of lock (lock version).
/// </summary>
private VersionInfoTdo() { }
/// <summary>
/// Holds the firmware version of the lock.
/// </summary>
public int FirmwareVersion { get; private set; }
/// <summary>
/// Holds the hardware version (revision) of the lock.
/// </summary>
public int HardwareVersion { get; private set; }
/// <summary>
/// Holds lock version (2 classic, 3 plus, 4 GPS).
/// </summary>
public int LockVersion { get; private set; }
public class Builder
{
private VersionInfoTdo lockVersionTdo = new VersionInfoTdo();
public int FirmwareVersion { get => lockVersionTdo.FirmwareVersion; set => lockVersionTdo.FirmwareVersion = value; }
public int HardwareVersion { get => lockVersionTdo.HardwareVersion; set => lockVersionTdo.HardwareVersion = value; }
public int LockVersion { get => lockVersionTdo.LockVersion; set => lockVersionTdo.LockVersion = value; }
public VersionInfoTdo Build()
{
return lockVersionTdo;
}
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace ShareeBike.Services.BluetoothLock
{
public class TimeOutProvider : ITimeOutProvider
{
/// <summary> Maximum factor applied on timeout factor. </summary>
private readonly int MAXIMUMFACTORTIMEOUT = 4;
/// <summary> Default timeout to connect to bluetooth lock. </summary>
public static int DEFAULT_BLUETOOTHCONNECT_TIMEOUTSECONDS = 5;
public TimeOutProvider(IEnumerable<TimeSpan> timeOuts = null)
{
TimeOuts = timeOuts != null && timeOuts.Count() > 0
? timeOuts.ToList()
: new List<TimeSpan> { new TimeSpan(0, 0, DEFAULT_BLUETOOTHCONNECT_TIMEOUTSECONDS) };
}
private List<TimeSpan> TimeOuts { get; }
public TimeSpan MultiConnect => TimeOuts.ToArray()[0];
public TimeSpan GetSingleConnect(int countOfTry) => countOfTry < TimeOuts.Count
? TimeOuts.ToArray()[countOfTry]
: new TimeSpan(TimeOuts.ToArray()[0].Ticks * Math.Min(countOfTry, MAXIMUMFACTORTIMEOUT));
}
}