commit 8a55b82509a6824cbeb347204c6b502a52586b51 Author: jkpete <1031139173@qq.com> Date: Sun Dec 14 09:30:11 2025 +0800 init proj diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b592f70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +#EGSave-Specific ignores +SaveData/ + +#Csharp Build +bin/ + +#CSharp Cache +obj/ + +# Web +# wwwroot/ +*.pck +*.wasm + + diff --git a/Components/App.razor b/Components/App.razor new file mode 100644 index 0000000..cba4efa --- /dev/null +++ b/Components/App.razor @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/Components/Layout/MainLayout.razor b/Components/Layout/MainLayout.razor new file mode 100644 index 0000000..5a24bb1 --- /dev/null +++ b/Components/Layout/MainLayout.razor @@ -0,0 +1,23 @@ +@inherits LayoutComponentBase + +
+ + +
+
+ About +
+ +
+ @Body +
+
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
diff --git a/Components/Layout/MainLayout.razor.css b/Components/Layout/MainLayout.razor.css new file mode 100644 index 0000000..038baf1 --- /dev/null +++ b/Components/Layout/MainLayout.razor.css @@ -0,0 +1,96 @@ +.page { + position: relative; + display: flex; + flex-direction: column; +} + +main { + flex: 1; +} + +.sidebar { + background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); +} + +.top-row { + background-color: #f7f7f7; + border-bottom: 1px solid #d6d5d5; + justify-content: flex-end; + height: 3.5rem; + display: flex; + align-items: center; +} + + .top-row ::deep a, .top-row ::deep .btn-link { + white-space: nowrap; + margin-left: 1.5rem; + text-decoration: none; + } + + .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { + text-decoration: underline; + } + + .top-row ::deep a:first-child { + overflow: hidden; + text-overflow: ellipsis; + } + +@media (max-width: 640.98px) { + .top-row { + justify-content: space-between; + } + + .top-row ::deep a, .top-row ::deep .btn-link { + margin-left: 0; + } +} + +@media (min-width: 641px) { + .page { + flex-direction: row; + } + + .sidebar { + width: 250px; + height: 100vh; + position: sticky; + top: 0; + } + + .top-row { + position: sticky; + top: 0; + z-index: 1; + } + + .top-row.auth ::deep a:first-child { + flex: 1; + text-align: right; + width: 0; + } + + .top-row, article { + padding-left: 2rem !important; + padding-right: 1.5rem !important; + } +} + +#blazor-error-ui { + background: lightyellow; + bottom: 0; + box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); + display: none; + left: 0; + padding: 0.6rem 1.25rem 0.7rem 1.25rem; + position: fixed; + width: 100%; + z-index: 1000; +} + + #blazor-error-ui .dismiss { + cursor: pointer; + position: absolute; + right: 0.75rem; + top: 0.5rem; + } diff --git a/Components/Layout/NavMenu.razor b/Components/Layout/NavMenu.razor new file mode 100644 index 0000000..91b07fe --- /dev/null +++ b/Components/Layout/NavMenu.razor @@ -0,0 +1,30 @@ + + + + + + diff --git a/Components/Layout/NavMenu.razor.css b/Components/Layout/NavMenu.razor.css new file mode 100644 index 0000000..4e15395 --- /dev/null +++ b/Components/Layout/NavMenu.razor.css @@ -0,0 +1,105 @@ +.navbar-toggler { + appearance: none; + cursor: pointer; + width: 3.5rem; + height: 2.5rem; + color: white; + position: absolute; + top: 0.5rem; + right: 1rem; + border: 1px solid rgba(255, 255, 255, 0.1); + background: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") no-repeat center/1.75rem rgba(255, 255, 255, 0.1); +} + +.navbar-toggler:checked { + background-color: rgba(255, 255, 255, 0.5); +} + +.top-row { + height: 3.5rem; + background-color: rgba(0,0,0,0.4); +} + +.navbar-brand { + font-size: 1.1rem; +} + +.bi { + display: inline-block; + position: relative; + width: 1.25rem; + height: 1.25rem; + margin-right: 0.75rem; + top: -1px; + background-size: cover; +} + +.bi-house-door-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-house-door-fill' viewBox='0 0 16 16'%3E%3Cpath d='M6.5 14.5v-3.505c0-.245.25-.495.5-.495h2c.25 0 .5.25.5.5v3.5a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5v-7a.5.5 0 0 0-.146-.354L13 5.793V2.5a.5.5 0 0 0-.5-.5h-1a.5.5 0 0 0-.5.5v1.293L8.354 1.146a.5.5 0 0 0-.708 0l-6 6A.5.5 0 0 0 1.5 7.5v7a.5.5 0 0 0 .5.5h4a.5.5 0 0 0 .5-.5Z'/%3E%3C/svg%3E"); +} + +.bi-plus-square-fill-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-plus-square-fill' viewBox='0 0 16 16'%3E%3Cpath d='M2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2zm6.5 4.5v3h3a.5.5 0 0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3a.5.5 0 0 1 1 0z'/%3E%3C/svg%3E"); +} + +.bi-list-nested-nav-menu { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' fill='white' class='bi bi-list-nested' viewBox='0 0 16 16'%3E%3Cpath fill-rule='evenodd' d='M4.5 11.5A.5.5 0 0 1 5 11h10a.5.5 0 0 1 0 1H5a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 3 7h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm-2-4A.5.5 0 0 1 1 3h10a.5.5 0 0 1 0 1H1a.5.5 0 0 1-.5-.5z'/%3E%3C/svg%3E"); +} + +.nav-item { + font-size: 0.9rem; + padding-bottom: 0.5rem; +} + + .nav-item:first-of-type { + padding-top: 1rem; + } + + .nav-item:last-of-type { + padding-bottom: 1rem; + } + + .nav-item ::deep .nav-link { + color: #d7d7d7; + background: none; + border: none; + border-radius: 4px; + height: 3rem; + display: flex; + align-items: center; + line-height: 3rem; + width: 100%; + } + +.nav-item ::deep a.active { + background-color: rgba(255,255,255,0.37); + color: white; +} + +.nav-item ::deep .nav-link:hover { + background-color: rgba(255,255,255,0.1); + color: white; +} + +.nav-scrollable { + display: none; +} + +.navbar-toggler:checked ~ .nav-scrollable { + display: block; +} + +@media (min-width: 641px) { + .navbar-toggler { + display: none; + } + + .nav-scrollable { + /* Never collapse the sidebar for wide screens */ + display: block; + + /* Allow sidebar to scroll for tall menus */ + height: calc(100vh - 3.5rem); + overflow-y: auto; + } +} diff --git a/Components/Pages/Counter.razor b/Components/Pages/Counter.razor new file mode 100644 index 0000000..1a4f8e7 --- /dev/null +++ b/Components/Pages/Counter.razor @@ -0,0 +1,19 @@ +@page "/counter" +@rendermode InteractiveServer + +Counter + +

Counter

+ +

Current count: @currentCount

+ + + +@code { + private int currentCount = 0; + + private void IncrementCount() + { + currentCount++; + } +} diff --git a/Components/Pages/Error.razor b/Components/Pages/Error.razor new file mode 100644 index 0000000..576cc2d --- /dev/null +++ b/Components/Pages/Error.razor @@ -0,0 +1,36 @@ +@page "/Error" +@using System.Diagnostics + +Error + +

Error.

+

An error occurred while processing your request.

+ +@if (ShowRequestId) +{ +

+ Request ID: @RequestId +

+} + +

Development Mode

+

+ Swapping to Development environment will display more detailed information about the error that occurred. +

+

+ The Development environment shouldn't be enabled for deployed applications. + It can result in displaying sensitive information from exceptions to end users. + For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development + and restarting the app. +

+ +@code{ + [CascadingParameter] + private HttpContext? HttpContext { get; set; } + + private string? RequestId { get; set; } + private bool ShowRequestId => !string.IsNullOrEmpty(RequestId); + + protected override void OnInitialized() => + RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier; +} diff --git a/Components/Pages/Home.razor b/Components/Pages/Home.razor new file mode 100644 index 0000000..9001e0b --- /dev/null +++ b/Components/Pages/Home.razor @@ -0,0 +1,7 @@ +@page "/" + +Home + +

Hello, world!

+ +Welcome to your new app. diff --git a/Components/Pages/Weather.razor b/Components/Pages/Weather.razor new file mode 100644 index 0000000..43a1ecb --- /dev/null +++ b/Components/Pages/Weather.razor @@ -0,0 +1,64 @@ +@page "/weather" +@attribute [StreamRendering] + +Weather + +

Weather

+ +

This component demonstrates showing data.

+ +@if (forecasts == null) +{ +

Loading...

+} +else +{ + + + + + + + + + + + @foreach (var forecast in forecasts) + { + + + + + + + } + +
DateTemp. (C)Temp. (F)Summary
@forecast.Date.ToShortDateString()@forecast.TemperatureC@forecast.TemperatureF@forecast.Summary
+} + +@code { + private WeatherForecast[]? forecasts; + + protected override async Task OnInitializedAsync() + { + // Simulate asynchronous loading to demonstrate streaming rendering + await Task.Delay(500); + + var startDate = DateOnly.FromDateTime(DateTime.Now); + var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; + forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = startDate.AddDays(index), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = summaries[Random.Shared.Next(summaries.Length)] + }).ToArray(); + } + + private class WeatherForecast + { + public DateOnly Date { get; set; } + public int TemperatureC { get; set; } + public string? Summary { get; set; } + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + } +} diff --git a/Components/Routes.razor b/Components/Routes.razor new file mode 100644 index 0000000..f756e19 --- /dev/null +++ b/Components/Routes.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/Components/_Imports.razor b/Components/_Imports.razor new file mode 100644 index 0000000..19ea864 --- /dev/null +++ b/Components/_Imports.razor @@ -0,0 +1,10 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using static Microsoft.AspNetCore.Components.Web.RenderMode +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.JSInterop +@using TargetService +@using TargetService.Components diff --git a/EGFramework/EGFramework.cs b/EGFramework/EGFramework.cs new file mode 100644 index 0000000..5c21082 --- /dev/null +++ b/EGFramework/EGFramework.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework +{ + #region Architecture & Module + public class EGArchitecture : IArchitecture where T : EGArchitecture, new() + { + private static T Architecture; + public static IArchitecture Interface + { + get + { + if (Architecture == null) + { + MakeSureArchitecture(); + } + return Architecture; + } + } + + private static void MakeSureArchitecture() + { + if (Architecture == null) + { + Architecture = new T(); + Architecture.Init(); + } + } + + protected virtual void Init() + { + + } + + private IOCContainer ModuleContainer = new IOCContainer(); + + public void RegisterModule(TModule module) where TModule : IModule + { + ModuleContainer.Register(module); + module.Init(); + } + public TModule GetModule() where TModule : class, IModule,new() + { + if (!ModuleContainer.self.ContainsKey(typeof(TModule))) + { + this.RegisterModule(new TModule()); + } + return ModuleContainer.Get(); + } + public bool IsInitModule() where TModule : class, IModule,new() + { + if (!ModuleContainer.self.ContainsKey(typeof(TModule))) + { + return true; + }else{ + return false; + } + } + } + + public abstract class EGModule:IModule{ + IArchitecture IBelongToArchitecture.GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + void IModule.Init() + { + this.Init(); + } + public abstract void Init(); + } + #endregion + + #region Interface + public interface IArchitecture + { + void RegisterModule(T model) where T : IModule; + T GetModule() where T : class, IModule,new(); + bool IsInitModule() where T : class, IModule,new(); + } + public interface IModule : IBelongToArchitecture + { + void Init(); + } + public interface IBelongToArchitecture + { + IArchitecture GetArchitecture(); + } + #endregion + + #region IOC + public class IOCContainer + { + private Dictionary Instances = new Dictionary(); + public void Register(T instance) + { + var key = typeof(T); + if (Instances.ContainsKey(key)) + { + Instances[key] = instance; + } + else + { + Instances.Add(key, instance); + } + } + public T Get() where T : class + { + var key = typeof(T); + if (Instances.TryGetValue(key, out var retInstance)) + { + return retInstance as T; + } + return null; + } + public Dictionary self => Instances; + } + #endregion + + #region Event + public interface IEasyEvent { + + } + public interface IUnRegister + { + void UnRegister(); + } + + public class EasyEvent : IEasyEvent + { + private Action OnEvent = e => { }; + public IUnRegister Register(Action onEvent) + { + OnEvent += onEvent; + return new CustomUnRegister(() => { UnRegister(onEvent); }); + } + public void UnRegister(Action onEvent) + { + OnEvent -= onEvent; + } + public void Invoke(T t) + { + OnEvent?.Invoke(t); + } + } + + public class EasyEvent : IEasyEvent + { + private Action OnEvent = () => { }; + public IUnRegister Register(Action onEvent) + { + OnEvent += onEvent; + return new CustomUnRegister(() => { UnRegister(onEvent); }); + } + public void UnRegister(Action onEvent) + { + OnEvent -= onEvent; + } + public void Invoke() + { + OnEvent?.Invoke(); + } + } + public struct CustomUnRegister : IUnRegister + { + /// + /// delegate object + /// + private Action OnUnRegister { get; set; } + + public CustomUnRegister(Action onUnRegister) + { + OnUnRegister = onUnRegister; + } + /// + /// release by parent; + /// + public void UnRegister() + { + OnUnRegister.Invoke(); + OnUnRegister = null; + } + } + #endregion + + #region FrameworkExtension + public interface IEGFramework{} + + public class EGArchitectureImplement:EGArchitecture{ + protected override void Init() + { + //base.Init(); + } + } + + public static class EGArchitectureImplementExtension{ + public static T GetModule(this IEGFramework self) where T : class, IModule,new() + { + return EGArchitectureImplement.Interface.GetModule(); + } + public static void RegisterModule(this IEGFramework self,T model) where T : class, IModule,new() + { + EGArchitectureImplement.Interface.RegisterModule(model); + } + } + #endregion +} diff --git a/EGFramework/EGPlatform.cs b/EGFramework/EGPlatform.cs new file mode 100644 index 0000000..70f6321 --- /dev/null +++ b/EGFramework/EGPlatform.cs @@ -0,0 +1,48 @@ +using System; +using System.Diagnostics; +namespace EGFramework{ + + public interface IPlatform{ + void Log(string message); + void Log(params object[] what); + } + // public class EGPlatformGodot : IPlatform{ + // public void Log(string message){ + // Godot.GD.Print(message); + // // Console.WriteLine(message); + // } + // public void Log(params object[] what){ + // Godot.GD.Print(what); + // // Console.WriteLine(what); + // } + // } + // if not use please explain this + public class EGPlatformDotnet : IPlatform{ + public void Log(string message){ + Console.WriteLine(message); + } + public void Log(params object[] what){ + Console.WriteLine(what); + } + } + public static class EG + { + public static EGPlatformDotnet Platform = new EGPlatformDotnet(); + public static void Print(string message){ + Platform.Log(message); + } + public static void Print(params object[] what){ + Platform.Log(what); + } + + } + + // public enum SupportPlatform{ + // Godot = 0x01, + // Unity = 0x02, + // WebApi = 0x03, + // WPF = 0x04, + // Form = 0x05, + // } + +} diff --git a/EGFramework/License_Third_Part/BACnet/MIT_license.txt b/EGFramework/License_Third_Part/BACnet/MIT_license.txt new file mode 100644 index 0000000..8c308cf --- /dev/null +++ b/EGFramework/License_Third_Part/BACnet/MIT_license.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014 Yabe project + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/EGFramework/License_Third_Part/Dapper/License.txt b/EGFramework/License_Third_Part/Dapper/License.txt new file mode 100644 index 0000000..aa78493 --- /dev/null +++ b/EGFramework/License_Third_Part/Dapper/License.txt @@ -0,0 +1,6 @@ +The Dapper library and tools are licenced under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +The Dapper logo is copyright Marc Gravell 2021 onwards; it is fine to use the Dapper logo when referencing the Dapper library and utilities, but +the Dapper logo (including derivatives) must not be used in a way that misrepresents an external product or library as being affiliated or endorsed +with Dapper. For example, you must not use the Dapper logo as the package icon on your own external tool (even if it uses Dapper internally), +without written permission. If in doubt: ask. \ No newline at end of file diff --git a/EGFramework/License_Third_Part/Dotnet/LICENSE.txt b/EGFramework/License_Third_Part/Dotnet/LICENSE.txt new file mode 100644 index 0000000..a616ed1 --- /dev/null +++ b/EGFramework/License_Third_Part/Dotnet/LICENSE.txt @@ -0,0 +1,23 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors + +All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/EGFramework/License_Third_Part/FluentFTP/LICENSE.TXT b/EGFramework/License_Third_Part/FluentFTP/LICENSE.TXT new file mode 100644 index 0000000..93d30b1 --- /dev/null +++ b/EGFramework/License_Third_Part/FluentFTP/LICENSE.TXT @@ -0,0 +1,7 @@ +Copyright (c) 2015 Robin Rodricks and FluentFTP Contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/EGFramework/License_Third_Part/LiteDB/LICENSE.txt b/EGFramework/License_Third_Part/LiteDB/LICENSE.txt new file mode 100644 index 0000000..08610a1 --- /dev/null +++ b/EGFramework/License_Third_Part/LiteDB/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2014-2022 Mauricio David + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/EGFramework/License_Third_Part/MQTTnet/LICENSE b/EGFramework/License_Third_Part/MQTTnet/LICENSE new file mode 100644 index 0000000..d18aef9 --- /dev/null +++ b/EGFramework/License_Third_Part/MQTTnet/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) .NET Foundation and Contributors +All Rights Reserved + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/EGFramework/License_Third_Part/Makaretu_Dns_Multicast/LICENSE.txt b/EGFramework/License_Third_Part/Makaretu_Dns_Multicast/LICENSE.txt new file mode 100644 index 0000000..61d5419 --- /dev/null +++ b/EGFramework/License_Third_Part/Makaretu_Dns_Multicast/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Richard Schneider + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/EGFramework/License_Third_Part/Mysql_Data/Notice.txt b/EGFramework/License_Third_Part/Mysql_Data/Notice.txt new file mode 100644 index 0000000..4636597 --- /dev/null +++ b/EGFramework/License_Third_Part/Mysql_Data/Notice.txt @@ -0,0 +1,3 @@ +GPL-2.0-only license licenseWITH license licenseUniversal-FOSS-exception-1.0 license +https://licenses.nuget.org/GPL-2.0-only +https://licenses.nuget.org/Universal-FOSS-exception-1.0 \ No newline at end of file diff --git a/EGFramework/License_Third_Part/NewtonSoft_Json/LICENSE.md b/EGFramework/License_Third_Part/NewtonSoft_Json/LICENSE.md new file mode 100644 index 0000000..dfaadbe --- /dev/null +++ b/EGFramework/License_Third_Part/NewtonSoft_Json/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2007 James Newton-King + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/EGFramework/License_Third_Part/QFramework/LICENSE b/EGFramework/License_Third_Part/QFramework/LICENSE new file mode 100644 index 0000000..2db0583 --- /dev/null +++ b/EGFramework/License_Third_Part/QFramework/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 凉鞋 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/EGFramework/License_Third_Part/StackExchange_Redis/LICENSE.txt b/EGFramework/License_Third_Part/StackExchange_Redis/LICENSE.txt new file mode 100644 index 0000000..db4620c --- /dev/null +++ b/EGFramework/License_Third_Part/StackExchange_Redis/LICENSE.txt @@ -0,0 +1,47 @@ +The MIT License (MIT) + +Copyright (c) 2014 Stack Exchange + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +=============================================== + +Third Party Licenses: + +The Redis project (https://redis.io/) is independent of this client library, and +is licensed separately under the three clause BSD license. The full license +information can be viewed here: https://redis.io/topics/license + +This tool makes use of the "redis-doc" library from https://redis.io/documentation +in the intellisense comments, which is licensed under the +Creative Commons Attribution-ShareAlike 4.0 International license; full +details are available here: +https://github.com/antirez/redis-doc/blob/master/COPYRIGHT + +The development solution uses the Redis-64 package from nuget +(https://www.nuget.org/packages/Redis-64) by Microsoft Open Technologies, inc. +This is licensed under the BSD license; full details are available here: +https://github.com/MSOpenTech/redis/blob/2.6/license.txt +This tool is not used in the release binaries. + +The development solution uses the BookSleeve package from nuget +(https://code.google.com/p/booksleeve/) by Marc Gravell. This is licensed +under the Apache 2.0 license; full details are available here: +https://www.apache.org/licenses/LICENSE-2.0 +This tool is not used in the release binaries. \ No newline at end of file diff --git a/EGFramework/License_Third_Part/WebDavClient/LICENSE.txt b/EGFramework/License_Third_Part/WebDavClient/LICENSE.txt new file mode 100644 index 0000000..9e41efb --- /dev/null +++ b/EGFramework/License_Third_Part/WebDavClient/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 Sergey Kazantsev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/EGFramework/License_Third_Part/source_han_sans/LICENSE.txt b/EGFramework/License_Third_Part/source_han_sans/LICENSE.txt new file mode 100644 index 0000000..ddf7b7e --- /dev/null +++ b/EGFramework/License_Third_Part/source_han_sans/LICENSE.txt @@ -0,0 +1,96 @@ +Copyright 2014-2021 Adobe (http://www.adobe.com/), with Reserved Font +Name 'Source'. Source is a trademark of Adobe in the United States +and/or other countries. + +This Font Software is licensed under the SIL Open Font License, +Version 1.1. + +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font +creation efforts of academic and linguistic communities, and to +provide a free and open framework in which fonts may be shared and +improved in partnership with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply to +any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software +components as distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, +deleting, or substituting -- in part or in whole -- any of the +components of the Original Version, by changing formats or by porting +the Font Software to a new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, +modify, redistribute, and sell modified and unmodified copies of the +Font Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, in +Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the +corresponding Copyright Holder. This restriction only applies to the +primary font name as presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created using +the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/EGFramework/Module/EGCQRS.cs b/EGFramework/Module/EGCQRS.cs new file mode 100644 index 0000000..fdac1bb --- /dev/null +++ b/EGFramework/Module/EGCQRS.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework +{ + #region Interface + public interface ICommand + { + void Execute(); + } + public interface IQuery + { + TResult Do(); + } + + public interface IEGCQRS + { + void SendCommand(ICommand command); + TResult DoQuery(IQuery query); + } + #endregion + + public class EGCQRS :EGModule, IEGCQRS + { + public void SendCommand(ICommand command) + { + command.Execute(); + } + public TResult DoQuery(IQuery query) + { + return query.Do(); + } + public override void Init() + { + + } + } + + #region Extension + public static class CanSendCommandExtension + { + public static void EGSendCommand(this IEGFramework self, ICommand command) + { + EGArchitectureImplement.Interface.GetModule().SendCommand(command); + } + } + + public static class CanQueryDataExtension + { + public static TResult EGQueryData(this IEGFramework self, IQuery query) + { + return EGArchitectureImplement.Interface.GetModule().DoQuery(query); + } + } + #endregion +} \ No newline at end of file diff --git a/EGFramework/Module/EGEvent.cs b/EGFramework/Module/EGEvent.cs new file mode 100644 index 0000000..8f5f477 --- /dev/null +++ b/EGFramework/Module/EGEvent.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework +{ + public interface IEGEvent{ + void SendEvent() where T : new(); + void SendEvent(T e); + IUnRegister RegisterEvent(Action onEvent); + void UnRegisterEvent(Action onEvent); + } + public class EGEvent : EGModule,IEGEvent + { + public override void Init() + { + + } + private readonly EasyEvents Events = new EasyEvents(); + public void SendEvent() where TEvent : new() + { + Events.GetEvent>()?.Invoke(new TEvent()); + } + + public void SendEvent(TEvent e) + { + Events.GetEvent>()?.Invoke(e); + } + + public IUnRegister RegisterEvent(Action onEvent) + { + var e = Events.GetOrAddEvent>(); + return e.Register(onEvent); + } + + public void UnRegisterEvent(Action onEvent) + { + var e = Events.GetEvent>(); + if (e != null) + { + e.UnRegister(onEvent); + } + } + } + + public class EasyEvents + { + private static EasyEvents GlobalEvents = new EasyEvents(); + public static T Get() where T : IEasyEvent + { + return GlobalEvents.GetEvent(); + } + public static void Register() where T : IEasyEvent, new() + { + GlobalEvents.AddEvent(); + } + private Dictionary TypeEvents = new Dictionary(); + public void AddEvent() where T : IEasyEvent, new() + { + TypeEvents.Add(typeof(T), new T()); + } + public T GetEvent() where T : IEasyEvent + { + IEasyEvent e; + if (TypeEvents.TryGetValue(typeof(T), out e)) + { + return (T)e; + } + return default; + } + public T GetOrAddEvent() where T : IEasyEvent, new() + { + var eType = typeof(T); + if (TypeEvents.TryGetValue(eType, out var e)) + { + return (T)e; + } + var t = new T(); + TypeEvents.Add(eType, t); + return t; + } + } + + /// + /// This EasyEvent will release all registered function while invoked. + /// + /// + public class EasyEventOnce : IEasyEvent + { + private Action OnEvent = e => { }; + private List AutoUnRegister = new List(); + public IUnRegister Register(Action onEvent) + { + OnEvent += onEvent; + CustomUnRegister unRegister = new CustomUnRegister(() => { UnRegister(onEvent); }); + AutoUnRegister.Add(unRegister); + return unRegister; + } + public void UnRegister(Action onEvent) + { + OnEvent -= onEvent; + } + public void Invoke(T t) + { + if(AutoUnRegister.Count>0){ + OnEvent?.Invoke(t); + foreach(CustomUnRegister unRegister in AutoUnRegister){ + unRegister.UnRegister(); + } + AutoUnRegister.Clear(); + } + } + } + + /// + /// This EasyEvent will release all registered function while invoked. + /// + public class EasyEventOnce : IEasyEvent{ + private Action OnEvent = () => { }; + private List AutoUnRegister = new List(); + public IUnRegister Register(Action onEvent) + { + OnEvent += onEvent; + CustomUnRegister unRegister = new CustomUnRegister(() => { UnRegister(onEvent); }); + AutoUnRegister.Add(unRegister); + return unRegister; + } + public void UnRegister(Action onEvent) + { + OnEvent -= onEvent; + } + public void Invoke() + { + if(AutoUnRegister.Count>0){ + OnEvent?.Invoke(); + foreach(CustomUnRegister unRegister in AutoUnRegister){ + unRegister.UnRegister(); + } + AutoUnRegister.Clear(); + } + } + } + + + public static class CanRegisterEventExtension + { + public static IUnRegister EGRegisterEvent(this IEGFramework self, Action onEvent) + { + return EGArchitectureImplement.Interface.GetModule().RegisterEvent(onEvent); + } + public static void EGUnRegisterEvent(this IEGFramework self, Action onEvent) + { + EGArchitectureImplement.Interface.GetModule().UnRegisterEvent(onEvent); + } + } + + public static class CanSendEventExtension + { + public static void EGSendEvent(this IEGFramework self) where T : new() + { + EGArchitectureImplement.Interface.GetModule().SendEvent(); + } + public static void EGSendEvent(this IEGFramework self, T e) + { + EGArchitectureImplement.Interface.GetModule().SendEvent(e); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/EGObjects.cs b/EGFramework/Module/EGObjects.cs new file mode 100644 index 0000000..2a3b742 --- /dev/null +++ b/EGFramework/Module/EGObjects.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework +{ + public interface IEGObjects + { + void RegisterObject(T object_); + T GetObject() where T : class,new(); + + } + public class EGObjects : EGModule,IEGObjects + { + private IOCContainer ObjectContainer = new IOCContainer(); + public override void Init() + { + + } + + public TObject GetObject() where TObject : class,new() + { + if (!ObjectContainer.self.ContainsKey(typeof(TObject))) + { + this.RegisterObject(new TObject()); + } + return ObjectContainer.Get(); + } + + public void RegisterObject(TObject object_) + { + ObjectContainer.Register(object_); + } + + public bool ContainsObject(){ + return ObjectContainer.self.ContainsKey(typeof(TObject)); + } + } + + public static class CanGetObjectExtension + { + public static T EGGetObject(this IEGFramework self) where T : class,new() + { + return EGArchitectureImplement.Interface.GetModule().GetObject(); + } + } + public static class CanRegisterObjectExtension + { + public static void EGRegisterObject(this IArchitecture self,T object_) where T : class,new() + { + self.GetModule().RegisterObject(object_); + } + public static void EGRegisterObject(this IEGFramework self,T object_) where T : class,new() + { + EGArchitectureImplement.Interface.GetModule().RegisterObject(object_); + } + } + + public static class CanContainsObjectExtension{ + public static bool EGContainsObject(this IEGFramework self) + { + return EGArchitectureImplement.Interface.GetModule().ContainsObject(); + } + } + +} diff --git a/EGFramework/Module/Extension/EGConvertExtension.cs b/EGFramework/Module/Extension/EGConvertExtension.cs new file mode 100644 index 0000000..2646c0e --- /dev/null +++ b/EGFramework/Module/Extension/EGConvertExtension.cs @@ -0,0 +1,392 @@ +using System; +using System.Linq; +using System.Text; + +namespace EGFramework { + //协议规则解析通用方法扩展 + public static class EGConvertExtension + { + /// + /// Hex string data to byte array,such as a string like "0x00 0xff 0x06" + /// + /// Only include A-F,0-9,hex + /// + public static byte[] ToByteArrayByHex(this string self) { + int hexLen = self.Length; + byte[] result; + if (hexLen % 2 == 1) + { + //奇数 + hexLen++; + result = new byte[(hexLen / 2)]; + self += "0" ; + } + else + { + //偶数 + result = new byte[(hexLen / 2)]; + } + int j = 0; + for (int i = 0; i < hexLen; i += 2) + { + result[j] = (byte)int.Parse(self.Substring(i, 2), System.Globalization.NumberStyles.HexNumber); + j++; + } + return result; + } + + + /// + /// get string from hex array ,like hex array {0x0a,0x11} => "0x0a 0x11" + /// + /// + /// + public static string ToStringByHex(this byte[] self) + { + StringBuilder sb = new StringBuilder(); + + foreach (byte b in self) + { + sb.Append(b.ToString("X2") + " "); + } + string result = sb.ToString().Trim(); + return result; + } + public static string ToStringByHex0x(this byte[] self) + { + StringBuilder sb = new StringBuilder(); + + foreach (byte b in self) + { + sb.Append("0x" + b.ToString("X2") + " "); + } + string result = sb.ToString().Trim(); + return result; + } + + /// + /// get hex from string ,like string "0x0a 0x11" => {0x0a,0x11} + /// + /// + /// + public static byte[] ToHexByString(this string self) + { + string[] hexStrings = self.Split(' '); + byte[] byteArray = new byte[hexStrings.Length]; + for (int i = 0; i < hexStrings.Length; i++) + { + byteArray[i] = Convert.ToByte(hexStrings[i], 16); + } + return byteArray; + } + public static byte[] ToHexByString0x(this string self) + { + if (self.Length <= 2 && self.Substring(0, 2) != "0x") { + return null; + } + return self.ToHexByString(); + } + + public static byte[] ToBytes(this ushort self){ + byte[] byteArray = BitConverter.GetBytes(self); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(byteArray); + } + return byteArray; + } + + public static ushort ToUShort(this byte[] self){ + if (BitConverter.IsLittleEndian) + { + Array.Reverse(self); + } + return BitConverter.ToUInt16(self, 0); + } + + public static ushort ToUShortLittleEndian(this byte[] self){ + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(self); + } + return BitConverter.ToUInt16(self, 0); + } + + public static byte[] ToBytes(this uint self){ + byte[] byteArray = BitConverter.GetBytes(self); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(byteArray); + } + return byteArray; + } + public static byte[] ToBytesLittleEndian(this uint self){ + byte[] byteArray = BitConverter.GetBytes(self); + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(byteArray); + } + return byteArray; + } + + public static uint ToUINT(this byte[] self){ + if (BitConverter.IsLittleEndian) + { + Array.Reverse(self); + } + return BitConverter.ToUInt32(self, 0); + } + public static uint ToUINTLittleEndian(this byte[] self){ + if (!BitConverter.IsLittleEndian) + { + Array.Reverse(self); + } + return BitConverter.ToUInt32(self, 0); + } + + + public static byte[] ToBytes(this uint[] uintArray) + { + int byteCount = uintArray.Length * sizeof(uint); + byte[] byteArray = new byte[byteCount]; + + for (int i = 0; i < uintArray.Length; i++) + { + byte[] tempBytes = BitConverter.GetBytes(uintArray[i]); + Array.Copy(tempBytes, 0, byteArray, i * sizeof(uint), sizeof(uint)); + } + return byteArray; + } + + /// + /// convert and resize byte array,such as uint is 0x00FF7799 => byte array {0xFF,0x77,0x99} + /// + /// + /// + public static byte[] ToBytesAndResizeArray(this uint self){ + byte[] byteArray = BitConverter.GetBytes(self); + if (BitConverter.IsLittleEndian) + { + Array.Reverse(byteArray); + } + int startIndex = Array.FindIndex(byteArray, b => b != 0); + if (startIndex == -1) + { + byteArray = new byte[1]; + } + else + { + byteArray = byteArray.Skip(startIndex).ToArray(); + } + return byteArray; + } + + public static byte[] ToByteArray(this bool[] boolArray) + { + int numBool = boolArray.Length; + int numBytes = (numBool + 7) / 8; + byte[] byteArray = new byte[numBytes]; + + for (int i = 0; i < numBool; i++) + { + int byteIndex = i / 8; + int bitIndex = i % 8; + if (boolArray[i]) + { + byteArray[byteIndex] |= (byte)(1 << bitIndex); + } + } + + return byteArray; + } + + public static bool[] ToBoolArray(this byte[] byteArray) + { + bool[] boolArray = new bool[byteArray.Length * 8]; + for (int i = 0; i < byteArray.Length; i++) + { + byte currentByte = byteArray[i]; + + for (int j = 0; j < 8; j++) + { + boolArray[i * 8 + j] = (currentByte & (1 << j)) != 0; + } + } + + return boolArray; + } + + public static bool[] ToBoolArray(this byte byteData) + { + bool[] boolArray = new bool[8]; + byte currentByte = byteData; + for (int j = 0; j < 8; j++) + { + boolArray[j] = (currentByte & (1 << j)) != 0; + } + return boolArray; + } + public static bool[] ToBoolArray(this int value) + { + string binaryString = Convert.ToString(value, 2); + bool[] boolArray = new bool[binaryString.Length]; + if(binaryString.Length < 8){ + boolArray = new bool[8]; + } + for (int i = 0; i < binaryString.Length; i++) + { + boolArray[binaryString.Length - i - 1] = binaryString[i] == '1'; + } + return boolArray; + } + + public static int ToInt(this bool[] boolArray) + { + int result = 0; + for (int i = 0; i < boolArray.Length; i++) + { + if (boolArray[i]) + { + result |= (1 << i); + } + } + return result; + } + + public static ushort[] ToUShortArray(this byte[] byteArray){ + ushort[] ushortArray = new ushort[byteArray.Length / 2]; + for (int i = 0, j = 0; i < byteArray.Length; i += 2, j++) + { + ushortArray[j] = (ushort)((byteArray[i] << 8) | byteArray[i + 1]); + } + return ushortArray; + } + + public static byte[] ToByteArray(this float[] floatArray) + { + byte[] byteArray = new byte[floatArray.Length * 4]; + for (int i = 0; i < floatArray.Length; i++) + { + byte[] tempArray = BitConverter.GetBytes(floatArray[i]); + if(!BitConverter.IsLittleEndian){ + Array.Reverse(tempArray); + } + //Array.Reverse(tempArray); // 大端序需要反转字节数组以满足高字节在后 + Array.Copy(tempArray, 0, byteArray, i * 4, 4); + } + return byteArray; + } + + public static byte[] ToByteArray(this float value) + { + byte[] byteArray = new byte[4]; + byte[] tempArray = BitConverter.GetBytes(value); + if(!BitConverter.IsLittleEndian){ + Array.Reverse(tempArray); + } + //Array.Reverse(tempArray); // 大端序需要反转字节数组以满足高字节在后 + Array.Copy(tempArray, 0, byteArray, 0, 4); + return byteArray; + } + public static byte[] ToByteArrayBigEndian(this float value) + { + byte[] byteArray = new byte[4]; + byte[] tempArray = BitConverter.GetBytes(value); + if(BitConverter.IsLittleEndian){ + Array.Reverse(tempArray); + } + //Array.Reverse(tempArray); // 大端序需要反转字节数组以满足高字节在后 + Array.Copy(tempArray, 0, byteArray, 0, 4); + return byteArray; + } + + + public static float[] ToFloatArray(this byte[] byteArray) + { + float[] floatArray = new float[byteArray.Length / 4]; + for (int i = 0; i < floatArray.Length; i++) + { + byte[] tempArray = new byte[4]; + Array.Copy(byteArray, i * 4, tempArray, 0, 4); + if(!BitConverter.IsLittleEndian){ + Array.Reverse(tempArray); + } + //Array.Reverse(tempArray); + floatArray[i] = BitConverter.ToSingle(tempArray, 0); + } + return floatArray; + } + + public static float[] ToFloatArrayBigEndian(this byte[] byteArray) + { + float[] floatArray = new float[byteArray.Length / 4]; + for (int i = 0; i < floatArray.Length; i++) + { + byte[] tempArray = new byte[4]; + Array.Copy(byteArray, i * 4, tempArray, 0, 4); + if(BitConverter.IsLittleEndian){ + Array.Reverse(tempArray); + } + //Array.Reverse(tempArray); + floatArray[i] = BitConverter.ToSingle(tempArray, 0); + } + return floatArray; + } + public static double[] ToDoubleArray(this byte[] byteArray) + { + double[] doubleArray = new double[byteArray.Length / 8]; + for (int i = 0; i < doubleArray.Length; i++) + { + byte[] tempArray = new byte[8]; + Array.Copy(byteArray, i * 8, tempArray, 0, 8); + if(!BitConverter.IsLittleEndian){ + Array.Reverse(tempArray); + } + //Array.Reverse(tempArray); + doubleArray[i] = BitConverter.ToDouble(tempArray, 0); + } + return doubleArray; + } + + public static byte[] ToByteArray(this int[] intArray) + { + byte[] byteArray = new byte[intArray.Length * 4]; + for (int i = 0; i < intArray.Length; i++) + { + byte[] tempArray = BitConverter.GetBytes(intArray[i]); + //Array.Reverse(tempArray); // 大端序需要反转字节数组以满足高字节在后 + Array.Copy(tempArray, 0, byteArray, i * 4, 4); + } + return byteArray; + } + + public static byte[] Reverse(this byte[] bytes){ + Array.Reverse(bytes); + return bytes; + } + + public static byte[] ToSubByte(this byte[] bytes,int index,int length){ + byte[] resultByte = new byte[length]; + Array.Copy(bytes,index,resultByte,0,length); + return resultByte; + } + + public static float[] ToSubFloat(this float[] floats,int index,int length){ + float[] resultFloats = new float[length]; + Array.Copy(floats,index,resultFloats,0,length); + return resultFloats; + } + public static float[] ToSubArrayByCount(this float[] originalArray, int targetLength) + { + float[] reducedArray = new float[targetLength]; + float ratio = (float)(originalArray.Length - 1) / (targetLength - 1); + for (int i = 0; i < targetLength; i++) + { + int originalIndex = (int)Math.Round(ratio * i); + reducedArray[i] = originalArray[originalIndex]; + } + return reducedArray; + } + + } +} diff --git a/EGFramework/Module/Extension/EGCrcExtension.cs b/EGFramework/Module/Extension/EGCrcExtension.cs new file mode 100644 index 0000000..7ef38ec --- /dev/null +++ b/EGFramework/Module/Extension/EGCrcExtension.cs @@ -0,0 +1,154 @@ +using System; +using System.Security.Cryptography; + +namespace EGFramework{ + public static class EGCrcModbusExtension + { + /// CRC calculate is a common device verify algorithm + /// use + // hex = {0x80,0x05}; + // Polynomial = x^16+x^15+x^2+1 = 1 80 05 + public const ushort CRC_16_Modbus_Polynomial = 0x8005; + // hex = {0xFF,0xFF} + public const ushort CRC_16_Modbus_Start = 0xFFFF; + // hex = {0x00,0x00} + public const ushort CRC_16_Modbus_ResultXOR = 0x0000; + private static readonly ushort[] Crc_16_Table_Modbus ={ + 0x00,0xC0C1,0xC181,0x140,0xC301,0x3C0,0x280,0xC241,0xC601,0x6C0,0x780,0xC741,0x500,0xC5C1,0xC481,0x440, + 0xCC01,0xCC0,0xD80,0xCD41,0xF00,0xCFC1,0xCE81,0xE40,0xA00,0xCAC1,0xCB81,0xB40,0xC901,0x9C0,0x880,0xC841, + 0xD801,0x18C0,0x1980,0xD941,0x1B00,0xDBC1,0xDA81,0x1A40,0x1E00,0xDEC1,0xDF81,0x1F40,0xDD01,0x1DC0,0x1C80,0xDC41, + 0x1400,0xD4C1,0xD581,0x1540,0xD701,0x17C0,0x1680,0xD641,0xD201,0x12C0,0x1380,0xD341,0x1100,0xD1C1,0xD081,0x1040, + 0xF001,0x30C0,0x3180,0xF141,0x3300,0xF3C1,0xF281,0x3240,0x3600,0xF6C1,0xF781,0x3740,0xF501,0x35C0,0x3480,0xF441, + 0x3C00,0xFCC1,0xFD81,0x3D40,0xFF01,0x3FC0,0x3E80,0xFE41,0xFA01,0x3AC0,0x3B80,0xFB41,0x3900,0xF9C1,0xF881,0x3840, + 0x2800,0xE8C1,0xE981,0x2940,0xEB01,0x2BC0,0x2A80,0xEA41,0xEE01,0x2EC0,0x2F80,0xEF41,0x2D00,0xEDC1,0xEC81,0x2C40, + 0xE401,0x24C0,0x2580,0xE541,0x2700,0xE7C1,0xE681,0x2640,0x2200,0xE2C1,0xE381,0x2340,0xE101,0x21C0,0x2080,0xE041, + 0xA001,0x60C0,0x6180,0xA141,0x6300,0xA3C1,0xA281,0x6240,0x6600,0xA6C1,0xA781,0x6740,0xA501,0x65C0,0x6480,0xA441, + 0x6C00,0xACC1,0xAD81,0x6D40,0xAF01,0x6FC0,0x6E80,0xAE41,0xAA01,0x6AC0,0x6B80,0xAB41,0x6900,0xA9C1,0xA881,0x6840, + 0x7800,0xB8C1,0xB981,0x7940,0xBB01,0x7BC0,0x7A80,0xBA41,0xBE01,0x7EC0,0x7F80,0xBF41,0x7D00,0xBDC1,0xBC81,0x7C40, + 0xB401,0x74C0,0x7580,0xB541,0x7700,0xB7C1,0xB681,0x7640,0x7200,0xB2C1,0xB381,0x7340,0xB101,0x71C0,0x7080,0xB041, + 0x5000,0x90C1,0x9181,0x5140,0x9301,0x53C0,0x5280,0x9241,0x9601,0x56C0,0x5780,0x9741,0x5500,0x95C1,0x9481,0x5440, + 0x9C01,0x5CC0,0x5D80,0x9D41,0x5F00,0x9FC1,0x9E81,0x5E40,0x5A00,0x9AC1,0x9B81,0x5B40,0x9901,0x59C0,0x5880,0x9841, + 0x8801,0x48C0,0x4980,0x8941,0x4B00,0x8BC1,0x8A81,0x4A40,0x4E00,0x8EC1,0x8F81,0x4F40,0x8D01,0x4DC0,0x4C80,0x8C41, + 0x4400,0x84C1,0x8581,0x4540,0x8701,0x47C0,0x4680,0x8641,0x8201,0x42C0,0x4380,0x8341,0x4100,0x81C1,0x8081,0x4040, + }; + public static ushort CalculateCRC16Modbus(this byte[] bytes) + { + CRC16 provider = new CRC16(Crc_16_Table_Modbus); + byte[] hash = provider.ComputeHash(bytes); + + ushort crc16 = BitConverter.ToUInt16(hash, 0); + + ushort reversedResult = (ushort)((crc16 >> 8) | (crc16 << 8)); + return reversedResult; + } + + } + public static class EGCrcUtility + { + //Crc with table + public static uint CalculateCrc(byte[] data, uint initialValue, uint xorValue, bool inputReverse, bool outputReverse,uint[] CrcTable) + { + uint crc = initialValue; + + for (int i = 0; i < data.Length; i++) + { + if (inputReverse) + data[i] = ReverseBits(data[i]); + + crc ^= (uint)(data[i] << 24); + + for (int j = 0; j < 8; j++) + { + crc = (crc << 8) ^ CrcTable[crc >> 24]; + } + } + + if (outputReverse) + crc = ReverseBits(crc); + + return crc ^ xorValue; + } + + //Crc without table + public static uint CalculateCrc(byte[] data, uint polynomial, uint initialValue, uint xorValue, bool inputReverse, bool outputReverse) + { + uint crc = initialValue; + + for (int i = 0; i < data.Length; i++) + { + if (inputReverse) + data[i] = ReverseBits(data[i]); + + crc ^= (uint)(data[i] << 24); + + for (int j = 0; j < 8; j++) + { + if ((crc & 0x80000000) != 0) + { + crc = (crc << 1) ^ polynomial; + } + else + { + crc <<= 1; + } + } + } + + if (outputReverse) + crc = ReverseBits(crc); + + return crc ^ xorValue; + } + + private static uint ReverseBits(uint value) + { + uint result = 0; + for (int i = 0; i < 8; i++) + { + result = (result << 1) | ((value >> i) & 1); + } + return result; + } + + private static byte ReverseBits(byte value) + { + byte result = 0; + for (int i = 0; i < 8; i++) + { + result = (byte)((result << 1) | ((value >> i) & 1)); + } + return result; + } + } + public class CRC16 : HashAlgorithm + { + private const ushort polynomial = 0x8005; + private ushort[] table = new ushort[256]; + private ushort crc = 0xFFFF; + + public CRC16(ushort[] table) + { + HashSizeValue = 16; + this.table = table; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + for (int i = ibStart; i < ibStart + cbSize; i++) + { + byte index = (byte)(crc ^ array[i]); + crc = (ushort)((crc >> 8) ^ table[index]); + } + } + + protected override byte[] HashFinal() + { + return BitConverter.GetBytes(crc); + } + + public override void Initialize() + { + crc = ushort.MaxValue; + } + } +} diff --git a/EGFramework/Module/Extension/EGDateTimeExtension.cs b/EGFramework/Module/Extension/EGDateTimeExtension.cs new file mode 100644 index 0000000..8fc3f00 --- /dev/null +++ b/EGFramework/Module/Extension/EGDateTimeExtension.cs @@ -0,0 +1,31 @@ +using System; +namespace EGFramework{ + public static class EGDateTimeExtension + { + public static string GetFullDateMsg(this IEGFramework self) + { + return DateTime.Now.ToString("yyyy-MM-dd") + " " + DateTime.Now.ToString("HH:mm:ss"); + } + public static string GetDayDateMsg(this IEGFramework self) + { + return DateTime.Now.ToString("HH:mm:ss"); + } + public static long GetDateTime(this object self) + { + DateTime dt = DateTime.Now; + return dt.Ticks; + } + public static string GetFullDateMsg(this long ticks){ + DateTime dateTime = new DateTime(ticks); + return dateTime.ToString("yyyy-MM-dd") + " " + dateTime.ToString("HH:mm:ss"); + } + public static string GetDayDateMsg(this long ticks){ + DateTime dateTime = new DateTime(ticks); + return dateTime.ToString("HH:mm:ss"); + } + public static string GetDateMsg(this long ticks){ + DateTime dateTime = new DateTime(ticks); + return dateTime.ToString("yyyy-MM-dd"); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/Extension/EGEncodingExtension.cs b/EGFramework/Module/Extension/EGEncodingExtension.cs new file mode 100644 index 0000000..297785d --- /dev/null +++ b/EGFramework/Module/Extension/EGEncodingExtension.cs @@ -0,0 +1,78 @@ +using System.Text; + +namespace EGFramework{ + //use this extension,you should add System.Text.Encoding.CodePages package from Nuget + public static class EGEncodingExtension + { + public static bool IsInit{ set; get; } + + /// + /// get encoding from encoding params(string). + /// + /// + /// + /// + public static Encoding GetEncoding(this IEGFramework self,string encodingTxt){ + if(!IsInit){ + IsInit = true; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + return Encoding.GetEncoding(encodingTxt); + } + + /// + /// Make a string to bytes with encoding params(string). + /// + /// + /// + /// + public static byte[] ToBytesByEncoding(this string self,string encodingTxt){ + if(!IsInit){ + IsInit = true; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + return Encoding.GetEncoding(encodingTxt).GetBytes(self); + } + /// + /// Make a string to bytes with encoding. + /// + /// + /// + /// + public static byte[] ToBytesByEncoding(this string self,Encoding encoding){ + if(!IsInit){ + IsInit = true; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + return encoding.GetBytes(self); + } + + /// + /// Make a bytes to string with encoding params(string). + /// + /// + /// + /// + public static string ToStringByEncoding(this byte[] self,string encodingTxt){ + if(!IsInit){ + IsInit = true; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + return Encoding.GetEncoding(encodingTxt).GetString(self); + } + /// + /// Make a bytes to string with encoding. + /// + /// + /// + /// + public static string ToStringByEncoding(this byte[] self,Encoding encoding){ + if(!IsInit){ + IsInit = true; + Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); + } + return encoding.GetString(self); + } + } +} + diff --git a/EGFramework/Module/Extension/EGIpExtension.cs b/EGFramework/Module/Extension/EGIpExtension.cs new file mode 100644 index 0000000..a836fe4 --- /dev/null +++ b/EGFramework/Module/Extension/EGIpExtension.cs @@ -0,0 +1,54 @@ + +namespace EGFramework { + public static class EGIpExtension + { + /// + /// Get host from IP. Such as 192.168.0.1:5555 => get 192.168.0.1 + /// + /// + /// + public static string GetHostByIp(this string ip) + { + int colonIndex = ip.IndexOf(":"); + string host = ""; + if (colonIndex != -1) + { + host = ip.Substring(0, colonIndex); + } + return host; + } + + public static int GetPortByIp(this string ip) + { + int colonIndex = ip.IndexOf(":"); + string portString = ip.Substring(colonIndex + 1); + int port; + if (int.TryParse(portString, out port)) + { + //nothing to do + } + else + { + port = 0; + } + return port; + } + + public static string GetStrFrontSymbol(this string str,char symbol){ + int colonIndex = str.IndexOf(symbol); + string frontStr = ""; + if (colonIndex != -1) + { + frontStr = str.Substring(0, colonIndex); + } + return frontStr; + } + + public static string GetStrBehindSymbol(this string str,char symbol){ + int colonIndex = str.IndexOf(symbol); + string behindStr = str.Substring(colonIndex + 1); + return behindStr; + } + } +} + diff --git a/EGFramework/Module/Extension/EGSqlExtension.cs b/EGFramework/Module/Extension/EGSqlExtension.cs new file mode 100644 index 0000000..51d7328 --- /dev/null +++ b/EGFramework/Module/Extension/EGSqlExtension.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Reflection; + +namespace EGFramework +{ + public static class EGSqlExtension + { + public static string ToCreateTableSQL(this PropertyInfo property) + { + string sqlCommand; + if (property.Name == "ID" || property.Name == "id" || property.Name == "Id") + { + return ""; + } + if (property.PropertyType == typeof(int) || property.PropertyType.IsEnum) + { + sqlCommand = "`" + property.Name + "` INTEGER" + " NOT NULL,"; + } + else if (property.PropertyType == typeof(double) || property.PropertyType == typeof(float)) + { + sqlCommand = "`" + property.Name + "` REAL" + " NOT NULL,"; + } + else if (property.PropertyType == typeof(bool)) + { + sqlCommand = "`" + property.Name + "` REAL" + " NOT NULL,"; + } + else if (property.PropertyType == typeof(long)) + { + sqlCommand = "`" + property.Name + "` BIGINT(20)" + " NOT NULL,"; + } + else if (property.PropertyType == typeof(string)) + { + sqlCommand = "`" + property.Name + "` VARCHAR(255)" + " NOT NULL,"; + } + else + { + sqlCommand = "`" + property.Name + "` VARCHAR(255)" + " NOT NULL,"; + } + return sqlCommand; + } + + public static string ToCreateTableSQL(this FieldInfo field) + { + string sqlCommand; + if (field.Name == "ID" || field.Name == "id" || field.Name == "Id") + { + return ""; + } + if (field.FieldType == typeof(int) || field.FieldType.IsEnum) + { + sqlCommand = "`" + field.Name + "` INTEGER" + " NOT NULL,"; + } + else if (field.FieldType == typeof(double) || field.FieldType == typeof(float)) + { + sqlCommand = "`" + field.Name + "` REAL" + " NOT NULL,"; + } + else if (field.FieldType == typeof(bool)) + { + sqlCommand = "`" + field.Name + "` REAL" + " NOT NULL,"; + } + else if (field.FieldType == typeof(long)) + { + sqlCommand = "`" + field.Name + "` BIGINT(20)" + " NOT NULL,"; + } + else if (field.FieldType == typeof(string)) + { + sqlCommand = "`" + field.Name + "` VARCHAR(255)" + " NOT NULL,"; + } + else + { + sqlCommand = "`" + field.Name + "` VARCHAR(255)" + " NOT NULL,"; + } + return sqlCommand; + } + + public static string ToCreateTableSQL(this KeyValuePair param) + { + string sqlCommand; + if (param.Key == "ID" || param.Key == "id" || param.Key == "Id") + { + return ""; + } + if (param.Value == typeof(int) || param.Value.IsEnum) + { + sqlCommand = "`" + param.Key + "` INTEGER" + " NOT NULL,"; + } + else if (param.Value == typeof(double) || param.Value == typeof(float)) + { + sqlCommand = "`" + param.Key + "` REAL" + " NOT NULL,"; + } + else if (param.Value == typeof(bool)) + { + sqlCommand = "`" + param.Key + "` REAL" + " NOT NULL,"; + } + else if (param.Value == typeof(long)) + { + sqlCommand = "`" + param.Key + "` BIGINT(20)" + " NOT NULL,"; + } + else if (param.Value == typeof(string)) + { + sqlCommand = "`" + param.Key + "` VARCHAR(255)" + " NOT NULL,"; + } + else + { + sqlCommand = "`" + param.Key + "` VARCHAR(255)" + " NOT NULL,"; + } + return sqlCommand; + } + + public static string ToCreateTableSQL(this Type type, string tableName) + { + var properties = type.GetProperties(); + FieldInfo[] fields = type.GetFields(); + string keySet = ""; + foreach (PropertyInfo key in properties) + { + keySet += key.ToCreateTableSQL(); + } + foreach (FieldInfo key in fields) + { + keySet += key.ToCreateTableSQL(); + } + keySet = keySet.TrimEnd(','); + string createSql = @"CREATE TABLE " + tableName + " (" + + "`ID` INTEGER PRIMARY KEY AUTO_INCREMENT, " + keySet + + ");"; + return createSql; + } + + public static string ToCreateTableSQL(this Dictionary tableParam,string tableName) + { + + string keySet = ""; + foreach(KeyValuePair key in tableParam){ + keySet += key.ToCreateTableSQL(); + } + keySet = keySet.TrimEnd(','); + string createSql = @"CREATE TABLE "+tableName+" ("+ + "`ID` INTEGER PRIMARY KEY AUTOINCREMENT, "+keySet + +");"; + return createSql; + } + + } +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/EGGenerate.cs b/EGFramework/Module/GenerateTools/EGGenerate.cs new file mode 100644 index 0000000..cd69606 --- /dev/null +++ b/EGFramework/Module/GenerateTools/EGGenerate.cs @@ -0,0 +1,16 @@ +namespace EGFramework +{ + public class EGGenerate : EGModule + { + public override void Init() + { + + } + + public T GenerateUI(object data) where T : new() + { + T ui = new T(); + return ui; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/GenerateToolsInterface.cs b/EGFramework/Module/GenerateTools/GenerateToolsInterface.cs new file mode 100644 index 0000000..8a6efb1 --- /dev/null +++ b/EGFramework/Module/GenerateTools/GenerateToolsInterface.cs @@ -0,0 +1,34 @@ +namespace EGFramework +{ + public interface IGenerateToolsInterface + { + public string GenerateCode(); + } + public interface IGodotTable + { + + } + public interface IGodotRowData + { + + } + + public interface ITableData + { + /// + /// Get the data of the table. + /// + /// + string[][] GetTableData(); + /// + /// Get the header of the table. + /// + /// + string[] GetTableHeader(); + } + + public interface ITableRowData + { + string[] GetRowData(); + } +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/Templete/Code/EGSvgGenerator.cs b/EGFramework/Module/GenerateTools/Templete/Code/EGSvgGenerator.cs new file mode 100644 index 0000000..6d29bc3 --- /dev/null +++ b/EGFramework/Module/GenerateTools/Templete/Code/EGSvgGenerator.cs @@ -0,0 +1,90 @@ +namespace EGFramework.Code{ + public class EGSvgGenerator : IGenerateToolsInterface + { + public string SvgHeader { get ; private set;} + public const string SvgFooter = ""; + + public int Width { get; set; } + public int Height { get; set; } + public EGSvgViewBox ViewBox { get; set; } + + public EGSvgGenerator(int width, int height, EGSvgViewBox viewBox) + { + Width = width; + Height = height; + ViewBox = viewBox; + SvgHeader = $""; + } + + public void DrawCircle(float x, float y, float radius, string color) + { + // Implementation for drawing a circle in SVG format + string svgCircle = $""; + } + public void DrawEllipse(float cx, float cy, float rx, float ry, string color) + { + // Implementation for drawing an ellipse in SVG format + string svgEllipse = $""; + } + public void DrawPolygon(float[] points, string color) + { + // Implementation for drawing a polygon in SVG format + string svgPolygon = ""; + } + public void DrawPolyline(float[] points, string color) + { + // Implementation for drawing a polyline in SVG format + string svgPolyline = ""; + } + public void DrawPath(string d, string color) + { + // Implementation for drawing a path in SVG format + string svgPath = $""; + } + public void DrawRectangle(float x, float y, float width, float height, string color) + { + // Implementation for drawing a rectangle in SVG format + string svgRectangle = $""; + } + public void DrawLine(float x1, float y1, float x2, float y2, string color) + { + // Implementation for drawing a line in SVG format + string svgLine = $""; + } + public void DrawText(float x, float y, string text, string color) + { + // Implementation for drawing text in SVG format + string svgText = $"{text}"; + } + + + public string GenerateCode() + { + return typeof(T).Name; + } + } + public struct EGSvgViewBox { + public float X { get; set; } + public float Y { get; set; } + public float Width { get; set; } + public float Height { get; set; } + + public EGSvgViewBox(float x, float y, float width, float height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/Templete/Code/EGSvgTempletes.cs b/EGFramework/Module/GenerateTools/Templete/Code/EGSvgTempletes.cs new file mode 100644 index 0000000..909f46e --- /dev/null +++ b/EGFramework/Module/GenerateTools/Templete/Code/EGSvgTempletes.cs @@ -0,0 +1,11 @@ +namespace EGFramework +{ + public static class EGSVG16PX + { + public const string Circle = ""; + public const string Rect = ""; + public const string Heart = ""; + public const string Star = ""; + + } +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/Templete/Variant/EGDataStruct.cs b/EGFramework/Module/GenerateTools/Templete/Variant/EGDataStruct.cs new file mode 100644 index 0000000..dfee6d8 --- /dev/null +++ b/EGFramework/Module/GenerateTools/Templete/Variant/EGDataStruct.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework +{ + public struct EGSelectParam + { + public int SelectID { set; get; } + + public Dictionary SelectList { set; get; } + public EGSelectParam(int selectID, Dictionary selectList) + { + SelectID = selectID; + SelectList = selectList; + } + } + public struct EGRangeParam + { + public double Min { set; get; } + public double Max { set; get; } + public double Step { set; get; } + public double Value { set; get; } + public EGRangeParam(double min, double max, double step, double value) + { + Min = min; + Max = max; + Step = step; + Value = value; + } + } + + public struct EGPathSelect + { + public string Path { set; get; } + public bool IsDir { set; get; } + public override string ToString() + { + return Path; + } + public EGPathSelect() { Path = ""; IsDir = false; } + + public EGPathSelect(string path) + { + this.Path = path; + this.IsDir = false; + } + public EGPathSelect(string path, bool isDir) + { + this.Path = path; + this.IsDir = isDir; + } + } + + public interface IEGReadOnlyString + { + public string GetString(); + } + public struct EGReadOnlyString : IEGReadOnlyString + { + public string Value { get; private set; } + public EGReadOnlyString(string value) + { + Value = value; + } + + public string GetString() + { + return Value; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/Templete/Variant/EGTree.cs b/EGFramework/Module/GenerateTools/Templete/Variant/EGTree.cs new file mode 100644 index 0000000..b753708 --- /dev/null +++ b/EGFramework/Module/GenerateTools/Templete/Variant/EGTree.cs @@ -0,0 +1,127 @@ +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; + +namespace EGFramework +{ + public interface IEGTree + { + public string Name { set; get; } + public IEGTree GetParent(); + public void SetParent(IEGTree tree); + public IEnumerable GetChilds(); + public void AppendTree(IEGTree tree); + + } + + public class EGTree : IEGTree + { + public string Name { set; get; } + + public string StrValue { set; get; } + public long IntegerValue { set; get; } + public byte[] ByteValue { set; get; } + public float FloatValue { set; get; } + public bool BoolValue { set; get; } + + public IEGTree Parent { set; get; } + public List Childs { set; get; } = new List(); + + public EGTree() + { + } + + public EGTree(string name) + { + this.Name = name; + } + + public void AppendTree(IEGTree tree) + { + tree.SetParent(this); + Childs.Add(tree); + } + + public virtual IEnumerable GetChilds() + { + return Childs; + } + + public virtual IEGTree GetParent() + { + return Parent; + } + + public void SetParent(IEGTree tree) + { + this.Parent = tree; + } + } + + public static class EGTreeFactory + { + public static EGTree CreateTreeByJson(string json) + { + JsonTextReader reader = new JsonTextReader(new StringReader(json)); + Stack TreeStack = new Stack(); + EGTree resultTree = new EGTree(); + EGTree lastTree = null; + while (reader.Read()) + { + if (reader.TokenType == JsonToken.StartObject) + { + if (lastTree != null) + { + TreeStack.Push(lastTree); + } + } + if (reader.TokenType == JsonToken.EndObject) + { + if(TreeStack.Count>0) + TreeStack.Pop(); + } + + if (reader.TokenType == JsonToken.PropertyName) + { + EGTree newTree = new EGTree(); + if (reader.Value != null) + { + newTree.Name = reader.Value.ToString(); + } + if (TreeStack.Count > 0) + { + TreeStack.Peek().AppendTree(newTree); + } + else + { + resultTree.AppendTree(newTree); + } + lastTree = newTree; + } + + if (lastTree != null && reader.TokenType == JsonToken.Integer) + { + lastTree.IntegerValue = (long)reader.Value; + } + if (lastTree != null && reader.TokenType == JsonToken.Boolean) + { + lastTree.BoolValue = (bool)reader.Value; + } + if (lastTree != null && reader.TokenType == JsonToken.String) + { + lastTree.StrValue = (string)reader.Value; + } + if (lastTree != null && reader.TokenType == JsonToken.Float) + { + lastTree.FloatValue = (float)reader.Value; + } + if (lastTree!=null && reader.TokenType == JsonToken.Bytes) + { + lastTree.ByteValue = (byte[])reader.Value; + } + } + return resultTree; + } + } + +} \ No newline at end of file diff --git a/EGFramework/Module/GenerateTools/Templete/Variant/EGVariantGenerator.cs b/EGFramework/Module/GenerateTools/Templete/Variant/EGVariantGenerator.cs new file mode 100644 index 0000000..d84a61c --- /dev/null +++ b/EGFramework/Module/GenerateTools/Templete/Variant/EGVariantGenerator.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace EGFramework{ + public static class EGenerateVariant + { + public static Dictionary EGenerateDictiontaryByType(this Type self) + { + PropertyInfo[] propertyNames = self.GetProperties(); + FieldInfo[] fieldNames = self.GetFields(); + Dictionary result = new Dictionary(); + foreach (PropertyInfo pName in propertyNames) + { + result.Add(pName.Name, pName.Name); + } + foreach (FieldInfo fName in fieldNames) + { + result.Add(fName.Name, fName.Name); + } + return result; + } + public static Dictionary EGenerateEmptyDictiontaryByType(this Type self) + { + PropertyInfo[] propertyNames = self.GetProperties(); + FieldInfo[] fieldNames = self.GetFields(); + Dictionary result = new Dictionary(); + foreach (PropertyInfo pName in propertyNames) + { + result.Add(pName.Name, ""); + } + foreach (FieldInfo fName in fieldNames) + { + result.Add(fName.Name, ""); + } + return result; + } + public static Dictionary EGenerateTypeDictiontaryByType(this Type self) + { + PropertyInfo[] propertyNames = self.GetProperties(); + FieldInfo[] fieldNames = self.GetFields(); + Dictionary result = new Dictionary(); + foreach (PropertyInfo pName in propertyNames) + { + result.Add(pName.Name, pName.PropertyType); + } + foreach (FieldInfo fName in fieldNames) + { + result.Add(fName.Name, fName.FieldType); + } + return result; + } + + public static Dictionary EGenerateDictiontaryByObject(this T self) + { + PropertyInfo[] propertyNames = typeof(T).GetProperties(); + FieldInfo[] fieldNames = typeof(T).GetFields(); + // object[] s = propertyNames.Select(p => p.GetValue(self)).ToArray(); + // object[] a = fieldNames.Select(p => p.GetValue(self)).ToArray(); + Dictionary result = new Dictionary(); + foreach (PropertyInfo pName in propertyNames) + { + object p = pName.GetValue(self); + result.Add(pName.Name, p); + } + foreach (FieldInfo fName in fieldNames) + { + object p = fName.GetValue(self); + result.Add(fName.Name, p); + } + return result; + } + + public static List> EGenerateDictionaryByGroup(this IEnumerable self) + { + List> result = new List>(); + PropertyInfo[] propertyNames = typeof(T).GetProperties(); + FieldInfo[] fieldNames = typeof(T).GetFields(); + foreach (T member in self) + { + Dictionary mResult = new Dictionary(); + foreach (PropertyInfo pName in propertyNames) + { + object p = pName.GetValue(member); + mResult.Add(pName.Name, p); + } + foreach (FieldInfo fName in fieldNames) + { + object p = fName.GetValue(member); + mResult.Add(fName.Name, p); + } + result.Add(mResult); + } + return result; + } + + //Default primary key is id,Id,ID. + public static string EGetDefaultPrimaryKey(this Dictionary self) + { + foreach (KeyValuePair param in self) + { + if (param.Key == "ID" || param.Key == "Id" || param.Key == "id") + { + return param.Key; + } + } + return ""; + } + + public static List> ESearchByKeyword(this List> data, string fieldName, string keyWords) + { + List> result = new List>(); + foreach (Dictionary tablerow in data) + { + if (tablerow.ContainsKey(fieldName)) + { + if (tablerow[fieldName].ToString().Contains(keyWords)) + { + result.Add(tablerow); + } + } + } + return result; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/OtherTools/EGModbus.cs b/EGFramework/Module/OtherTools/EGModbus.cs new file mode 100644 index 0000000..5a40647 --- /dev/null +++ b/EGFramework/Module/OtherTools/EGModbus.cs @@ -0,0 +1,280 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace EGFramework{ + /// + /// This class is not a simple protocol tools,only support sample functions to easy use. + /// + public class EGModbus : IEGFramework, IModule + { + public ConcurrentQueue RTUCache = new ConcurrentQueue(); + public ConcurrentQueue TCPCache = new ConcurrentQueue(); + + public int Delay = 2000; + + public ConcurrentQueue WaitForSendRTU = new ConcurrentQueue(); + public int NextSendRTU = 0; + public int SendPointerRTU = 1; + + public ConcurrentQueue WaitForSendTCP = new ConcurrentQueue(); + public int NextSendTCP = 0; + public int SendPointerTCP = 1; + + public EasyEvent OnReadTimeOut = new EasyEvent(); + + public void Init() + { + this.EGRegisterMessageEvent((e,sender,ProtocolType)=>{ + if(ProtocolType == ProtocolType.SerialPort){ + RTUCache.Enqueue(e); + } + }); + this.EGRegisterMessageEvent((e,sender,ProtocolType)=>{ + if(ProtocolType == ProtocolType.TCPClient){ + TCPCache.Enqueue(e); + } + }); + this.EGOnMessage(); + this.EGOnMessage(); + } + + #region Modbus RTU Operations + private bool IsRequestRTU { set; get; } + public async Task ReadRTUAsync(ModbusRegisterType registerType,string serialPort,byte deviceAddress,ushort start,ushort count){ + if(IsRequestRTU){ + SendPointerRTU++; + int messageId = SendPointerRTU; + WaitForSendRTU.Enqueue(messageId); + await Task.Run(async () => + { + while (IsRequestRTU || NextSendRTU != messageId) + { + await Task.Delay(10); + //break; + } + }); + EG.Print("-----Read"+messageId+" ----"); + //return null; + } + RTUCache.Clear(); + IsRequestRTU = true; + IRequest ReadRequest; + ModbusRTU_Response? res = null; + switch(registerType){ + case ModbusRegisterType.Coil: + ReadRequest = new ModbusRTU_ReadCoils(deviceAddress,start,count); + this.EGSendMessage(ReadRequest,serialPort,ProtocolType.SerialPort); + this.EGSerialPort().SetExpectReceivedDataLength(6+count/8); + break; + case ModbusRegisterType.DiscreteInput: + ReadRequest = new ModbusRTU_ReadDiscreteInput(deviceAddress,start,count); + this.EGSendMessage(ReadRequest,serialPort,ProtocolType.SerialPort); + this.EGSerialPort().SetExpectReceivedDataLength(6+count/8); + break; + case ModbusRegisterType.HoldingRegister: + ReadRequest = new ModbusRTU_ReadHoldingRegisters(deviceAddress,start,count); + this.EGSendMessage(ReadRequest,serialPort,ProtocolType.SerialPort); + this.EGSerialPort().SetExpectReceivedDataLength(5+count*2); + break; + case ModbusRegisterType.InputRegisters: + ReadRequest = new ModbusRTU_ReadInputRegisters(deviceAddress,start,count); + this.EGSendMessage(ReadRequest,serialPort,ProtocolType.SerialPort); + this.EGSerialPort().SetExpectReceivedDataLength(5+count*2); + break; + } + await Task.Run(async ()=>{ + int timeout = 0; + while(RTUCache.Count==0 && timeout < Delay){ + await Task.Delay(10); + timeout+=10; + } + if(RTUCache.Count>0){ + RTUCache.TryDequeue(out res); + }else{ + //Print Error Timeout + OnReadTimeOut.Invoke(); + } + }); + this.EGSerialPort().ClearReceivedCache(serialPort); + IsRequestRTU = false; + if(this.WaitForSendRTU.Count>0){ + this.WaitForSendRTU.TryDequeue(out NextSendRTU); + } + return res; + } + + public async Task WriteOnceRTUAsync(ModbusRegisterType registerType,string serialPort,byte deviceAddress,ushort registerAddress,object value){ + if(IsRequestRTU){ + SendPointerRTU++; + int messageId = SendPointerRTU; + WaitForSendRTU.Enqueue(messageId); + await Task.Run(async () => + { + while (IsRequestRTU || NextSendRTU != messageId) + { + await Task.Delay(10); + //break; + } + }); + EG.Print("-----Write"+messageId+" ----"); + //return null; + } + RTUCache.Clear(); + IsRequestRTU = true; + IRequest WriteRequest; + ModbusRTU_Response? res = null; + switch(registerType){ + case ModbusRegisterType.Coil: + WriteRequest = new ModbusRTU_WriteSingleCoil(deviceAddress,registerAddress,(bool)value); + this.EGSendMessage(WriteRequest,serialPort,ProtocolType.SerialPort); + this.EGSerialPort().SetExpectReceivedDataLength(WriteRequest.ToProtocolByteData().Length); + break; + case ModbusRegisterType.HoldingRegister: + WriteRequest = new ModbusRTU_WriteSingleHoldingRegister(deviceAddress,registerAddress,(ushort)value); + this.EGSendMessage(WriteRequest,serialPort,ProtocolType.SerialPort); + this.EGSerialPort().SetExpectReceivedDataLength(WriteRequest.ToProtocolByteData().Length); + break; + } + await Task.Run(async ()=>{ + int timeout = 0; + while(RTUCache.Count==0 && timeout < Delay){ + await Task.Delay(10); + timeout+=10; + } + if(RTUCache.Count>0){ + RTUCache.TryDequeue(out res); + }else{ + //Print Error Timeout + OnReadTimeOut.Invoke(); + } + }); + this.EGSerialPort().ClearReceivedCache(serialPort); + IsRequestRTU = false; + if(this.WaitForSendRTU.Count>0){ + this.WaitForSendRTU.TryDequeue(out NextSendRTU); + } + return res; + } + + #endregion + + #region Modbus TCP Operations + private bool IsRequestTCP { set; get; } + public async Task ReadTCPAsync(ModbusRegisterType registerType,string ipPort,byte deviceAddress,ushort start,ushort count){ + if(IsRequestTCP){ + SendPointerTCP++; + int messageId = SendPointerTCP; + WaitForSendTCP.Enqueue(messageId); + await Task.Run(async () => + { + while (IsRequestTCP || NextSendTCP != messageId) + { + await Task.Delay(10); + //break; + } + }); + EG.Print("-----Read"+messageId+" ----"); + //return null; + } + TCPCache.Clear(); + IsRequestTCP = true; + IRequest ReadRequest; + ModbusTCP_Response? res = null; + switch(registerType){ + case ModbusRegisterType.HoldingRegister: + ReadRequest = new ModbusTCP_ReadHoldingRegisters(deviceAddress,start,count); + // this.AppendMessage("【发送-"+DataModbusItem.SerialPort+"】 "+ReadRequest.ToProtocolByteData().ToStringByHex()); + this.EGSendMessage(ReadRequest,ipPort,ProtocolType.TCPClient); + break; + } + await Task.Run(async ()=>{ + int timeout = 0; + while(TCPCache.Count == 0 && timeout < Delay){ + await Task.Delay(10); + timeout += 10; + } + if(TCPCache.Count>0){ + TCPCache.TryDequeue(out res); + }else{ + //Print Error Timeout + OnReadTimeOut.Invoke(); + } + }); + IsRequestTCP = false; + if(this.WaitForSendTCP.Count>0){ + this.WaitForSendTCP.TryDequeue(out NextSendTCP); + } + return res; + } + + public async Task WriteOnceTCPAsync(ModbusRegisterType registerType,string ipPort,byte deviceAddress,ushort registerAddress,object value){ + if(IsRequestTCP){ + SendPointerTCP++; + int messageId = SendPointerTCP; + WaitForSendTCP.Enqueue(messageId); + await Task.Run(async () => + { + while (IsRequestTCP || NextSendTCP != messageId) + { + await Task.Delay(10); + //break; + } + }); + EG.Print("-----Write"+messageId+" ----"); + //return null; + } + TCPCache.Clear(); + IsRequestTCP = true; + IRequest WriteRequest; + ModbusTCP_Response? res = null; + switch(registerType){ + case ModbusRegisterType.Coil: + WriteRequest = new ModbusTCP_WriteSingleCoil(deviceAddress,registerAddress,(bool)value); + this.EGSendMessage(WriteRequest,ipPort,ProtocolType.TCPClient); + break; + case ModbusRegisterType.HoldingRegister: + if(value.GetType() == typeof(float)){ + ushort[] writeData = ((float)value).ToByteArrayBigEndian().ToUShortArray(); + WriteRequest = new ModbusTCP_WriteMultiHoldingRegister(deviceAddress,registerAddress,writeData); + }else{ + WriteRequest = new ModbusTCP_WriteSingleHoldingRegister(deviceAddress,registerAddress,(ushort)value); + } + this.EGSendMessage(WriteRequest,ipPort,ProtocolType.TCPClient); + break; + } + await Task.Run(async ()=>{ + int timeout = 0; + while(TCPCache.Count==0 && timeout < Delay){ + await Task.Delay(10); + timeout+=10; + } + if(TCPCache.Count>0){ + TCPCache.TryDequeue(out res); + }else{ + //Print Error Timeout + OnReadTimeOut.Invoke(); + } + }); + IsRequestTCP = false; + if(this.WaitForSendTCP.Count>0){ + this.WaitForSendTCP.TryDequeue(out NextSendTCP); + } + if(this.WaitForSendTCP.Count>0){ + this.WaitForSendTCP.TryDequeue(out NextSendTCP); + } + return res; + } + #endregion + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } + public static class CanGetEGModbusExtension{ + public static EGModbus EGModbus(this IEGFramework self){ + return self.GetModule(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/OtherTools/EGSqlite.cs b/EGFramework/Module/OtherTools/EGSqlite.cs new file mode 100644 index 0000000..2df6342 --- /dev/null +++ b/EGFramework/Module/OtherTools/EGSqlite.cs @@ -0,0 +1,245 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Microsoft.Data.Sqlite; +using Newtonsoft.Json; + +namespace EGFramework{ + public class EGSqlite : EGModule + { + public string DBName = "Default"; + private string DefaultDBFolder = "SaveData"; + public SqliteConnection SqliteConn; + public string ExceptionMsg; + + public override void Init() + { + if (!Directory.Exists(DefaultDBFolder)) + { + Directory.CreateDirectory(DefaultDBFolder); + } + InitDatabase(DBName); + } + + public void InitDatabase(string dataBaseName) + { + SqliteConn = new SqliteConnection("Data Source="+DefaultDBFolder+"/"+dataBaseName+".db;Mode=ReadWriteCreate;"); // Open the connection: + try + { + SqliteConn.Open(); + } + catch (Exception ex) + { + ExceptionMsg = ex.ToString(); + } + } + + //Save data to default sqlite database; + public void SaveData(TData data) where TData : new() + { + // if table is not exist, create table and insert data to table,else insert into data to table + if(IsTableExist()){ + InsertData(data); + }else{ + CreateTable(); + InsertData(data); + } + } + /// + /// Get data from table where named type of TData + /// + /// Table name + /// + public List GetDataSet() where TData : new() + { + // query dataSet from table TData_List + List dataSet = new List(); + if(IsTableExist()){ + dataSet = SelectData(); + }else{ + ExceptionMsg = "No such table,ensure one data with type of TData has been saved at least!"; + return null; + } + return dataSet; + } + + #region SQL Operation + /// + /// Create table where table name is type of TData + /// + /// + /// + public string CreateTable() where TData: new() { + string result = "Success:"; + try + { + string sqlCommand = "CREATE TABLE " + typeof(TData).Name; + sqlCommand += "(\"ID\" INTEGER NOT NULL UNIQUE,"; + var properties = typeof(TData).GetProperties(); + EG.Print(properties.Count() + " Readed "); + foreach(var property in properties){ + if(property.PropertyType == typeof(int) || property.PropertyType == typeof(bool) || property.PropertyType.IsEnum){ + sqlCommand += "\"" + property.Name + "\" INTEGER" + " NOT NULL,"; + }else if(property.PropertyType == typeof(double) || property.PropertyType == typeof(float)){ + sqlCommand += "\"" + property.Name + "\" REAL" + " NOT NULL,"; + } + else{ + sqlCommand += "\"" + property.Name + "\" TEXT" + " NOT NULL,"; + } + } + sqlCommand += "PRIMARY KEY(\"ID\" AUTOINCREMENT))"; + EG.Print(sqlCommand); + SqliteCommand createCommand = new SqliteCommand(sqlCommand,SqliteConn); + result = result + createCommand.ExecuteNonQuery().ToString(); + } + catch (System.Exception e) + { + return "Error:"+e; + } + return result; + } + + /// + /// Drop table where table name is type of TData + /// + /// + /// + public string DropTable() where TData: new(){ + string result = "Success:"; + try + { + string sqlCommand = "DROP TABLE " + typeof(TData).Name; + SqliteCommand createCommand = new SqliteCommand(sqlCommand,SqliteConn); + result = result + createCommand.ExecuteNonQuery().ToString(); + } + catch (System.Exception e) + { + return "Error:"+e; + } + return result; + } + + /// + /// Insert data to table where table name is type of TData + /// + /// + /// + /// success or error + public string InsertData(TData data) where TData: new(){ + string result = "Success:"; + try + { + string sqlCommand = "INSERT INTO " + typeof(TData).Name; + var properties = typeof(TData).GetProperties(); + Dictionary dataParams = new Dictionary(); + foreach(var property in properties){ + dataParams.Add(property.Name,property.GetValue(data)); + if(property.PropertyType==typeof(bool) || property.PropertyType.IsEnum){ + // If property is bool type , save data to data base should be 0 or 1 instead of false or true; + // If property is Enum type , then transform data to int; + dataParams[property.Name] = System.Convert.ToInt32(dataParams[property.Name]); + }else if(property.PropertyType.IsClass || property.PropertyType.IsValueType && !property.PropertyType.IsPrimitive && property.PropertyType != typeof(string)){ + dataParams[property.Name] = JsonConvert.SerializeObject(dataParams[property.Name]); + } + } + sqlCommand += "("; + string keySet = ""; + foreach(string key in dataParams.Keys){ + keySet += key + ","; + } + keySet = keySet.TrimEnd(','); + sqlCommand += keySet; + sqlCommand += ") VALUES ("; + string valueSet = ""; + foreach(var value in dataParams.Values){ + if(value.GetType() == typeof(int) || value.GetType() == typeof(float) || value.GetType() == typeof(double)){ + valueSet += value + ","; + }else{ + valueSet += "'" + value + "',"; + } + } + valueSet = valueSet.TrimEnd(','); + sqlCommand += valueSet; + sqlCommand += ")"; + SqliteCommand createCommand = new SqliteCommand(sqlCommand,SqliteConn); + result = result + createCommand.ExecuteNonQuery().ToString(); + } + catch (System.Exception e) + { + ExceptionMsg = e.ToString(); + return "Error:"+ExceptionMsg; + } + return result; + } + + /// + /// Query Data and return object list with TData type,Support Data Type:ClassObject,Enum,int,string.float,struct.Not support double,if double then auto convert to float + /// + /// List of TData or null ,if null then you can print ExceptionMsg to check your error + public List SelectData() where TData: new(){ + List resultList = new List(); + try + { + string sqlCommand = "SELECT * FROM " + typeof(TData).Name; + SqliteCommand selectCommand = new SqliteCommand(sqlCommand,SqliteConn); + SqliteDataReader reader = selectCommand.ExecuteReader(); + var properties = typeof(TData).GetProperties(); + + while (reader.Read()) + { + TData dataRow = new TData(); + foreach(var property in properties){ + if(property.PropertyType == reader[property.Name].GetType()){ + property.SetValue(dataRow,reader[property.Name]); + }else if(property.PropertyType.IsEnum){ + object propertyEnum = Enum.Parse(property.PropertyType,reader[property.Name].ToString()); + property.SetValue(dataRow,propertyEnum); + } + else if(property.PropertyType.IsPrimitive) { + object propertyObject = System.Convert.ChangeType(reader[property.Name],property.PropertyType); + property.SetValue(dataRow,propertyObject); + }else{ + object classObject = JsonConvert.DeserializeObject(reader[property.Name].ToString(),property.PropertyType); + property.SetValue(dataRow,classObject); + } + } + resultList.Add(dataRow); + } + } + catch (System.Exception e) + { + ExceptionMsg = e.ToString(); + return null; + } + return resultList; + } + + public bool IsTableExist() where TData:new(){ + try + { + string sqlCommand = "SELECT name FROM sqlite_sequence"; + SqliteCommand selectCommand = new SqliteCommand(sqlCommand,SqliteConn); + SqliteDataReader reader = selectCommand.ExecuteReader(); + while (reader.Read()){ + if(reader["name"].ToString()==typeof(TData).Name){ + return true; + } + } + } + catch (System.Exception e) + { + ExceptionMsg = e.ToString(); + return false; + } + return false; + } + #endregion + } + + public static class CanGetEGSqliteExtension{ + public static EGSqlite EGSqlite(this IEGFramework self){ + return EGArchitectureImplement.Interface.GetModule(); + } + } +} diff --git a/EGFramework/Module/OtherTools/EGWebDav.cs b/EGFramework/Module/OtherTools/EGWebDav.cs new file mode 100644 index 0000000..185b00a --- /dev/null +++ b/EGFramework/Module/OtherTools/EGWebDav.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using WebDav; + + +namespace EGFramework{ + public class EGWebDav : IModule + { + public string ServerUrl { set; get; } = ""; + private string UserName { set; get; } = ""; + private string Password { set; get; } = ""; + public bool IsInit { set; get; } + private WebDavClient WebDavClient { set; get; } + private string CurrentPath { set; get; } = "/"; + + public List CurrentFileList { set; get; } = new List(); + public void Init() + { + + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + + public void InitClient(string serverUrl, string userName,string password){ + this.ServerUrl = serverUrl; + this.UserName = userName; + this.Password = password; + Dictionary headersAdd = new Dictionary + { + { "Connection", "keep-alive" }, + { "Authorization", "Basic "+ EGWebDavExtension.EncodeCredentials(userName,password) } + }; + WebDavClient = new WebDavClient(new WebDavClientParams + { + BaseAddress = new Uri(ServerUrl), + Credentials = new NetworkCredential(userName, password), + DefaultRequestHeaders = headersAdd + }); + Console.WriteLine("Client has been init"); + } + + //---------download or upload from WebDav server---------// + + /// + /// Download a file from dav path + /// + /// Such as /dav/Picture/Picture1.jpg + /// download destination,such as C:\Users\W35\Pictures + /// you can define file by this name,or by uri + /// + public async Task DownloadFile(string downloadUri,string localPath,string fileName = ""){ + if (fileName.Equals("")){ + fileName = Path.GetFileName(downloadUri); + } + using (var response = await WebDavClient.GetRawFile(downloadUri)) + { + if(response.IsSuccessful == true){ + // use response.Stream + using (FileStream DestinationStream = File.Create(localPath + "/" + fileName)) + { + await response.Stream.CopyToAsync(DestinationStream); + //Print("【WebDav】" + fileName + "下载成功!"); + } + return true; + }else{ + return false; + } + } + } + public async Task DownloadFilProcessed(string downloadUri,string localPath,string fileName = ""){ + if (fileName.Equals("")){ + fileName = Path.GetFileName(downloadUri); + } + using (var response = await WebDavClient.GetProcessedFile(downloadUri)) + { + if(response.IsSuccessful == true){ + // use response.Stream + using (FileStream DestinationStream = File.Create(localPath + "/" + fileName)) + { + await response.Stream.CopyToAsync(DestinationStream); + //Print("【WebDav】" + fileName + "下载成功!"); + } + return true; + }else{ + return false; + } + } + } + + /// + /// Upload a file by localUrl + /// + /// Such as C:\Users\W35\Pictures\Picture1.jpg + /// upload destination,such as /dav/Picture + /// you can define file by this name,or by local url + /// + public async Task UploadFile(string localUrl,string uploadPath,string fileName = ""){ + if (fileName.Equals("")){ + fileName = Path.GetFileName(localUrl); + } + // use response.Stream + var result = await WebDavClient.PutFile(uploadPath+"/"+fileName, File.OpenRead(localUrl)); + if(result.IsSuccessful){ + return true; + }else{ + return false; + } + } + + //-----------operate disk-----------// + + /// + /// Default root path is "/",any path should be start with "/" + /// + /// + /// + public async Task> GetList(string currentPath){ + PropfindResponse result = await WebDavClient.Propfind(ServerUrl+currentPath); + List ResultFileList = new List(); + if (result.IsSuccessful) + { + foreach (WebDavResource res in result.Resources) + { + ResultFileList.Add(new WebDavFileMsg{ + FileName = res.DisplayName , + IsCollection = res.IsCollection , + Size = res.ContentLength , + Uri = res.Uri , + LastUpdateTime = res.LastModifiedDate + }); + } + } + return ResultFileList; + } + + /// + /// simple CD command, prop find all file message to CurrentFileList. + /// + /// + /// + public async Task ChangeDictionary(string destinationPath){ + CurrentPath = destinationPath; + PropfindResponse result = await WebDavClient.Propfind(ServerUrl+CurrentPath); + CurrentFileList.Clear(); + if (result.IsSuccessful) + { + foreach (WebDavResource res in result.Resources) + { + CurrentFileList.Add(new WebDavFileMsg{ + FileName = res.DisplayName , + IsCollection = res.IsCollection , + Size = res.ContentLength , + Uri = res.Uri , + LastUpdateTime = res.LastModifiedDate + }); + } + } + } + + /// + /// create a directory + /// + /// + /// + public async Task MakeDictionary(string dictionaryName){ + await WebDavClient.Mkcol(dictionaryName); + } + + /// + /// simple cp command, copy a file with differentName. + /// + /// + /// + /// + public async Task Copy(string sourceFile,string copyFile){ + await WebDavClient.Copy(sourceFile,copyFile); + } + + /// + /// simple mv command, move a file with change fileName or different path. + /// + /// + /// + /// + public async Task Move(string sourceFile,string moveFile){ + await WebDavClient.Move(sourceFile,moveFile); + } + + /// + /// simple rm command,delete a file. + /// + /// + /// + public async Task Remove(string fileName){ + await WebDavClient.Delete(fileName); + } + } + + public struct WebDavFileMsg : IEGFileMsg{ + public string FileName { set; get; } + public bool IsCollection { set; get; } + + /// + /// unit is kb + /// + public long? Size { set; get; } + public string Uri { set; get; } + public DateTime? LastUpdateTime { set; get; } + + public DateTime? LastModify { set; get; } + + public void Init(string fileName, bool isCollection, string uri, long? size = null, DateTime? lastModify = null) + { + this.FileName = fileName; + this.IsCollection = isCollection; + this.Uri = uri; + this.Size = size; + this.LastModify = lastModify; + } + } + + public static class EGWebDavExtension{ + public static EGWebDav EGWebDav(this IEGFramework self) + { + return EGArchitectureImplement.Interface.GetModule(); + } + public static string EncodeCredentials(string username, string password) + { + string credentials = $"{username}:{password}"; + byte[] credentialsBytes = System.Text.Encoding.UTF8.GetBytes(credentials); + string encodedCredentials = Convert.ToBase64String(credentialsBytes); + return encodedCredentials; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/EGMessage.cs b/EGFramework/Module/ProtocolTools/EGMessage.cs new file mode 100644 index 0000000..cf49a1a --- /dev/null +++ b/EGFramework/Module/ProtocolTools/EGMessage.cs @@ -0,0 +1,335 @@ +using System; +using System.Collections.Concurrent; +using System.Timers; + +namespace EGFramework +{ + //before use this module,you should install NewtonSoft.Json Nuget Package + //in vscode,you can install this package by install vscode extension NuGet Package Manager GUI + //search 'Newton' and choose Newtonsoft.Json + //EGMessage, Every Message tools will be based in this class + public class EGMessage : EGModule + { + public EasyEvent OnDataReceived { set; get; } = new EasyEvent(); + public EasyEvent OnResponse { set; get; } = new EasyEvent(); + public EasyEvent OnRequest { set; get; } = new EasyEvent(); + + /// + /// Send delay in millisecond,if you don't need a Timer to delay send message,you can set it to 0. ( this delay options is prevent for sticky package ) + /// + /// + public int SendDelay { set; get; } = 100; + public ConcurrentDictionary> RequestCache { set; get; } = new ConcurrentDictionary>(); + private System.Timers.Timer RequestTimer { set; get; } + + public override void Init() + { + if(SendDelay>0){ + RequestTimer = new System.Timers.Timer(SendDelay); + RequestTimer.Elapsed += ExecuteRequest; + RequestTimer.AutoReset = true; + RequestTimer.Enabled = true; + } + } + #region ReceiveFunctions + private void ReceiveResponse(ResponseMsg msg) where T :IResponse, new() + { + this.ExecuteResponse(new T(),msg.stringData,msg.byteData,msg.sender,msg.protocolType); + } + /// + /// Start to receive type of T data + /// + /// Data class of message + public void OnReceive() where T : IResponse, new() + { + OnDataReceived.Register(ReceiveResponse); + } + /// + /// Stop to receive type of T data + /// + /// Data class of message + public void OffReceive() where T : IResponse, new() + { + OnDataReceived.UnRegister(ReceiveResponse); + } + #endregion + #region request & response + + public void SendRequest(TRequest request,string sender,ProtocolType protocolType) where TRequest:IRequest + { + if(SendDelay>0){ + if(!RequestCache.ContainsKey(sender)){ + RequestCache[sender] = new ConcurrentQueue(); + } + RequestCache[sender].Enqueue(new RequestMsgEvent(request,sender,protocolType)); + }else{ + OnRequest.Invoke(new RequestMsgEvent(request,sender,protocolType)); + } + //ExecuteRequest(); + //OnRequest.Invoke(requestCache.Dequeue()); + } + + private void ExecuteRequest(object source, ElapsedEventArgs e){ + foreach(ConcurrentQueue singleCache in RequestCache.Values){ + if(singleCache.Count>0){ + singleCache.TryDequeue(out RequestMsgEvent msg); + OnRequest.Invoke(msg); + } + } + } + + private void ExecuteResponse(TResponse response,string protocolString,byte[] protocolBytes,string sender,ProtocolType protocolType) where TResponse:IResponse + { + bool isSet = response.TrySetData(protocolString,protocolBytes); + if (isSet) + { + //this.SendEvent(new ResponseMsgEvent(response, sender)); + OnResponse.Invoke(new ResponseMsgEvent(response,sender,protocolType)); + } + } + #endregion + + public void SetDelay(int millisecond){ + this.SendDelay = millisecond; + if(millisecond != 0){ + RequestTimer.Interval = millisecond; + } + } + } + + #region interface + public interface IResponse + { + /// + /// Attempt to fill in the data. If it does not comply with the relevant protocol rules, it is recommended to return false. If false is returned here, the data response will be ignored. + /// + /// original received + /// + bool TrySetData(string protocolData,byte[] protocolBytes); + } + public interface IRequest + { + /// + /// define you message info in this function,and this return will be send to server&client by request. + /// + /// request info + string ToProtocolData(); + + byte[] ToProtocolByteData(); + } + #endregion + #region AbstractClass + + public class BaseJsonResponse : IResponse + { + private string ExceptionMsg; + public virtual string ToProtocolData() + { + return ""; + } + public virtual bool TrySetData(string json,byte[] bytes) + { + try + { + return true; + } + catch (Exception e) + { + ExceptionMsg = e.ToString(); + //PrintErr(ExceptionMsg); + return false; + } + } + } + + public class StringRequest : IRequest + { + private string RequestStr; + public StringRequest() { + RequestStr = "No message"; + } + public StringRequest(string str) { + RequestStr = str; + } + + public byte[] ToProtocolByteData() + { + return null; + } + + public string ToProtocolData() + { + return RequestStr; + } + } + #endregion + #region Extension + public static class CanRegisterMessageExtension { + /// + /// To register event until message received,if you only need message,please use: + /// this.RegisterMessageEvent(e=>{ //To execute your message}) + /// if you want to get sender,you also can to: + /// this.RegisterMessageEvent((res,sender))=>{ //To execute your message + /// Print(res.toProtocolData()); + /// Print(sender); + /// }) + /// + /// + /// + /// + /// + public static IUnRegister EGRegisterMessageEvent(this IEGFramework self, Action onEvent)where TResponse : IResponse + { + return EGArchitectureImplement.Interface.GetModule().OnResponse.Register(e=> { + if (e.res.GetType() == typeof(TResponse)) { + onEvent.Invoke((TResponse)e.res); + } + }); + } + /// + /// To register event until message received,if you only need message,please use: + /// this.RegisterMessageEvent(e=>{ //To execute your message}) + /// if you want to get sender,you also can to: + /// this.RegisterMessageEvent((res,sender))=>{ //To execute your message + /// Print(res.toProtocolData()); + /// Print(sender); + /// }) + /// + /// + /// + /// + /// + public static IUnRegister EGRegisterMessageEvent(this IEGFramework self, Action onEvent)where TResponse : IResponse + { + return EGArchitectureImplement.Interface.GetModule().OnResponse.Register(e=> { + if (e.res.GetType() == typeof(TResponse)) { + onEvent.Invoke((TResponse)e.res,e.sender); + } + }); + } + + public static IUnRegister EGRegisterMessageEvent(this IEGFramework self, Action onEvent)where TResponse : IResponse + { + return EGArchitectureImplement.Interface.GetModule().OnResponse.Register(e=> { + if (e.res.GetType() == typeof(TResponse)) { + onEvent.Invoke((TResponse)e.res,e.sender,e.protocolType); + } + }); + } + /// + /// Start to receive type of TResponse data + /// + /// + /// + public static void EGOnMessage(this IEGFramework self) where TResponse : IResponse,new() + { + EGArchitectureImplement.Interface.GetModule().OnReceive(); + } + /// + /// Stop to receive type of TResponse data + /// + /// + /// + public static void EGOffMessage(this IEGFramework self) where TResponse : IResponse,new() + { + EGArchitectureImplement.Interface.GetModule().OffReceive(); + } + } + public static class CanSendMessageExtension { + /// + /// to send message by request and define sender + /// + /// + /// + /// + /// + public static void EGSendMessage(this IEGFramework self, TRequest request,string sender,ProtocolType protocolType)where TRequest : IRequest + { + EGArchitectureImplement.Interface.GetModule().SendRequest(request,sender,protocolType); + } + } + + /// + /// this extension to link with protocol tools,such as tcp,udp,serial port,etc... + /// + public static class EGMessageEventExtension{ + public static void EGOnReceivedData(this IModule self, ResponseMsg receivedData) + { + EGArchitectureImplement.Interface.GetModule().OnDataReceived.Invoke(receivedData); + } + public static void EGRegisterSendAction(this IModule self, Action sendAction){ + EGArchitectureImplement.Interface.GetModule().OnRequest.Register(sendAction); + } + } + #endregion + #region event + + public struct ResponseMsg + { + public string sender; + public string stringData; + public byte[] byteData; + + public ProtocolType protocolType; + public ResponseMsg(string stringData_,byte[] byteData_,string sender_,ProtocolType protocolType_) + { + stringData = stringData_; + byteData = byteData_; + sender = sender_; + protocolType = protocolType_; + } + } + + public struct ResponseMsgEvent + { + public IResponse res; + public string sender; + public ProtocolType protocolType; + public ResponseMsgEvent(IResponse res_,string sender_,ProtocolType protocolType_) + { + res = res_; + sender = sender_; + protocolType = protocolType_; + } + } + + public struct RequestMsgEvent + { + public IRequest req; + public string sender; + public ProtocolType protocolType; + public RequestMsgEvent(IRequest req_ ,string sender_,ProtocolType protocolType_) + { + req = req_; + sender = sender_; + protocolType = protocolType_; + } + } + #endregion + + public enum ProtocolType{ + TCPClient = 0x00, + TCPServer = 0x01, + UDP = 0x02, + SerialPort = 0x03, + WebSocketClient = 0x10, + WebSocketServer = 0x11, + HttpServer = 0x20, + HttpGet = 0x21, + HttpPost = 0x22, + HttpPut = 0x23, + HttpPatch = 0x24, + HttpDelete = 0x25, + DTLSClient = 0x30, + DTLSServer = 0x31, + SSLClient = 0x40, + SSLServer = 0x41, + FileStream = 0x50, + MemoryStream = 0x60, + MQTTClient = 0x70, + Bacnet = 0x80, + SSHClient = 0x90, + Process = 0xA0, + //MQTT,SSH,etc... + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/EGProtocolSchedule.cs b/EGFramework/Module/ProtocolTools/EGProtocolSchedule.cs new file mode 100644 index 0000000..e01aa0e --- /dev/null +++ b/EGFramework/Module/ProtocolTools/EGProtocolSchedule.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework{ + public class EGProtocolSchedule : IEGFramework,IModule + { + public Dictionary ProtocolTools = new Dictionary(); + + public void Init() + { + + } + public void CheckedProcess() + { + foreach (IProtocolReceived tool in ProtocolTools.Values) + { + if (tool.GetReceivedMsg().Count > 0) + { + bool isDequeue = tool.GetReceivedMsg().TryDequeue(out ResponseMsg msg); + if (isDequeue) + { + this.GetModule().OnDataReceived.Invoke(msg); + } + } + } + } + public void EnabledAllTools(){ + this.EnabledTool(); + this.EnabledTool(); + this.EnabledTool(); + this.EnabledTool(); + this.EnabledTool(); + + } + public void EnabledTool() where TProtocolReceived : class, IModule,IProtocolReceived,new(){ + if(!ProtocolTools.ContainsKey(typeof(TProtocolReceived))){ + ProtocolTools.Add(typeof(TProtocolReceived),this.GetModule()); + } + + } + + public void DisabledAllTools(){ + ProtocolTools.Clear(); + } + + public void DisabledTool() where TProtocolReceived : class, IModule,IProtocolReceived,new(){ + if(ProtocolTools.ContainsKey(typeof(TProtocolReceived))){ + ProtocolTools.Remove(typeof(TProtocolReceived)); + } + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } +} + diff --git a/EGFramework/Module/ProtocolTools/Internet/EGBacnet.cs b/EGFramework/Module/ProtocolTools/Internet/EGBacnet.cs new file mode 100644 index 0000000..3040279 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGBacnet.cs @@ -0,0 +1,573 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO.BACnet; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace EGFramework{ + public class EGBacnet : IEGFramework, IModule,IProtocolSend,IProtocolReceived + { + public BacnetClient BacnetClient; + // All the present Bacnet Device List + public Dictionary DevicesList = new Dictionary(); + public Encoding StringEncoding { set; get; } = Encoding.ASCII; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.Bacnet){ + if(request.req.ToProtocolData() != "" && request.req.ToProtocolData() != null){ + this.SendStringData(request.sender,request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData().Length > 0 && request.req.ToProtocolByteData() != null){ + this.SendByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + StartIPV4(); + } + + public void StartIPV4(int port = 0xBAC0) + { + // Bacnet on UDP/IP/Ethernet + BacnetClient = new BacnetClient(new BacnetIpUdpProtocolTransport(port, false)); + BacnetClient.Start(); // go + BacnetClient.OnIam += new BacnetClient.IamHandler(OnIam); + BacnetClient.WhoIs(); + + // Or Bacnet Ethernet + // bacnet_client = new BacnetClient(new BacnetEthernetProtocolTransport("Connexion au réseau local")); + + // Send WhoIs in order to get back all the Iam responses : + /* Optional Remote Registration as A Foreign Device on a BBMD at @192.168.1.1 on the default 0xBAC0 port + + bacnet_client.RegisterAsForeignDevice("192.168.1.1", 60); + Thread.Sleep(20); + bacnet_client.RemoteWhoIs("192.168.1.1"); + */ + } + public void StartIPV6(int port = 0xBAC0){ + //Bacnet on IPV6 + BacnetClient = new BacnetClient(new BacnetIpV6UdpProtocolTransport(port)); + BacnetClient.Start(); // go + BacnetClient.OnIam += new BacnetClient.IamHandler(OnIam); + BacnetClient.WhoIs(); + } + public void StartMSTP(string serialPort = "COM4",int baudRate = 38400){ + // Bacnet Mstp on COM4 à 38400 bps, own master id 8 + BacnetClient = new BacnetClient(new BacnetMstpProtocolTransport(serialPort, baudRate, 8)); + BacnetClient.Start(); // go + BacnetClient.OnIam += new BacnetClient.IamHandler(OnIam); + BacnetClient.WhoIs(); + } + + + public void OnIam(BacnetClient sender, BacnetAddress adr, uint device_id, uint max_apdu, BacnetSegmentations segmentation, ushort vendor_id) + { + lock (DevicesList) + { + // Not already in the list + if(DevicesList.ContainsKey(device_id)){ + DevicesList[device_id]=adr; + }else{ + DevicesList.Add(device_id,adr); // add it + } + if(!IsWaitForIam){ + WaitForIamResponse(); + } + } + } + public bool IsWaitForIam { set; get; } = false; + public async void WaitForIamResponse(){ + IsWaitForIam = true; + await Task.Delay(2000); + EG.Print("-----Get device----"); + string serializeData = JsonConvert.SerializeObject(new EGBacnetWhoIsResponse(DevicesList.Keys.ToList())); + this.ResponseMsgs.Enqueue(new ResponseMsg(serializeData,StringEncoding.GetBytes(serializeData),"Who Is",ProtocolType.Bacnet)); + IsWaitForIam = false; + } + + public bool ReadProperty(BacnetAddress bacnetAddress, EGBacnetRequest bacnetData, out IList Value) + { + IList NoScalarValue; + Value = new List(); + // Property Read + if (BacnetClient.ReadPropertyRequest(bacnetAddress, new BacnetObjectId(bacnetData.ObjectTypes, bacnetData.RegisterAddress), bacnetData.PropertyIds, out NoScalarValue)==false) + return false; + + Value = NoScalarValue; + return true; + } + public bool WriteProperty(BacnetAddress bacnetAddress,EGBacnetRequest bacnetData) + { + // Property Write + BacnetValue[] NoScalarValue = { new BacnetValue(bacnetData.Value) }; + //GD.Print("ValueType is " + bacnetData.ValueType+"|" +bacnetData.Value.GetType()); + if (BacnetClient.WritePropertyRequest(bacnetAddress, new BacnetObjectId(bacnetData.ObjectTypes, bacnetData.RegisterAddress), bacnetData.PropertyIds, NoScalarValue) == false) + return false; + return true; + } + public bool ExecuteBacnetData(EGBacnetRequest bacnetRequest){ + bool executeResult = false; + EG.Print("Execute bacnet request"); + if(bacnetRequest.OperateCode == EGBacnetOperateCode.WhoIsRequest){ + EG.Print("Request for Who Is"); + BacnetClient.WhoIs(); + return true; + } + if(!DevicesList.ContainsKey(bacnetRequest.DeviceId)){ + return false; + } + BacnetAddress address = DevicesList[bacnetRequest.DeviceId]; + IList bacnetValueQuery = null; + switch(bacnetRequest.OperateCode){ + case EGBacnetOperateCode.ReadPropertyRequest: + executeResult = ReadProperty(address,bacnetRequest,out bacnetValueQuery); + break; + case EGBacnetOperateCode.WritePropertyRequest: + executeResult = WriteProperty(address,bacnetRequest); + break; + } + EGBacnetResponse response = new EGBacnetResponse(bacnetRequest,bacnetValueQuery); + response.IsSuccess = executeResult; + string serializeData = JsonConvert.SerializeObject(response); + this.ResponseMsgs.Enqueue(new ResponseMsg(serializeData,StringEncoding.GetBytes(serializeData),bacnetRequest.DeviceId.ToString(),ProtocolType.Bacnet)); + return executeResult; + } + + public EGBacnetWhoIsResponse WhoIs(){ + return new EGBacnetWhoIsResponse(DevicesList.Keys.ToList()); + } + + public EGBacnetResponse ReadRegisterProperty(EGBacnetRequest bacnetRequest){ + bool executeResult = false; + try + { + BacnetAddress address = DevicesList[bacnetRequest.DeviceId]; + IList bacnetValueQuery = null; + switch(bacnetRequest.OperateCode){ + case EGBacnetOperateCode.ReadPropertyRequest: + executeResult = ReadProperty(address,bacnetRequest,out bacnetValueQuery); + break; + + } + EG.Print(bacnetValueQuery[0].Value.GetType()); + EGBacnetResponse response = new EGBacnetResponse(bacnetRequest,bacnetValueQuery); + response.IsSuccess = executeResult; + return response; + } + catch (System.Exception e) + { + EGBacnetResponse response = new EGBacnetResponse(bacnetRequest); + response.IsSuccess = executeResult; + response.FailedReason = e.ToString(); + return response; + } + } + + public EGBacnetResponse WriteRegisterProperty(EGBacnetRequest bacnetRequest){ + bool executeResult = false; + try + { + if(bacnetRequest.ValueType != BacnetApplicationTags.BACNET_APPLICATION_TAG_NULL){ + bacnetRequest.Value = bacnetRequest.Value.ConvertBacnetValueType(bacnetRequest.ValueType); + } + EG.Print(bacnetRequest.Value.GetType()); + BacnetAddress address = DevicesList[bacnetRequest.DeviceId]; + switch(bacnetRequest.OperateCode){ + case EGBacnetOperateCode.WritePropertyRequest: + executeResult = WriteProperty(address,bacnetRequest); + break; + } + EGBacnetResponse response = new EGBacnetResponse(bacnetRequest); + response.IsSuccess = executeResult; + return response; + } + catch (System.Exception e) + { + EGBacnetResponse response = new EGBacnetResponse(bacnetRequest); + response.IsSuccess = executeResult; + response.FailedReason = e.ToString(); + return response; + } + } + + public EGBacnetResponseReadMulti ReadRegisterMulti(EGBacnetRequestReadMulti bacnetRequest){ + bool executeResult = false; + try + { + if(!DevicesList.ContainsKey(bacnetRequest.DeviceId)){ + return new EGBacnetResponseReadMulti(){ + FailedReason = "Device " + bacnetRequest.DeviceId + " is offline!" + }; + } + BacnetAddress address = DevicesList[bacnetRequest.DeviceId]; + List resultSet = new List(); + switch(bacnetRequest.OperateCode){ + case EGBacnetOperateCode.ReadMultiRequest: + foreach(EGBacnetRegisterInfo info in bacnetRequest.RegisterInfos){ + EGBacnetRegisterValueInfo result = ReadRegisterOne(address,info); + resultSet.Add(result); + } + executeResult = true; + break; + } + EGBacnetResponseReadMulti response = new EGBacnetResponseReadMulti(resultSet); + response.IsSuccess = executeResult; + return response; + } + catch (System.Exception e) + { + return new EGBacnetResponseReadMulti(){ + FailedReason = e.ToString() + }; + } + + } + public EGBacnetRegisterValueInfo ReadRegisterOne(BacnetAddress bacnetAddress, EGBacnetRegisterInfo registerInfo){ + EGBacnetRegisterValueInfo value = new EGBacnetRegisterValueInfo(){ + ObjectTypes = registerInfo.ObjectTypes, + RegisterAddress = registerInfo.RegisterAddress, + PropertyIds = registerInfo.PropertyIds + }; + IList NoScalarValue; + try + { + bool IsSuccess = BacnetClient.ReadPropertyRequest(bacnetAddress, new BacnetObjectId(registerInfo.ObjectTypes, registerInfo.RegisterAddress), registerInfo.PropertyIds, out NoScalarValue); + if(IsSuccess){ + value.IsSuccess = true; + value.Value = NoScalarValue[0]; + } + } + catch (System.Exception e) + { + EG.Print(e); + throw; + } + return value; + } + + public void SendByteData(string destination, byte[] data) + { + try + { + string DataJson = StringEncoding.GetString(data); + SendStringData(destination,DataJson); + } + catch (System.Exception) + { + throw; + } + } + + public void SendStringData(string destination, string data) + { + try + { + EGBacnetRequest bacnetData = JsonConvert.DeserializeObject(data); + ExecuteBacnetData(bacnetData); + } + catch (System.Exception e) + { + EG.Print("Error:"+e); + throw; + } + } + + public void SetEncoding(Encoding textEncoding) + { + this.StringEncoding = textEncoding; + } + + public ConcurrentQueue GetReceivedMsg() + { + return this.ResponseMsgs; + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } + + #region Who Is + public class EGBacnetWhoIsResponse : IResponse,IRequest + { + public string FunctionCode { set; get; } = "WhoIsResponse"; + public List DevicesList = new List(); + public EGBacnetWhoIsResponse(){ + } + public EGBacnetWhoIsResponse(List devicesList){ + this.DevicesList = devicesList; + } + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + EGBacnetWhoIsResponse response = JsonConvert.DeserializeObject(protocolData); + if(response.FunctionCode != FunctionCode){ + return false; + } + this.DevicesList = response.DevicesList; + //throw new System.NotImplementedException(); + return true; + } + catch (System.Exception e) + { + EG.Print("Who is error:"+ e); + return false; + } + } + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this,Formatting.Indented); + } + + public byte[] ToProtocolByteData() + { + return null; + } + } + #endregion + + #region Read&Write One Register + public class EGBacnetRequest : IRequest,IResponse{ + public string FunctionCode { set; get; } = "OperateRequest"; + public EGBacnetOperateCode OperateCode { set; get; } + public uint DeviceId { set; get; } + public BacnetObjectTypes ObjectTypes { set; get; } + public uint RegisterAddress { set; get; } + public BacnetPropertyIds PropertyIds { set; get; } + public object Value { set; get; } + public BacnetApplicationTags ValueType { set; get; } + + public byte[] ToProtocolByteData() + { + return null; + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this,Formatting.Indented); + } + + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + //GD.Print("Init Request"); + EGBacnetRequest request = JsonConvert.DeserializeObject(protocolData); + if(request.FunctionCode != FunctionCode){ + return false; + } + //GD.Print("OperateCode is " + request.OperateCode); + this.OperateCode = request.OperateCode; + this.DeviceId = request.DeviceId; + this.ObjectTypes = request.ObjectTypes; + this.RegisterAddress = request.RegisterAddress; + this.PropertyIds = request.PropertyIds; + this.Value = request.Value; + this.ValueType = request.ValueType; + return true; + } + catch (System.Exception e) + { + EG.Print("Error:"+e); + return false; + } + } + } + public class EGBacnetResponse : IResponse,IRequest + { + public string FunctionCode { set; get; } = "OperateResponse"; + public bool IsSuccess { set; get; } = false; + public EGBacnetRequest Request { set; get; } + public IList ValueQuery { set; get; } + public string FailedReason { set; get; } + + public EGBacnetResponse(){ + + } + public EGBacnetResponse(EGBacnetRequest request){ + this.Request = request; + } + public EGBacnetResponse(EGBacnetRequest request,IList values){ + this.Request = request; + this.ValueQuery = values; + } + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + EGBacnetResponse response = JsonConvert.DeserializeObject(protocolData); + if(response.FunctionCode != FunctionCode){ + return false; + } + this.IsSuccess = response.IsSuccess; + this.Request = response.Request; + this.ValueQuery = response.ValueQuery; + return true; + } + catch (System.Exception) + { + return false; + } + //throw new System.NotImplementedException(); + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this,Formatting.Indented); + } + + public byte[] ToProtocolByteData() + { + return null; + } + } + + #endregion + + #region Multi Operate + public class EGBacnetRequestReadMulti : IRequest,IResponse{ + public string FunctionCode { set; get; } = "ReadMultiRequest"; + public EGBacnetOperateCode OperateCode { set; get; } + public uint DeviceId { set; get; } + public List RegisterInfos { set; get; } = new List(); + + public byte[] ToProtocolByteData() + { + return null; + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this,Formatting.Indented); + } + + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + //GD.Print("Init Request"); + EGBacnetRequestReadMulti request = JsonConvert.DeserializeObject(protocolData); + if(request.FunctionCode != FunctionCode){ + return false; + } + //GD.Print("OperateCode is " + request.OperateCode); + this.OperateCode = request.OperateCode; + this.DeviceId = request.DeviceId; + this.RegisterInfos = request.RegisterInfos; + return true; + } + catch (System.Exception e) + { + EG.Print("Error:"+e); + return false; + } + } + } + + public class EGBacnetResponseReadMulti : IResponse,IRequest + { + public string FunctionCode { set; get; } = "ReadMultiResponse"; + public bool IsSuccess { set; get; } = false; + public List RegisterInfos { set; get; } = new List(); + public string FailedReason { set; get; } + + public EGBacnetResponseReadMulti(){} + + public EGBacnetResponseReadMulti(List valueSet){ + this.RegisterInfos = valueSet; + } + + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + EGBacnetResponseReadMulti response = JsonConvert.DeserializeObject(protocolData); + if(response.FunctionCode != FunctionCode){ + FailedReason = "[Error] FunctionCode is error!"; + return false; + } + this.IsSuccess = response.IsSuccess; + this.RegisterInfos = response.RegisterInfos; + this.FailedReason = response.FailedReason; + return true; + } + catch (System.Exception e) + { + EG.Print("Error:"+e); + return false; + } + //throw new System.NotImplementedException(); + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this,Formatting.Indented); + } + + public byte[] ToProtocolByteData() + { + return null; + } + } + + + public struct EGBacnetRegisterInfo{ + public BacnetObjectTypes ObjectTypes { set; get; } + public uint RegisterAddress { set; get; } + public BacnetPropertyIds PropertyIds { set; get; } + } + + public struct EGBacnetRegisterValueInfo{ + public BacnetObjectTypes ObjectTypes { set; get; } + public uint RegisterAddress { set; get; } + public BacnetPropertyIds PropertyIds { set; get; } + public bool IsSuccess { set; get; } + public BacnetValue Value{ set; get; } + } + + #endregion + + public enum EGBacnetOperateCode{ + ReadPropertyRequest = 0, + WritePropertyRequest = 1, + WhoIsRequest = 2, + ReadMultiRequest = 3, + } + + public static class CanGetEGBacnetExtension{ + public static EGBacnet EGBacnet(this IEGFramework self){ + return self.GetModule(); + } + + public static object ConvertBacnetValueType(this object value,BacnetApplicationTags valueType){ + object resultValue = value; + switch(valueType){ + case BacnetApplicationTags.BACNET_APPLICATION_TAG_BOOLEAN: + resultValue = Convert.ToBoolean(value); + break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_UNSIGNED_INT: + resultValue = Convert.ToUInt32(value); + break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_SIGNED_INT: + resultValue = Convert.ToInt32(value); + break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_REAL: + resultValue = Convert.ToSingle(value); + break; + case BacnetApplicationTags.BACNET_APPLICATION_TAG_DOUBLE: + resultValue = Convert.ToDouble(value); + break; + } + return resultValue; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/Internet/EGHttpClient.cs b/EGFramework/Module/ProtocolTools/Internet/EGHttpClient.cs new file mode 100644 index 0000000..846957d --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGHttpClient.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.Drawing.Printing; +using System.IO; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; + +namespace EGFramework{ + public class EGHttpClient : IEGFramework, IModule + { + public HttpClient HTTPClient { set; get; } = new HttpClient(); + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public void Init() + { + + } + + public async Task HttpRequest(string hostUrl,IRequest request,ProtocolType protocolType = ProtocolType.HttpGet,EGFormData formData = null) where TResponse : IResponse,new(){ + // Call asynchronous network methods in a try/catch block to handle exceptions. + try + { + HttpContent httpContent = new StringContent("",Encoding.UTF8,"application/json"); + if(formData != null){ + MultipartFormDataContent formContent = new MultipartFormDataContent(); + foreach(KeyValuePair pair in formData.FormStrings){ + formContent.Add(new StringContent(pair.Value,Encoding.UTF8),pair.Key); + } + foreach(KeyValuePair pair in formData.FormBytes){ + formContent.Add(new ByteArrayContent(pair.Value),pair.Key,pair.Key+"."+formData.Suffix); + } + foreach(KeyValuePair pair in formData.FormStreams){ + formContent.Add(new StreamContent(pair.Value),pair.Key,pair.Key+"."+formData.Suffix); + } + httpContent = formContent; + } + else if(request.ToProtocolData() != null && request.ToProtocolData() != ""){ + httpContent = new StringContent(request.ToProtocolData(),Encoding.UTF8,"application/json"); + }else if (request.ToProtocolByteData() != null){ + httpContent = new ByteArrayContent(request.ToProtocolByteData()); + } + HttpResponseMessage httpResponse; + switch(protocolType){ + case ProtocolType.HttpGet: + httpResponse = await HTTPClient.GetAsync(hostUrl); + break; + case ProtocolType.HttpPost: + httpResponse = await HTTPClient.PostAsync(hostUrl,httpContent); + break; + case ProtocolType.HttpPut: + httpResponse = await HTTPClient.PutAsync(hostUrl,httpContent); + break; + case ProtocolType.HttpPatch: + httpResponse = await HTTPClient.PatchAsync(hostUrl,httpContent); + break; + case ProtocolType.HttpDelete: + httpResponse = await HTTPClient.DeleteAsync(hostUrl); + break; + default: + httpResponse = await HTTPClient.GetAsync(hostUrl); + break; + } + httpResponse.EnsureSuccessStatusCode(); + byte[] responseBytes = await httpResponse.Content.ReadAsByteArrayAsync(); + string responseBody = await httpResponse.Content.ReadAsStringAsync(); + TResponse response = new TResponse(); + response.TrySetData(responseBody,responseBytes); + return response; + } + catch (HttpRequestException e) + { + EG.Print("Exception Message : "+e.Message); + return default; + } + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } + + public class EGFormData{ + public string Suffix = ""; + public Dictionary FormStrings = new Dictionary(); + public Dictionary FormBytes = new Dictionary(); + public Dictionary FormStreams = new Dictionary(); + } + public static class CanGetEGHttpClientExtension{ + public static EGHttpClient EGHttpClient(this IEGFramework self){ + return self.GetModule(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/Internet/EGHttpServer.cs b/EGFramework/Module/ProtocolTools/Internet/EGHttpServer.cs new file mode 100644 index 0000000..0840b34 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGHttpServer.cs @@ -0,0 +1,148 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json; + +namespace EGFramework{ + public class EGHttpServer : IEGFramework, IModule + { + public HttpListener HttpServer { set; get; } + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public Dictionary ResponsePools { set; get; } = new Dictionary(); + + public Dictionary> ExecuteDelegates { set; get; } = new Dictionary>(); + public void Init() + { + + } + + /// + /// if you are in Win7 or newest system, you should add prefix in urlacl by cmd for example: netsh http add urlacl url=http://+:6555/index/ user=Everyone + /// + /// + /// + public async void Listen(string prefix,Func responseFunc){ + ExecuteDelegates.Add(prefix,responseFunc); + if(!HttpListener.IsSupported){ + return; + } + if(HttpServer == null){ + HttpServer = new HttpListener(); + } + HttpServer.Prefixes.Add(prefix); + if(!HttpServer.IsListening){ + HttpServer.Start(); + //GD.Print("Http listened in :" + prefix); + while(true){ + HttpListenerContext context = await HttpServer.GetContextAsync(); + HttpListenerRequest request = context.Request; + string responseKey = ""; + ResponseMsg receivedMsgs = new ResponseMsg(); + try + { + switch (request.HttpMethod) + { + case "POST": + { + Stream stream = context.Request.InputStream; + StreamReader reader = new StreamReader(stream, Encoding.UTF8); + // byte[] postBuffer = new byte[stream.Length]; + // stream.Read(postBuffer,0,postBuffer.Length); + string postString = reader.ReadToEnd(); + receivedMsgs = new ResponseMsg(postString,new byte[1],"POST", ProtocolType.HttpServer); + } + break; + case "GET": + { + NameValueCollection data = request.QueryString; + Dictionary getDic = data.AllKeys.ToDictionary(k=>k,k=>data[k]); + string getData = JsonConvert.SerializeObject(getDic); + byte[] getBuffer = StringEncoding.GetBytes(getData); + responseKey = "GET:"+this.GetDateTime().ToString(); + receivedMsgs = new ResponseMsg(getData,getBuffer,responseKey, ProtocolType.HttpServer); + //GD.Print("Received from "+receivedMsgs.sender+": "+receivedMsgs.stringData); + } + break; + case "PUT": + { + Stream stream = context.Request.InputStream; + StreamReader reader = new StreamReader(stream, Encoding.UTF8); + byte[] postBuffer = new byte[stream.Length]; + stream.Read(postBuffer,0,postBuffer.Length); + string postData = reader.ReadToEnd(); + receivedMsgs = new ResponseMsg(postData,postBuffer,"PUT", ProtocolType.HttpServer); + } + break; + case "PATCH": + { + Stream stream = context.Request.InputStream; + StreamReader reader = new StreamReader(stream, Encoding.UTF8); + byte[] postBuffer = new byte[stream.Length]; + stream.Read(postBuffer,0,postBuffer.Length); + string postData = reader.ReadToEnd(); + receivedMsgs = new ResponseMsg(postData,postBuffer,"PATCH", ProtocolType.HttpServer); + } + break; + case "DELETE": + { + NameValueCollection data = request.QueryString; + Dictionary getDic = data.AllKeys.ToDictionary(k=>k,k=>data[k]); + string getData = JsonConvert.SerializeObject(getDic); + byte[] getBuffer = StringEncoding.GetBytes(getData); + receivedMsgs = new ResponseMsg(getData,getBuffer,"DELETE", ProtocolType.HttpServer); + } + break; + } + } + catch (System.Exception e) + { + receivedMsgs.stringData = "Exception:"+e; + throw; + } + HttpListenerResponse response = context.Response; + response.AppendHeader("Access-Control-Allow-Origin", "*"); + response.AppendHeader("Access-Control-Allow-Credentials", "true"); + response.AppendHeader("Server", "MyIIS"); + response.StatusCode = 200; + byte[] buffer = StringEncoding.GetBytes("API not found!"); + if(ExecuteDelegates.ContainsKey(request.Url.ToString()+"/")){ + IRequest result = ExecuteDelegates[request.Url.ToString()+"/"].Invoke(receivedMsgs); + buffer = StringEncoding.GetBytes(result.ToProtocolData()); + } + System.IO.Stream output = response.OutputStream; + await output.WriteAsync(buffer, 0, buffer.Length); + output.Close(); + // if(!ResponsePools.ContainsKey("")){ + // ResponsePools.Add("",response); + // } + // Response("","Hello world"); + } + + } + EG.Print("Server Overed"); + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + + } + + public static class CanGetEGHttpServerExtension{ + public static EGHttpServer EGHttpServer(this IEGFramework self){ + return self.GetModule(); + } + + public static void EGHttpServerListen(this IEGFramework self ,string prefix,Func responseFunc){ + self.GetModule().Listen(prefix,responseFunc); + } + } + +} diff --git a/EGFramework/Module/ProtocolTools/Internet/EGMQTT.cs b/EGFramework/Module/ProtocolTools/Internet/EGMQTT.cs new file mode 100644 index 0000000..b2d6b0e --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGMQTT.cs @@ -0,0 +1,163 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using MQTTnet; +using MQTTnet.Client; + +//Important: +//This EGModule implement by the nuget package MQTTnet: +//the project url is: https://github.com/dotnet/MQTTnet +//license is : https://github.com/dotnet/MQTTnet/blob/master/LICENSE by MIT license +namespace EGFramework{ + public class EGMqtt : IEGFramework, IModule, IProtocolSend, IProtocolReceived + { + public MqttFactory MqttFactory = new MqttFactory(); + public Dictionary MqttDevices { set; get; } = new Dictionary(); + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public EasyEvent OnMqttConnect { set; get; } = new EasyEvent(); + + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.MQTTClient){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + this.SendStringData(request.sender.GetStrFrontSymbol('|'),request.sender.GetStrBehindSymbol('|'),request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + this.SendByteData(request.sender.GetStrFrontSymbol('|'),request.sender.GetStrBehindSymbol('|'),request.req.ToProtocolByteData()); + } + } + }); + } + + public async void ConnectMQTTServer(string serverURL){ + if(!MqttDevices.ContainsKey(serverURL)){ + IMqttClient mqttClient = MqttFactory.CreateMqttClient(); + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer(serverURL).Build(); + mqttClient.ApplicationMessageReceivedAsync += e => + { + byte[] receivedBytes = e.ApplicationMessage.PayloadSegment.ToArray(); + ResponseMsgs.Enqueue(new ResponseMsg(StringEncoding.GetString(receivedBytes),receivedBytes,serverURL + "|" + e.ApplicationMessage.Topic,ProtocolType.MQTTClient)); + //GD.Print(e.ApplicationMessage.Topic+":"+e.ApplicationMessage.PayloadSegment.ToArray().ToStringByHex()); + return Task.CompletedTask; + }; + await mqttClient.ConnectAsync(mqttClientOptions,CancellationToken.None); + MqttDevices.Add(serverURL,mqttClient); + EG.Print("Success Connect!"+MqttDevices[serverURL].IsConnected); + OnMqttConnect.Invoke(serverURL); + }else{ + if(!MqttDevices[serverURL].IsConnected){ + var mqttClientOptions = new MqttClientOptionsBuilder().WithTcpServer(serverURL).Build(); + await MqttDevices[serverURL].ConnectAsync(mqttClientOptions,CancellationToken.None); + EG.Print("Success Connect!"+MqttDevices[serverURL].IsConnected); + OnMqttConnect.Invoke(serverURL); + }else{ + EG.Print("Server has been Connected"+MqttDevices[serverURL].IsConnected); + OnMqttConnect.Invoke(serverURL); + } + } + } + + public async void DisconnectMQTTServer(string serverURL){ + if(MqttDevices.ContainsKey(serverURL) && MqttDevices[serverURL].IsConnected){ + await MqttDevices[serverURL].DisconnectAsync(new MqttClientDisconnectOptionsBuilder().WithReason(MqttClientDisconnectOptionsReason.NormalDisconnection).Build()); + }else{ + EG.Print("Not connect"); + } + } + + public async void SubScribeTheme(string serverURL,string Theme){ + MqttClientSubscribeOptions mqttSubscribeOptions = MqttFactory.CreateSubscribeOptionsBuilder() + .WithTopicFilter( + f => + { + f.WithTopic(Theme); + }) + .Build(); + if(MqttDevices.ContainsKey(serverURL) && MqttDevices[serverURL].IsConnected){ + await MqttDevices[serverURL].SubscribeAsync(mqttSubscribeOptions,CancellationToken.None); + EG.Print("Subscribe "+Theme+" success!"); + }else{ + EG.Print("Not connect"); + } + } + + public async void UnSubScribeTheme(string serverURL,string Theme){ + MqttClientUnsubscribeOptions mqttUnSubscribeOptions = MqttFactory.CreateUnsubscribeOptionsBuilder() + .WithTopicFilter(Theme) + .Build(); + if(MqttDevices.ContainsKey(serverURL) && MqttDevices[serverURL].IsConnected){ + await MqttDevices[serverURL].UnsubscribeAsync(mqttUnSubscribeOptions,CancellationToken.None); + } + } + + public async void PublishTheme(string serverURL,string Theme,string Data){ + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic(Theme) + .WithPayload(Data) + .Build(); + if(MqttDevices.ContainsKey(serverURL) && MqttDevices[serverURL].IsConnected){ + await MqttDevices[serverURL].PublishAsync(applicationMessage, CancellationToken.None); + } + } + + public async void PublishTheme(string serverURL,string Theme,byte[] Data){ + var applicationMessage = new MqttApplicationMessageBuilder() + .WithTopic(Theme) + .WithPayload(Data) + .Build(); + if(MqttDevices.ContainsKey(serverURL) && MqttDevices[serverURL].IsConnected){ + await MqttDevices[serverURL].PublishAsync(applicationMessage, CancellationToken.None); + EG.Print("publish success!"); + } + } + + public void SendByteData(string serverURL,string theme, byte[] data) + { + this.PublishTheme(serverURL,theme,data); + } + public void SendByteData(string destination, byte[] data) + { + this.SendByteData(destination.GetStrFrontSymbol('|'),destination.GetStrBehindSymbol('|'),data); + } + + public void SendStringData(string serverURL,string theme, string data) + { + this.PublishTheme(serverURL,theme,data); + } + public void SendStringData(string destination, string data) + { + this.SendStringData(destination.GetStrFrontSymbol('|'),destination.GetStrBehindSymbol('|'),data); + } + + public void SetEncoding(Encoding textEncoding) + { + StringEncoding = textEncoding; + } + public ConcurrentQueue GetReceivedMsg() + { + return ResponseMsgs; + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } + + public static class CanGetEGMqttExtension{ + public static EGMqtt EGMqtt(this IEGFramework self){ + return self.GetModule(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/Internet/EGSsh.cs b/EGFramework/Module/ProtocolTools/Internet/EGSsh.cs new file mode 100644 index 0000000..573e240 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGSsh.cs @@ -0,0 +1,179 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Renci.SshNet; + +namespace EGFramework{ + public class EGSsh : IModule, IProtocolSend, IProtocolReceived + { + public Dictionary SshClientDevices { set; get; } = new Dictionary(); + + public string ErrorLogs { set; get; } + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public int TimeOutDelay = 5000; + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.SSHClient){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + this.SendStringData(request.sender,request.req.ToProtocolData()); + }else if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + this.SendByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + } + + /// + /// Connect Ssh Server by using username and passwd + /// + public async Task ConnectSsh(string host,string username,string password){ + try{ + //Reconnect + if(SshClientDevices.ContainsKey(host)){ + if(SshClientDevices[host].IsConnected){ + return true; + }else{ + CancellationTokenSource sourceReconnect = new CancellationTokenSource(); + CancellationToken tokenReconnect = sourceReconnect.Token; + tokenReconnect.Register(() => EG.Print("Ssh connect timeout!")); + await SshClientDevices[host].ConnectAsync(tokenReconnect); + if(!SshClientDevices[host].IsConnected){ + return false; + }else{ + return true; + } + } + } + //First Connect + SshClient client = new SshClient(host, username, password); + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken token = source.Token; + token.Register(() => EG.Print("Ssh connect timeout!")); + source.CancelAfter(TimeOutDelay); + await client.ConnectAsync(token); + if(!client.IsConnected){ + return false; + }else{ + SshClientDevices.Add(host,client); + } + return true; + } + catch(Exception e){ + ErrorLogs = "[ssh connect error]" + e.ToString(); + return false; + } + } + + /// + /// Connect Ssh Server by using private key file + /// + public async Task ConnectSsh(string host,string username,PrivateKeyFile keyFile){ + try{ + //Reconnect + if(SshClientDevices.ContainsKey(host)){ + if(SshClientDevices[host].IsConnected){ + return true; + }else{ + CancellationTokenSource sourceReconnect = new CancellationTokenSource(); + CancellationToken tokenReconnect = sourceReconnect.Token; + tokenReconnect.Register(() => EG.Print("Ssh connect timeout!")); + await SshClientDevices[host].ConnectAsync(tokenReconnect); + if(!SshClientDevices[host].IsConnected){ + return false; + }else{ + return true; + } + } + } + //First Connect + SshClient client = new SshClient(host, username, keyFile); + CancellationTokenSource source = new CancellationTokenSource(); + CancellationToken token = source.Token; + token.Register(() => EG.Print("Ssh connect timeout!")); + source.CancelAfter(TimeOutDelay); + await client.ConnectAsync(token); + if(!client.IsConnected){ + return false; + }else{ + SshClientDevices.Add(host,client); + } + return true; + } + catch(Exception e){ + ErrorLogs = "[ssh connect error]" + e.ToString(); + return false; + } + } + + + public void DisconnectSsh(string host){ + if(SshClientDevices.ContainsKey(host)){ + if (SshClientDevices[host].IsConnected) + { + SshClientDevices[host].Disconnect(); + SshClientDevices[host].Dispose(); + SshClientDevices.Remove(host); + } + } + } + + public ConcurrentQueue GetReceivedMsg() + { + return ResponseMsgs; + } + + public void SendByteData(string destination, byte[] data) + { + if(SshClientDevices.ContainsKey(destination)){ + if (SshClientDevices[destination].IsConnected) + { + SshCommand cmd = SshClientDevices[destination].RunCommand(StringEncoding.GetString(data)); + ResponseMsg receivedMsgs = new ResponseMsg(cmd.Result,StringEncoding.GetBytes(cmd.Result),destination, ProtocolType.SSHClient); + ResponseMsgs.Enqueue(receivedMsgs); + } + } + } + + public void SendStringData(string destination, string data) + { + if(SshClientDevices.ContainsKey(destination)){ + if (SshClientDevices[destination].IsConnected) + { + SshCommand cmd = SshClientDevices[destination].RunCommand(data); + ResponseMsg receivedMsgs = new ResponseMsg(cmd.Result,StringEncoding.GetBytes(cmd.Result),destination, ProtocolType.SSHClient); + ResponseMsgs.Enqueue(receivedMsgs); + } + } + } + + public void SetEncoding(Encoding textEncoding) + { + this.StringEncoding = textEncoding; + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + //throw new System.NotImplementedException(); + } + } + + public static class CanGetEGSShClientExtension{ + public static EGSsh EGSsh(this IEGFramework self){ + return self.GetModule(); + } + + public static SshClient EGGetSshClient(this IEGFramework self,string host){ + return self.GetModule().SshClientDevices[host]; + } + } + +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/Internet/EGTCPClient.cs b/EGFramework/Module/ProtocolTools/Internet/EGTCPClient.cs new file mode 100644 index 0000000..e99bbfe --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGTCPClient.cs @@ -0,0 +1,182 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Text; +using System.Net.Sockets; +using System.Threading.Tasks; +using System.Net; + +namespace EGFramework{ + public class EGTCPClient : IModule, IEGFramework, IProtocolSend, IProtocolReceived + { + public Dictionary TCPClientDevices { set; get; } = new Dictionary(); + + public string ErrorLogs { set; get; } + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.TCPClient){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + this.SendStringData(request.sender.GetHostByIp(),request.sender.GetPortByIp(),request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + this.SendByteData(request.sender.GetHostByIp(),request.sender.GetPortByIp(),request.req.ToProtocolByteData()); + } + } + }); + } + + /// + /// Connect Tcp client to server with check if target server is listened. + /// + public async Task ConnectTCP(string host,int port){ + try{ + if(!TCPClientDevices.ContainsKey(host + ":" + port)){ + TcpClient tcpClient = new TcpClient(); + await tcpClient.ConnectAsync(host,port); + //Print("Connect Tcp success in "+tcpClient.Client.RemoteEndPoint.ToString()); + TCPClientDevices.Add(host + ":" + port,tcpClient); + _ = HandleClientAsync(tcpClient,host,port); + }else{ + if(!TCPClientDevices[host + ":" + port].Connected){ + await TCPClientDevices[host + ":" + port].ConnectAsync(host,port); + _ = HandleClientAsync(TCPClientDevices[host + ":" + port],host,port); + } + } + return true; + } + catch(Exception e){ + ErrorLogs = "[open port error]" + e.ToString(); + return false; + } + } + + /// + /// Disconnect Tcp client to server. + /// + public void DisconnectTCP(string host,int port){ + if(TCPClientDevices.ContainsKey(host + ":" + port)){ + if (TCPClientDevices[host + ":" + port].Connected) + { + TCPClientDevices[host + ":" + port].Close(); + TCPClientDevices.Remove(host + ":" + port); + } + }else{ + //Not found in TCPClientDevices,need add? + } + } + + + public void SetEncoding(Encoding textEncoding) + { + this.StringEncoding = textEncoding; + } + public async void SendByteData(string host,int port,byte[] data){ + // if serial port not open,open first + try{ + bool result = await ConnectTCP(host,port); + if(result){ + await TCPClientDevices[host + ":" + port].GetStream().WriteAsync(data,0,data.Length); + } + }catch(Exception e){ + ErrorLogs = "[write error]" + e.ToString(); + } + } + public void SendByteData(string destination,byte[] data){ + SendByteData(destination.GetHostByIp(),destination.GetPortByIp(),data); + } + + public void SendByteDataOnce(string host,int port,byte[] data){ + SendByteData(host,port,data); + DisconnectTCP(host,port); + } + + public void SendStringData(string host,int port,string str){ + SendByteData(host,port,StringEncoding.GetBytes(str)); + } + + public void SendStringData(string destination,string data){ + SendStringData(destination.GetHostByIp(),destination.GetPortByIp(),data); + } + public void SendStringDataOnce(string host,int port,string str){ + SendStringData(host,port,str); + DisconnectTCP(host,port); + } + + public ConcurrentQueue GetReceivedMsg() + { + return ResponseMsgs; + } + + /// + /// UpdateStatus + /// + public async void CheckAndRelink(){ + foreach(TcpClient tcpClient in TCPClientDevices.Values){ + if(!tcpClient.Connected){ + await tcpClient.ConnectAsync((IPEndPoint)tcpClient.Client.RemoteEndPoint); + } + } + } + + public async Task HandleClientAsync(TcpClient client,string host,int port) + { + try + { + NetworkStream stream = client.GetStream(); + string ClientName = host+":"+port; + while (true) + { + byte[] buffer = new byte[1024]; + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + if (bytesRead == 0) + { + break; + } + string data = StringEncoding.GetString(buffer, 0, bytesRead); + byte[] receivedByte = new byte[bytesRead]; + Array.Copy(buffer, 0, receivedByte, 0, bytesRead); + ResponseMsg receivedMsgs = new ResponseMsg(data,receivedByte,ClientName, ProtocolType.TCPClient); + ResponseMsgs.Enqueue(receivedMsgs); + //this.EGOnReceivedData(receivedMsgs); + } + DeleteClient(client,host,port); + } + catch (Exception) + { + } + } + + public void DeleteClient(TcpClient client,string host,int port) + { + client.Close(); + string clientName = host+":"+port; + if (TCPClientDevices.ContainsKey(clientName)) { + TCPClientDevices.Remove(clientName); + } + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + + + } + + public static class CanGetEGTCPClientExtension{ + public static EGTCPClient EGTCPClient(this IEGFramework self){ + return self.GetModule(); + } + + public static TcpClient EGGetTCPClient(this IEGFramework self,string host,int port){ + return self.GetModule().TCPClientDevices[host + ":" + port]; + } + } + +} diff --git a/EGFramework/Module/ProtocolTools/Internet/EGTCPServer.cs b/EGFramework/Module/ProtocolTools/Internet/EGTCPServer.cs new file mode 100644 index 0000000..3f2e2c2 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGTCPServer.cs @@ -0,0 +1,167 @@ +using System; +using System.Collections.Generic; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; + +namespace EGFramework{ + public class EGTCPServer : IModule, IEGFramework,IProtocolReceived + { + public Dictionary TcpServerDevices { set; get; } = new Dictionary(); + public Dictionary IsListening { set; get; } = new Dictionary(); + public Dictionary LinkedClients { set; get; } = new Dictionary(); + public Dictionary> LinkedIPs { set; get; } = new Dictionary>(); + + public Dictionary ClientMappings { set; get; } = new Dictionary(); + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public EasyEvent OnClientConnect { set; get; } = new EasyEvent(); + public EasyEvent OnClientDisconnect { set; get; } = new EasyEvent(); + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public string ErrorLogs { set; get; } + public void Init() + { + this.EGRegisterSendAction(request => { + if(request.protocolType == ProtocolType.TCPServer){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + ResponseStringData(request.sender,request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + ResponseByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + } + + public ConcurrentQueue GetReceivedMsg() + { + return ResponseMsgs; + } + + public async void StartServer(int port) + { + IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, port); + TcpListener tcpServer = new TcpListener(ipEndPoint); + if(TcpServerDevices.ContainsKey(port)){ + return; + }else{ + TcpServerDevices.Add(port,tcpServer); + IsListening.Add(port,true); + } + TcpServerDevices[port].Start(); + try + { + while (IsListening[port]) + { + TcpClient client = await TcpServerDevices[port].AcceptTcpClientAsync(); + LinkedClients.Add(client.Client.RemoteEndPoint.ToString(), client); + OnClientConnect.Invoke(client.Client.RemoteEndPoint.ToString()); + if (!LinkedIPs.Keys.Contains(port)) + { + LinkedIPs.Add(port, new List()); + LinkedIPs[port].Add(client.Client.RemoteEndPoint.ToString()); + } + else + { + LinkedIPs[port].Add(client.Client.RemoteEndPoint.ToString()); + } + if (!ClientMappings.ContainsKey(client.Client.RemoteEndPoint.ToString())) + { + ClientMappings.Add(client.Client.RemoteEndPoint.ToString(), port); + } + _ = HandleClientAsync(client); + EG.Print("[EGTCPServer]"+port+" Client connected: " + client.Client.RemoteEndPoint.ToString()); + } + TcpServerDevices[port].Stop(); + } + catch (Exception e) + { + EG.Print("[EGTCPServer]"+port+" Error: " + e.ToString()); + } + } + public void EndServer(int port){ + IsListening[port] = false; + } + + public async Task HandleClientAsync(TcpClient client) + { + try + { + NetworkStream stream = client.GetStream(); + string ClientName = client.Client.RemoteEndPoint.ToString(); + while (client.Connected) + { + byte[] buffer = new byte[1024]; + int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); + if (bytesRead == 0) + { + break; + } + string data = StringEncoding.GetString(buffer, 0, bytesRead); + ResponseMsg receivedMsgs = new ResponseMsg(data,buffer,ClientName, ProtocolType.TCPServer); + //await Task.Run(() => OnDataReceived(receivedMsgs)).ConfigureAwait(false); + //this.EGOnReceivedData(receivedMsgs); + ResponseMsgs.Enqueue(receivedMsgs); + } + //await Task.Run(() => DeleteClient(client)).ConfigureAwait(false); + EG.Print("[EGTCPServer] Client Disconnected: " + client.Client.LocalEndPoint.ToString() + "--" + client.Client.RemoteEndPoint.ToString()); + int port = ClientMappings[client.Client.RemoteEndPoint.ToString()]; + ClientMappings.Remove(client.Client.RemoteEndPoint.ToString()); + LinkedIPs[port].Remove(client.Client.RemoteEndPoint.ToString()); + DeleteClient(client); + client.Close(); + } + catch (Exception) + { + } + } + + public async void ResponseByteData(string clientName,byte[] data){ + // if serial port not open,open first + try{ + await this.LinkedClients[clientName]?.GetStream().WriteAsync(data, 0, data.Length); + }catch(Exception e){ + ErrorLogs = "[write error]" + e.ToString(); + } + } + public void ResponseStringData(string clientName,string str){ + byte[] buffer = StringEncoding.GetBytes(str); + ResponseByteData(clientName,buffer); + } + + public void DeleteClient(TcpClient client) + { + string clientName = client.Client.RemoteEndPoint.ToString(); + if (LinkedClients.ContainsKey(clientName)) { + LinkedClients.Remove(clientName); + } + OnClientDisconnect.Invoke(clientName); + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + + } + + public static class CanGetEGTCPServerExtension{ + public static EGTCPServer EGTCPServer(this IEGFramework self){ + return self.GetModule(); + } + + public static void EGTCPServerListen(this IEGFramework self ,int port){ + self.GetModule().StartServer(port); + } + public static void EGTCPServerEndListen(this IEGFramework self ,int port){ + self.GetModule().EndServer(port); + } + + } +} + diff --git a/EGFramework/Module/ProtocolTools/Internet/EGUDP.cs b/EGFramework/Module/ProtocolTools/Internet/EGUDP.cs new file mode 100644 index 0000000..2939ef9 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGUDP.cs @@ -0,0 +1,137 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Sockets; +using System.Text; + +namespace EGFramework{ + public class EGUDP : IEGFramework, IModule, IProtocolSend, IProtocolReceived + { + public Dictionary UDPDevices { set; get; } = new Dictionary(); + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public void Init() + { + this.EGRegisterSendAction(request => { + if(request.protocolType == ProtocolType.UDP){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + this.SendStringData(request.sender.GetHostByIp(),request.sender.GetPortByIp(),request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + this.SendByteData(request.sender.GetHostByIp(),request.sender.GetPortByIp(),request.req.ToProtocolByteData()); + } + } + }); + } + + public void ListenUDP(int localPort){ + if (!UDPDevices.ContainsKey(localPort)) { + try + { + UdpClient udpDevice = new UdpClient(localPort); + UDPDevices.Add(localPort,udpDevice); + //udpDevice.EnableBroadcast = true; + HandleUDPListenAsync(udpDevice); + //StartListening(localPort); + } + catch (Exception e){ + EG.Print("Error" + e); + } + } + } + + public void EndListenUDP(int localPort){ + if (UDPDevices.ContainsKey(localPort)) { + UDPDevices[localPort].Close(); + UDPDevices.Remove(localPort); + } + } + + public async void HandleUDPListenAsync(UdpClient client) + { + try + { + //EG.Print("UDP listened in "+((IPEndPoint)client.Client.LocalEndPoint).Port); + while (true) + { + UdpReceiveResult data = await client.ReceiveAsync(); + string dataStr = StringEncoding.GetString(data.Buffer); + ResponseMsg receivedMsgs = new ResponseMsg(dataStr,data.Buffer,data.RemoteEndPoint.ToString(), ProtocolType.UDP); + //this.EGOnReceivedData(receivedMsgs); + ResponseMsgs.Enqueue(receivedMsgs); + } + } + catch (Exception e) + { + EG.Print("Listen false by:"+e); + } + } + + public void SendByteData(string host,int port,byte[] data){ + UdpClient udpClient; + if(UDPDevices.Count>0){ + udpClient = UDPDevices.First().Value; + }else{ + udpClient = new UdpClient(); + } + try{ + EG.Print(udpClient.EnableBroadcast); + udpClient.Send(data, data.Length, host, port); + } + catch ( Exception e ){ + EG.Print(e.ToString()); + } + if(UDPDevices.Count<=0){ + udpClient.Close(); + udpClient.Dispose(); + } + } + public void SendByteData(string destination,byte[] data){ + SendByteData(destination.GetHostByIp(),destination.GetPortByIp(),data); + } + + public void SendStringData(string host,int port,string data){ + byte[] buffer = StringEncoding.GetBytes(data); + this.SendByteData(host,port,buffer); + } + public void SendStringData(string destination,string data){ + SendStringData(destination.GetHostByIp(),destination.GetPortByIp(),data); + } + + public void SetEncoding(Encoding textEncoding){ + StringEncoding = textEncoding; + } + + public void BroadCastUDPMessage(string host,int port,byte[] message){ + + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + + public ConcurrentQueue GetReceivedMsg() + { + return ResponseMsgs; + } + } + + public static class CanGetEGUDPExtension{ + public static EGUDP EGUDP(this IEGFramework self){ + return self.GetModule(); + } + + public static void EGUDPListen(this IEGFramework self ,int port){ + self.GetModule().ListenUDP(port); + } + public static void EGUDPEndListen(this IEGFramework self ,int port){ + self.GetModule().EndListenUDP(port); + } + } +} + diff --git a/EGFramework/Module/ProtocolTools/Internet/EGWebSocketClient.cs b/EGFramework/Module/ProtocolTools/Internet/EGWebSocketClient.cs new file mode 100644 index 0000000..27f02ee --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Internet/EGWebSocketClient.cs @@ -0,0 +1,139 @@ +using System; +using System.Collections.Concurrent; +using System.Text; +using System.Net.Sockets; +using System.Collections.Generic; +using System.Net.WebSockets; +using System.Threading.Tasks; + +namespace EGFramework{ + public class EGWebSocketClient : IEGFramework, IModule, IProtocolSend, IProtocolReceived + { + public Dictionary WebSocketClientDevices { set; get; } = new Dictionary(); + + public string ErrorLogs { set; get; } + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.TCPClient){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + this.SendStringData(request.sender,request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + this.SendByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + } + + /// + /// Connect Websocket client to server with check if target server is listened. + /// + public async Task ConnectWebSocket(string address){ + try{ + Uri uri = new Uri(address); + if(!WebSocketClientDevices.ContainsKey(address)){ + ClientWebSocket ws = new ClientWebSocket(); + await ws.ConnectAsync(uri,default); + //Print("Connect Tcp success in "+tcpClient.Client.RemoteEndPoint.ToString()); + WebSocketClientDevices.Add(address,ws); + _ = HandleClientAsync(ws,address); + }else{ + if(WebSocketClientDevices[address].State != WebSocketState.Open){ + await WebSocketClientDevices[address].ConnectAsync(uri,default); + _ = HandleClientAsync(WebSocketClientDevices[address],address); + } + } + return true; + } + catch(Exception e){ + ErrorLogs = "[open port error]" + e.ToString(); + return false; + } + } + + /// + /// Disconnect Websocket client to server. + /// + public void DisconnectWebsocket(string address){ + if(WebSocketClientDevices.ContainsKey(address)){ + if (WebSocketClientDevices[address].State == WebSocketState.Open) + { + WebSocketClientDevices[address].CloseAsync(WebSocketCloseStatus.NormalClosure,"Client closed",default); + WebSocketClientDevices[address].Dispose(); + WebSocketClientDevices.Remove(address); + } + }else{ + //Not found in Websocket client,need add? + } + } + + public async Task HandleClientAsync(ClientWebSocket client,string address) + { + try + { + string ClientName = address; + while (true) + { + byte[] buffer = new byte[1024]; + WebSocketReceiveResult result = await client.ReceiveAsync(buffer, default); + if (result.Count == 0) + { + break; + } + string data = StringEncoding.GetString(buffer, 0, result.Count); + byte[] receivedByte = new byte[result.Count]; + Array.Copy(buffer, 0, receivedByte, 0, result.Count); + ResponseMsg receivedMsgs = new ResponseMsg(data,receivedByte,ClientName, ProtocolType.WebSocketClient); + ResponseMsgs.Enqueue(receivedMsgs); + //this.EGOnReceivedData(receivedMsgs); + } + DisconnectWebsocket(address); + } + catch (Exception) + { + } + } + + public ConcurrentQueue GetReceivedMsg() + { + return this.ResponseMsgs; + } + + public async void SendByteDataAsync(string address,byte[] data){ + // if serial port not open,open first + try{ + bool result = await ConnectWebSocket(address); + if(result){ + await WebSocketClientDevices[address].SendAsync(data,WebSocketMessageType.Binary,true,default); + } + }catch(Exception e){ + ErrorLogs = "[write error]" + e.ToString(); + } + } + + public void SendByteData(string destination, byte[] data) + { + SendByteDataAsync(destination, data); + } + + public void SendStringData(string destination, string data) + { + SendByteData(destination,StringEncoding.GetBytes(data)); + } + + public void SetEncoding(Encoding textEncoding) + { + this.StringEncoding = textEncoding; + } + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } +} diff --git a/EGFramework/Module/ProtocolTools/Other/EGFileStream.cs b/EGFramework/Module/ProtocolTools/Other/EGFileStream.cs new file mode 100644 index 0000000..55db780 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Other/EGFileStream.cs @@ -0,0 +1,101 @@ +using System.Collections.Generic; +using System.Text; +using System.IO; +using System.Collections.Concurrent; + +namespace EGFramework{ + public class EGFileStream : IEGFramework, IModule,IProtocolSend,IProtocolReceived + { + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.FileStream){ + if(request.req.ToProtocolData() != "" && request.req.ToProtocolData() != null){ + this.SendStringData(request.sender,request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData().Length > 0 && request.req.ToProtocolByteData() != null){ + this.SendByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + + public async void SendByteData(string destination, byte[] data) + { + string path = destination; + try + { + if (File.Exists(path)) + { + File.Delete(path); + } + FileStream fileStream = File.Create(path); + await fileStream.WriteAsync(data,0,data.Length); + fileStream.Close(); + await fileStream.DisposeAsync(); + } + catch (System.Exception) + { + throw; + } + //throw new System.NotImplementedException(); + } + + public void SendStringData(string destination, string data) + { + SendByteData(destination,StringEncoding.GetBytes(data)); + //throw new System.NotImplementedException(); + } + + /// + /// Read file by FileStream and return received a byte data or string data from file + /// + /// File Path + public async void ReadFromFile(string path){ + try + { + FileStream fileStream = new FileStream(path,FileMode.Open); + byte[] buffer = new byte[fileStream.Length]; + await fileStream.ReadAsync(buffer, 0, (int)fileStream.Length); + fileStream.Close(); + await fileStream.DisposeAsync(); + string data = StringEncoding.GetString(buffer); + ResponseMsg receivedMsgs = new ResponseMsg(data,buffer,path, ProtocolType.FileStream); + ResponseMsgs.Enqueue(receivedMsgs); + } + catch (System.Exception e) + { + EG.Print("e:" + e); + throw; + } + } + + public void SetEncoding(Encoding textEncoding) + { + this.StringEncoding = textEncoding; + } + + public ConcurrentQueue GetReceivedMsg() + { + return this.ResponseMsgs; + } + } + public static class CanGetEGFileStreamExtension{ + public static EGFileStream EGFileStream(this IEGFramework self){ + return self.GetModule(); + } + + public static void EGReadFromFile(this IEGFramework self,string path){ + self.GetModule().ReadFromFile(path); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/Other/EGProcess.cs b/EGFramework/Module/ProtocolTools/Other/EGProcess.cs new file mode 100644 index 0000000..b89f024 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/Other/EGProcess.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; + +namespace EGFramework{ + public class EGProcess : IEGFramework, IModule, IProtocolSend, IProtocolReceived + { + public Dictionary Processes { set; get; } = new Dictionary(); + + public string ErrorLogs { set; get; } + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.Process){ + if(request.req.ToProtocolData() != null && request.req.ToProtocolData() != ""){ + this.SendStringData(request.sender,request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData() != null && request.req.ToProtocolByteData().Length > 0){ + this.SendByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + } + + /// + /// Connect process program with check if target program is exist. + /// + public void InitProcess(string processName){ + try{ + if(!Processes.ContainsKey(processName)){ + Process process = new Process(); + if(processName.GetStrFrontSymbol(' ') == ""){ + process.StartInfo.FileName = processName.GetStrBehindSymbol(' '); + }else{ + process.StartInfo.FileName = processName.GetStrFrontSymbol(' '); + process.StartInfo.Arguments = processName.GetStrBehindSymbol(' ');; // Add any arguments if needed + } + EG.Print("[open process]"+process.StartInfo.FileName+" "+process.StartInfo.Arguments); + process.StartInfo.UseShellExecute = false; + process.StartInfo.RedirectStandardInput = true; + process.StartInfo.RedirectStandardOutput = true; + process.StartInfo.RedirectStandardError = true; + process.StartInfo.CreateNoWindow = true; + process.EnableRaisingEvents = true; + process.OutputDataReceived += (sender, e) => { + if (!string.IsNullOrEmpty(e.Data)) { + try + { + ResponseMsgs.Enqueue(new ResponseMsg { sender = processName, stringData = e.Data }); + } + catch (Exception err) + { + ErrorLogs = "[process output error]" + err.ToString(); + EG.Print(ErrorLogs); + } + + } + }; + process.Exited += (sender, e) => { + Processes.Remove(processName); + }; + Processes.Add(processName,process); + process.Start(); + process.BeginOutputReadLine(); + } + } + catch(Exception e){ + ErrorLogs = "[open process error]" + e.ToString(); + EG.Print(ErrorLogs); + } + } + + public void CloseProcess(string processName){ + if(Processes.ContainsKey(processName)){ + Processes[processName].Kill(); + Processes.Remove(processName); + EG.Print("[close process]"+processName); + } + } + + public void SetEncoding(Encoding textEncoding) + { + this.StringEncoding = textEncoding; + } + + public ConcurrentQueue GetReceivedMsg() + { + return this.ResponseMsgs; + } + + public void SendByteData(string destination, byte[] data) + { + Processes[destination].StandardInput.WriteLine(data); + } + + public void SendStringData(string destination, string data) + { + Processes[destination].StandardInput.WriteLine(data); + } + + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } + public static class CanGetEGProcessExtension{ + public static EGProcess EGProcess(this IEGFramework self){ + return self.GetModule(); + } + + public static Process EGGetSshClient(this IEGFramework self,string processName){ + return self.GetModule().Processes[processName]; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/ProtocolTools/ProtocolExtension/EGDnsExtension.cs b/EGFramework/Module/ProtocolTools/ProtocolExtension/EGDnsExtension.cs new file mode 100644 index 0000000..564fc3d --- /dev/null +++ b/EGFramework/Module/ProtocolTools/ProtocolExtension/EGDnsExtension.cs @@ -0,0 +1,299 @@ +using System; +using System.Collections.Generic; + +namespace EGFramework{ + /// + /// Support a class for analysis the .local(mdns) or other www.com(dns) protocol to get the message, + /// mdns protocol format type reference from https://github.com/richardschneider/net-mdns + /// nuget package is Makaretu.Dns.Multicast + /// mdns reference from https://www.rfc-editor.org/rfc/rfc6763.html + /// + public static class EGDnsExtension + { + public const string DefaultDnsServer = "1.1.1.1"; + public const int DefaultDnsPort = 53; + public const string DefaultMDnsServerIpv4 = "224.0.0.251"; + public const string DefaultMDnsServerIpv6 = "FF02::FB"; + public const int DefaultMDnsPort = 5353; + + public const string DNS_SRV_RR = "_services._dns-sd._udp.local"; + } + + /// + /// Dns's OpCode + /// + public enum DnsOpCode : ushort{ + /// + /// Standard query. + /// + Query = 0x0000, + /// + /// Inverse query (obsolete), see https://tools.ietf.org/html/rfc3425. + /// + InverseQuery = 0x0800, + /// + /// A server status request. + /// + Status = 0x1000, + /// + /// Zone change, see https://tools.ietf.org/html/rfc1996. + /// + Notify = 0x2000, + /// + /// Update message, see https://tools.ietf.org/html/rfc2136. + /// + Update = 0x2800 + } + + /// + /// A resource record or query type. + /// + public enum DnsType{ + /// + /// A host address. + /// + A = 1, + /// + /// An authoritative name server. + /// + NS = 2, + /// + /// The canonical name for an alias. + /// + CNAME = 5, + /// + /// Marks the start of a zone of authority. + /// + SOA = 6, + /// + /// A mailbox domain name (EXPERIMENTAL). + /// + MB = 7, + /// + /// A mail group member (EXPERIMENTAL). + /// + MG = 8, + /// + /// A mailbox rename domain name (EXPERIMENTAL). + /// + MR = 9, + /// + /// A Null resource record (EXPERIMENTAL). + /// + NULL = 10, + /// + /// A well known service description. + /// + WKS = 11, + /// + /// A domain name pointer. + /// + PTR = 12, + /// + /// Host information. + /// + HINFO = 13, + /// + /// Mailbox or mail list information. + /// + MINFO = 14, + /// + /// Mail exchange. + /// + MX = 15, + /// + /// Text resources. + /// + TXT = 16, + /// + /// Responsible Person. + /// + RP = 17, + /// + /// AFS Data Base location. + /// + AFSDB = 18, + /// + /// An IPv6 host address. + /// + AAAA = 28, + /// + /// A resource record which specifies the location of the server(s) for a specific protocol and domain. + /// + SRV = 33, + /// + /// Maps an entire domain name. + /// + DNAME = 39, + /// + /// Option record. + /// + OPT = 41, + /// + /// Delegation Signer. + /// + DS = 43, + /// + /// Signature for a RRSET with a particular name, class, and type. + /// + RRSIG = 46, + /// + /// Next secure owener. + /// + NSEC = 47, + /// + /// Public key cryptography to sign and authenticate resource records. + /// + DNSKEY = 48, + /// + /// Authenticated next secure owner. + /// + NSEC3 = 50, + /// + /// Parameters needed by authoritative servers to calculate hashed owner names. + /// + NSEC3PARAM = 51, + /// + /// Shared secret key. + /// + TKEY = 249, + /// + /// Transactional Signature. + /// + TSIG = 250, + /// + /// A request for a transfer of an entire zone. + /// + AXFR = 252, + /// + /// A request for mailbox-related records (MB, MG or MR). + /// + MAILB = 253, + /// + /// A request for any record(s). + /// + ANY = 255, + /// + /// A Uniform Resource Identifier (URI) resource record. + /// + URI = 256, + /// + /// A certification authority authorization. + /// + CAA = 257 + } + + /// + /// The values are maintained by IANA at https://www.iana.org/assignments/dns-parameters/dns-parameters.xhtml#dns-parameters-2. + /// + public enum DnsClass : ushort + { + /// + /// The Internet. + /// + IN = 1, + /// + /// The CSNET class (Obsolete - used only for examples insome obsolete RFCs). + /// + CS = 2, + /// + /// The CHAOS class. + /// + CH = 3, + /// + /// Hesiod[Dyer 87]. + /// + HS = 4, + /// + /// Used in UPDATE message to signify no class. + /// + None = 254, + /// + /// Only used in QCLASS. + /// + ANY = 255 + } + + public struct DnsHead{ + + } + + /// + /// MDns Head + /// | TransactionID (2 bytes) | OpCode (2 bytes) | Dns Sign (2 byte) + /// + public struct MDnsHead { + /// + /// + /// + /// + public ushort TransactionID { set; get; } + + + /// + /// The requested operation. + /// + /// + public DnsOpCode OpCode { set; get; } + #region Sign Code + /// + /// A one bit field that specifies whether this message is a query(0), or a response(1). + /// + /// + public bool QR { set; get; } + + public bool AA { set; get; } + + public bool TC { set; get; } + + public bool RD { set; get; } + public bool RA { set; get; } + + public byte OpCode4Bit { set; get; } + /// + /// Reserved for future use. + /// + /// Must be zero in all queries and responses. + public byte Z { set; get; } + /// + /// Authentic data. + /// + /// true if the response data is authentic; otherwise, false. + public bool AD { get; set; } + /// + /// Checking disabled. + /// + /// true if the query does not require authenticated data; otherwise, false. + public bool CD { get; set; } + #endregion + } + + public struct DnsQuestionRequest : IRequest + { + + public byte ReplyCode { set; get; } + public ushort QuestionsCount { set; get; } + public ushort AnswerRRs { set; get; } + public ushort AuthorityRRs { set; get; } + public ushort Additional { set; get; } + + public List Data { set; get; } + + public byte QuestionType { set; get; } + + public byte QuestionClass { set; get; } + + + public byte[] ToProtocolByteData() + { + throw new NotImplementedException(); + } + + public string ToProtocolData() + { + throw new NotImplementedException(); + } + + } +} + diff --git a/EGFramework/Module/ProtocolTools/ProtocolExtension/EGModbusExtension.cs b/EGFramework/Module/ProtocolTools/ProtocolExtension/EGModbusExtension.cs new file mode 100644 index 0000000..89f4b29 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/ProtocolExtension/EGModbusExtension.cs @@ -0,0 +1,899 @@ +using System; +using System.Linq; + +namespace EGFramework{ + /// + /// some extensions function about Modbus-TCP and Modbus-RTU (Physic layer used RS-485) + /// not include Modbus-ASCII,because LRC verify not developed + /// + public static class EGModbusExtension + { + + //Send Protocol + //---------Modbus-TCP's Prefix--------- + //[00 00 00 00 00 06] 01 03 00 00 00 08 + //00 00 ----- info head (check for reply Any things can be defined) + //xx xx 00 00 00 06 info length ( Max length 65535 ) + public static byte[] MakeModbusTCPPrefix(this object self,ushort messageId,uint length){ + return messageId.ToBytes().Concat(length.ToBytes()).ToArray(); + } + public static byte[] MakeModbusTCPPrefix(this object self,ushort messageId,byte[] sendData){ + return messageId.ToBytes().Concat(((uint)sendData.Length).ToBytes()).ToArray(); + } + } + + /// + /// Modbus FunctionCode + /// 0x01 => Read Coils ---- OK + /// 0x02 => Read Discrete input ---- OK + /// 0x03 => Read Holding registers ---- OK + /// 0x04 => Read Input registers ---- OK + /// 0x05 => Write Single Coils ---- OK + /// 0x06 => Write Single Holding registers ---- OK + /// 0x0F => Write Multi Coils ---- OK + /// 0x10 => Write Multi Holding registers ---- OK + /// + + #region Modbus TCP Request and Response + public class ModbusTCP_ReadCoils : IRequest + { + public const byte FunctionCode = 0x01; + public const ushort MessageId = 0xFF01; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 2000 + public ModbusTCP_ReadCoils(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = this.MakeModbusTCPPrefix(MessageId,6); + protocolRequest = protocolRequest.Append(DeviceAddress).ToArray(); + protocolRequest = protocolRequest.Append(FunctionCode).ToArray(); + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>2000){ + ReadCount = 2000; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_ReadDiscreteInput : IRequest + { + public const byte FunctionCode = 0x02; + public const ushort MessageId = 0xFF02; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 2000 + public ModbusTCP_ReadDiscreteInput(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = this.MakeModbusTCPPrefix(MessageId,6); + protocolRequest = protocolRequest.Append(DeviceAddress).ToArray(); + protocolRequest = protocolRequest.Append(FunctionCode).ToArray(); + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>2000){ + ReadCount = 2000; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_ReadHoldingRegisters : IRequest + { + public const byte FunctionCode = 0x03; + + public const ushort MessageId = 0xFF03; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 125 + public ModbusTCP_ReadHoldingRegisters(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = this.MakeModbusTCPPrefix(MessageId,6); + protocolRequest = protocolRequest.Append(DeviceAddress).ToArray(); + protocolRequest = protocolRequest.Append(FunctionCode).ToArray(); + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>125){ + ReadCount = 125; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_ReadInputRegisters : IRequest + { + public const byte FunctionCode = 0x04; + + public const ushort MessageId = 0xFF04; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 125 + public ModbusTCP_ReadInputRegisters(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = this.MakeModbusTCPPrefix(MessageId,6); + protocolRequest = protocolRequest.Append(DeviceAddress).ToArray(); + protocolRequest = protocolRequest.Append(FunctionCode).ToArray(); + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>125){ + ReadCount = 125; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_WriteSingleCoil : IRequest + { + public const byte FunctionCode = 0x05; + + public const ushort MessageId = 0xFF05; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public bool Value { set; get; } + + public ModbusTCP_WriteSingleCoil(byte deviceAddress,ushort registerAddress,bool value){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + Value = value; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = this.MakeModbusTCPPrefix(MessageId,6); + protocolRequest = protocolRequest.Append(DeviceAddress).ToArray(); + protocolRequest = protocolRequest.Append(FunctionCode).ToArray(); + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + byte[] registerValues = {0x00,0x00}; + if(Value){ + registerValues[0]=0xFF; + } + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_WriteSingleHoldingRegister : IRequest + { + public const byte FunctionCode = 0x06; + + public const ushort MessageId = 0xFF06; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort Value { set; get; } + + public ModbusTCP_WriteSingleHoldingRegister(byte deviceAddress,ushort registerAddress,ushort value){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + Value = value; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = this.MakeModbusTCPPrefix(MessageId,6); + protocolRequest = protocolRequest.Append(DeviceAddress).ToArray(); + protocolRequest = protocolRequest.Append(FunctionCode).ToArray(); + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + byte[] registerValues = Value.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_WriteMultiCoil : IRequest{ + public const byte FunctionCode = 0x0F; + + public const ushort MessageId = 0xFF0F; + public byte DeviceAddress { set; get; } + public ushort RegisterStartAddress { set; get; } + public bool[] Values { set; get; } + + /// + /// Construct the protocol + /// + /// usual use 0x01 for device address + /// + /// Values length should be less than 2000 + public ModbusTCP_WriteMultiCoil(byte deviceAddress,ushort registerStartAddress,bool[] values){ + DeviceAddress = deviceAddress; + RegisterStartAddress = registerStartAddress; + Values = values; + } + public byte[] ToProtocolByteData() + { + + byte[] protocolRequest = {DeviceAddress,FunctionCode}; + protocolRequest = protocolRequest.Concat(RegisterStartAddress.ToBytes()).ToArray(); + protocolRequest = protocolRequest.Concat(((ushort)Values.Length).ToBytes()).ToArray(); + + //Length range should be 1-2000 0x0001-0x07D0,otherwise delete the data after 2000 + if(Values.Length>2000){ + bool[] SourceValues = Values; + Values = new bool[2000]; + Array.Copy(Values,0,SourceValues,0,2000); + } + //bool array 2000 => byte array 250 + byte[] valueGroup = Values.ToByteArray(); + byte valueLength = (byte)valueGroup.Length; + protocolRequest = protocolRequest.Append(valueLength).ToArray(); + protocolRequest = protocolRequest.Concat(valueGroup).ToArray(); + byte[] protocolPrefix = this.MakeModbusTCPPrefix(MessageId,protocolRequest); + protocolRequest = protocolPrefix.Concat(protocolRequest).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + /// + /// Start write at Register start address,such as 0x03,write order by order like 0x03,0x04,0x05...,write count is the value array length + /// + public class ModbusTCP_WriteMultiHoldingRegister : IRequest + { + public const byte FunctionCode = 0x10; + + public const ushort MessageId = 0xFF10; + public byte DeviceAddress { set; get; } + public ushort RegisterStartAddress { set; get; } + public ushort[] Values { set; get; } + + /// + /// Construct the protocol + /// + /// usual use 0x01 for device address + /// + /// Values length should be less than 125 + public ModbusTCP_WriteMultiHoldingRegister(byte deviceAddress,ushort registerStartAddress,ushort[] values){ + DeviceAddress = deviceAddress; + RegisterStartAddress = registerStartAddress; + Values = values; + } + public byte[] ToProtocolByteData() + { + + byte[] protocolRequest = {DeviceAddress,FunctionCode}; + protocolRequest = protocolRequest.Concat(RegisterStartAddress.ToBytes()).ToArray(); + protocolRequest = protocolRequest.Concat(((ushort)Values.Length).ToBytes()).ToArray(); + + //Length range should be 1-125 0x0001-0x07D,otherwise delete the data after 125 + if(Values.Length>125){ + ushort[] SourceValues = Values; + Values = new ushort[125]; + Array.Copy(Values,0,SourceValues,0,125); + } + //ushort array 125 => byte array 250 + byte[] valueGroup = {}; + foreach(ushort value in Values){ + byte[] registerValues = value.ToBytes(); + valueGroup = valueGroup.Concat(registerValues).ToArray(); + } + byte valueLength = (byte)valueGroup.Length; + protocolRequest = protocolRequest.Append(valueLength).ToArray(); + protocolRequest = protocolRequest.Concat(valueGroup).ToArray(); + byte[] protocolPrefix = this.MakeModbusTCPPrefix(MessageId,protocolRequest); + protocolRequest = protocolPrefix.Concat(protocolRequest).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusTCP_Response : IResponse + { + public bool[] Coil { set; get; } + public bool[] DiscreteInput { set; get; } + public ushort[] HoldingRegister { set; get; } + public ushort[] InputRegister { set; get; } + public byte FunctionCode { set; get; } + public byte DeviceAddress { set; get; } + public ushort RegisterStartAddress { set; get; } + public uint DataLength { set; get; } + + public byte[] SourceData { set; get; } + public byte[] SourceValueData { set; get; } + public ModbusFunctionType FunctionType { set; get; } + public ModbusErrorCode ErrorCode { set; get; } + public bool IsError { set; get; } + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + if(protocolBytes==null && protocolBytes.Length < 8){ + return false; + } + SourceData = protocolBytes; + DeviceAddress = protocolBytes[6]; + FunctionType = (ModbusFunctionType)protocolBytes[7]; + + if(FunctionCode == 0x83){ + ErrorCode = (ModbusErrorCode)protocolBytes[2]; + IsError = true; + return true; + } + byte[] dataLength = new byte[4]; + Array.Copy(protocolBytes,2,dataLength,0,4); + DataLength = dataLength.ToUINT(); + if(protocolBytes.Length != DataLength+6){ + return false; + } + //every response's start should be 0xff,because the request's start is 0xff + if(protocolBytes[0]==0xff){ + switch(FunctionType){ + case ModbusFunctionType.ReadCoil: + byte readCoilLength = protocolBytes[8]; + byte[] CoilBytes = new byte[readCoilLength]; + Array.Copy(protocolBytes,9,CoilBytes,0,readCoilLength); + SourceValueData = CoilBytes; + Coil = CoilBytes.ToBoolArray(); + return true; + case ModbusFunctionType.ReadDiscreteInput: + byte readDiscreteInputLength = protocolBytes[8]; + byte[] DiscreteInputBytes = new byte[readDiscreteInputLength]; + Array.Copy(protocolBytes,9,DiscreteInputBytes,0,readDiscreteInputLength); + SourceValueData = DiscreteInputBytes; + DiscreteInput = DiscreteInputBytes.ToBoolArray(); + return true; + case ModbusFunctionType.ReadHoldingRegisters: + byte readHoldingRegistersLength = protocolBytes[8]; + byte[] HoldingRegistersBytes = new byte[readHoldingRegistersLength]; + Array.Copy(protocolBytes,9,HoldingRegistersBytes,0,readHoldingRegistersLength); + SourceValueData = HoldingRegistersBytes; + HoldingRegister = HoldingRegistersBytes.ToUShortArray(); + return true; + case ModbusFunctionType.ReadInputRegisters: + byte readInputRegistersLength = protocolBytes[8]; + byte[] InputRegistersBytes = new byte[readInputRegistersLength]; + Array.Copy(protocolBytes,9,InputRegistersBytes,0,readInputRegistersLength); + SourceValueData = InputRegistersBytes; + InputRegister = InputRegistersBytes.ToUShortArray(); + return true; + case ModbusFunctionType.WriteSingleCoil: + return true; + case ModbusFunctionType.WriteSingleHoldingRegister: + return true; + case ModbusFunctionType.WriteMultiCoil: + return true; + case ModbusFunctionType.WriteMultiHoldingRegister: + return true; + default: + return false; + } + } + } + catch (Exception) + { + return false; + } + return false; + } + } + #endregion + + #region Modbus RTU Request and Response,Used RS-485 for Physic layer + public class ModbusRTU_ReadCoils : IRequest{ + public const byte FunctionCode = 0x01; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 2000 + public ModbusRTU_ReadCoils(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = { DeviceAddress , FunctionCode }; + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>2000){ + ReadCount = 2000; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_ReadDiscreteInput : IRequest{ + public const byte FunctionCode = 0x02; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 2000 + public ModbusRTU_ReadDiscreteInput(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = { DeviceAddress , FunctionCode }; + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>2000){ + ReadCount = 2000; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_ReadHoldingRegisters : IRequest{ + public const byte FunctionCode = 0x03; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + public ModbusRTU_ReadHoldingRegisters() + { + + } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 2000 + public ModbusRTU_ReadHoldingRegisters(byte deviceAddress, ushort registerAddress, ushort readCount) + { + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = { DeviceAddress , FunctionCode }; + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>125){ + ReadCount = 125; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_ReadInputRegisters : IRequest{ + public const byte FunctionCode = 0x04; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort ReadCount { set; get; } + + /// + /// Construct the protocol + /// + /// + /// + /// Read count should be less than 2000 + public ModbusRTU_ReadInputRegisters(byte deviceAddress,ushort registerAddress,ushort readCount){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + ReadCount = readCount; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = { DeviceAddress , FunctionCode }; + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + if(ReadCount>125){ + ReadCount = 125; + } + byte[] registerValues = ReadCount.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_WriteSingleCoil : IRequest{ + public const byte FunctionCode = 0x05; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public bool Value { set; get; } + + public ModbusRTU_WriteSingleCoil(byte deviceAddress,ushort registerAddress,bool value){ + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + Value = value; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = { DeviceAddress , FunctionCode }; + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + byte[] registerValues = {0x00,0x00}; + if(Value){ + registerValues[0]=0xFF; + } + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_WriteSingleHoldingRegister : IRequest{ + public const byte FunctionCode = 0x06; + public byte DeviceAddress { set; get; } + public ushort RegisterAddress { set; get; } + public ushort Value { set; get; } + + public ModbusRTU_WriteSingleHoldingRegister() + { + + } + public ModbusRTU_WriteSingleHoldingRegister(byte deviceAddress, ushort registerAddress, ushort value) + { + DeviceAddress = deviceAddress; + RegisterAddress = registerAddress; + Value = value; + } + public byte[] ToProtocolByteData() + { + byte[] protocolRequest = { DeviceAddress , FunctionCode }; + protocolRequest = protocolRequest.Concat(RegisterAddress.ToBytes()).ToArray(); + byte[] registerValues = Value.ToBytes(); + protocolRequest = protocolRequest.Concat(registerValues).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_WriteMultiCoil : IRequest{ + public const byte FunctionCode = 0x0F; + public byte DeviceAddress { set; get; } + public ushort RegisterStartAddress { set; get; } + public bool[] Values { set; get; } + + /// + /// Construct the protocol + /// + /// usual use 0x01 for device address + /// + /// Values length should be less than 2000 + public ModbusRTU_WriteMultiCoil(byte deviceAddress,ushort registerStartAddress,bool[] values){ + DeviceAddress = deviceAddress; + RegisterStartAddress = registerStartAddress; + Values = values; + } + public byte[] ToProtocolByteData() + { + + byte[] protocolRequest = {DeviceAddress,FunctionCode}; + protocolRequest = protocolRequest.Concat(RegisterStartAddress.ToBytes()).ToArray(); + protocolRequest = protocolRequest.Concat(((ushort)Values.Length).ToBytes()).ToArray(); + + //Length range should be 1-2000 0x0001-0x07D0,otherwise delete the data after 2000 + if(Values.Length>2000){ + bool[] SourceValues = Values; + Values = new bool[2000]; + Array.Copy(Values,0,SourceValues,0,2000); + } + //bool array 2000 => byte array 250 + byte[] valueGroup = Values.ToByteArray(); + byte valueLength = (byte)valueGroup.Length; + protocolRequest = protocolRequest.Append(valueLength).ToArray(); + protocolRequest = protocolRequest.Concat(valueGroup).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + /// + /// Start write at Register start address,such as 0x03,write order by order like 0x03,0x04,0x05...,write count is the value array length + /// + public class ModbusRTU_WriteMultiHoldingRegister : IRequest + { + public const byte FunctionCode = 0x10; + + public const ushort MessageId = 0xFF10; + public byte DeviceAddress { set; get; } + public ushort RegisterStartAddress { set; get; } + public ushort[] Values { set; get; } + + public ModbusRTU_WriteMultiHoldingRegister() + { + + } + /// + /// Construct the protocol + /// + /// usual use 0x01 for device address + /// + /// Values length should be less than 125 + public ModbusRTU_WriteMultiHoldingRegister(byte deviceAddress, ushort registerStartAddress, ushort[] values) + { + DeviceAddress = deviceAddress; + RegisterStartAddress = registerStartAddress; + Values = values; + } + public byte[] ToProtocolByteData() + { + + byte[] protocolRequest = {DeviceAddress,FunctionCode}; + protocolRequest = protocolRequest.Concat(RegisterStartAddress.ToBytes()).ToArray(); + protocolRequest = protocolRequest.Concat(((ushort)Values.Length).ToBytes()).ToArray(); + + //Length range should be 1-125 0x0001-0x07D,otherwise delete the data after 125 + if(Values.Length>125){ + ushort[] SourceValues = Values; + Values = new ushort[125]; + Array.Copy(Values,0,SourceValues,0,125); + } + //ushort array 125 => byte array 250 + byte[] valueGroup = {}; + foreach(ushort value in Values){ + byte[] registerValues = value.ToBytes(); + valueGroup = valueGroup.Concat(registerValues).ToArray(); + } + byte valueLength = (byte)valueGroup.Length; + protocolRequest = protocolRequest.Append(valueLength).ToArray(); + protocolRequest = protocolRequest.Concat(valueGroup).ToArray(); + protocolRequest = protocolRequest.Concat(protocolRequest.CalculateCRC16Modbus().ToBytes()).ToArray(); + return protocolRequest; + } + + public string ToProtocolData() + { + return ""; + } + } + + public class ModbusRTU_Response : IResponse + { + public bool[] Coil { set; get; } + public bool[] DiscreteInput { set; get; } + public ushort[] HoldingRegister { set; get; } + public ushort[] InputRegister { set; get; } + public byte FunctionCode { set; get; } + public byte DeviceAddress { set; get; } + public ushort RegisterStartAddress { set; get; } + + public byte[] SourceData { set; get; } + public byte[] SourceValueData { set; get; } + public ModbusFunctionType FunctionType { set; get; } + public ModbusErrorCode ErrorCode { set; get; } + public bool IsError { set; get; } + + public virtual bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + if(protocolBytes==null && protocolBytes.Length < 4){ + return false; + } + SourceData = protocolBytes; + DeviceAddress = protocolBytes[0]; + FunctionCode = protocolBytes[1]; + FunctionType = (ModbusFunctionType)protocolBytes[1]; + if(FunctionCode >= 0x80){ + ErrorCode = (ModbusErrorCode)protocolBytes[2]; + IsError = true; + return true; + } + + //check crc verify is success + byte[] resultArray = new byte[protocolBytes.Length - 2]; + Array.Copy(protocolBytes, 0, resultArray, 0, protocolBytes.Length - 2); + byte[] crcArray = new byte[2]; + Array.Copy(protocolBytes, protocolBytes.Length - 2, crcArray, 0, 2); + if(resultArray.CalculateCRC16Modbus()!=crcArray.ToUShort()){ + return false; + } + + //every response's start should be 0xff,because the request's start is 0xff + switch(FunctionType){ + case ModbusFunctionType.ReadCoil: + byte readCoilLength = protocolBytes[2]; + byte[] CoilBytes = new byte[readCoilLength]; + Array.Copy(protocolBytes,3,CoilBytes,0,readCoilLength); + SourceValueData = CoilBytes; + Coil = CoilBytes.ToBoolArray(); + return true; + case ModbusFunctionType.ReadDiscreteInput: + byte readDiscreteInputLength = protocolBytes[2]; + byte[] DiscreteInputBytes = new byte[readDiscreteInputLength]; + Array.Copy(protocolBytes,3,DiscreteInputBytes,0,readDiscreteInputLength); + SourceValueData = DiscreteInputBytes; + DiscreteInput = DiscreteInputBytes.ToBoolArray(); + return true; + case ModbusFunctionType.ReadHoldingRegisters: + byte readHoldingRegistersLength = protocolBytes[2]; + byte[] HoldingRegistersBytes = new byte[readHoldingRegistersLength]; + Array.Copy(protocolBytes,3,HoldingRegistersBytes,0,readHoldingRegistersLength); + SourceValueData = HoldingRegistersBytes; + HoldingRegister = HoldingRegistersBytes.ToUShortArray(); + return true; + case ModbusFunctionType.ReadInputRegisters: + byte readInputRegistersLength = protocolBytes[2]; + byte[] InputRegistersBytes = new byte[readInputRegistersLength]; + Array.Copy(protocolBytes,3,InputRegistersBytes,0,readInputRegistersLength); + SourceValueData = InputRegistersBytes; + InputRegister = InputRegistersBytes.ToUShortArray(); + return true; + case ModbusFunctionType.WriteSingleCoil: + return true; + case ModbusFunctionType.WriteSingleHoldingRegister: + return true; + case ModbusFunctionType.WriteMultiCoil: + return true; + case ModbusFunctionType.WriteMultiHoldingRegister: + return true; + default: + return false; + } + + } + catch (Exception) + { + return false; + } + } + } + #endregion + + public enum ModbusFunctionType{ + None = 0x00, + ReadCoil = 0x01, + ReadDiscreteInput = 0x02, + ReadHoldingRegisters = 0x03, + ReadInputRegisters = 0x04, + WriteSingleCoil = 0x05, + WriteSingleHoldingRegister = 0x06, + WriteMultiCoil = 0x0f, + WriteMultiHoldingRegister = 0x10 + } + + public enum ModbusRegisterType{ + None = 0x00, + Coil = 0x01, + DiscreteInput = 0x02, + HoldingRegister = 0x03, + InputRegisters = 0x04 + } + + public enum ModbusErrorCode{ + NoError = 0x00, + IllegalFunction = 0x01, + IllegalDataAddress = 0x02, + IllegalDataValue = 0x03, + SlaveDeviceFailure = 0x04, + Acknowledge = 0x05, + SlaveDeviceBusy = 0x06, + MemoryParityError = 0x08, + GatewayPathUnavailable = 0x0A, + GatewayTargetDeviceFailedToRespond = 0x0B + } +} diff --git a/EGFramework/Module/ProtocolTools/ProtocolToolsInterface.cs b/EGFramework/Module/ProtocolTools/ProtocolToolsInterface.cs new file mode 100644 index 0000000..ed21f06 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/ProtocolToolsInterface.cs @@ -0,0 +1,19 @@ +using System.Collections.Concurrent; +using System.Text; + +namespace EGFramework{ + public interface IProtocolSend{ + public void SendByteData(string destination,byte[] data); + public void SendStringData(string destination,string data); + public void SetEncoding(Encoding textEncoding); + } + public interface IProtocolReceived{ + public ConcurrentQueue GetReceivedMsg(); + } + + public interface IProtocolListener{ + public bool IsEnabled(string ServiceName); + } + + +} diff --git a/EGFramework/Module/ProtocolTools/SerialPort/EGSerialPort.cs b/EGFramework/Module/ProtocolTools/SerialPort/EGSerialPort.cs new file mode 100644 index 0000000..0d7c7f2 --- /dev/null +++ b/EGFramework/Module/ProtocolTools/SerialPort/EGSerialPort.cs @@ -0,0 +1,240 @@ +using System.IO.Ports; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections.Concurrent; + +namespace EGFramework{ + public class EGSerialPort : IModule,IEGFramework,IProtocolSend,IProtocolReceived + { + public Dictionary SerialPortDevices { set; get; } = new Dictionary(); + public Dictionary SerialPortMapping { set; get; } = new Dictionary(); + + public int DefaultBaudRate { set; get; } = 115200; + + // If data pack break,you can set this param to control the data pack completed at least. + private int MinDataPackLength { set; get; } = 0; + + public string ErrorLogs { set; get; } + + public string ReceivedStr { set; get; } = ""; + + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + + public ConcurrentQueue ResponseMsgs { set; get; } = new ConcurrentQueue(); + + public Dictionary ReceivedCache { set; get; } = new Dictionary(); + + public void Init() + { + this.EGRegisterSendAction(request=>{ + if(request.protocolType == ProtocolType.SerialPort){ + if(request.req.ToProtocolData() != "" && request.req.ToProtocolData() != null){ + this.SendSerialStringData(request.sender,request.req.ToProtocolData()); + } + if(request.req.ToProtocolByteData().Length > 0 && request.req.ToProtocolByteData() != null){ + this.SendSerialByteData(request.sender,request.req.ToProtocolByteData()); + } + } + }); + } + + public ConcurrentQueue GetReceivedMsg() + { + return this.ResponseMsgs; + } + + public void SetEncoding(Encoding encoding){ + this.StringEncoding = encoding; + } + + public Encoding GetEncoding(){ + return StringEncoding; + } + + public List RefreshSerialPort(){ + string[] portNames = SerialPort.GetPortNames(); + SerialPortMapping.Clear(); + int index = 0; + foreach (string portName in portNames) + { + SerialPortMapping.Add(portName,index); + index++; + } + return SerialPortMapping.Keys.ToList(); + } + + public void SetBaudRate(int baudRate){ + this.DefaultBaudRate = baudRate; + } + + public bool Open(string serialPort) + { + return Open(serialPort, DefaultBaudRate); + } + /// + /// Open serial port with check if target serial port isOpen. + /// + /// + public bool Open(string serialPort, int baudRate) + { + try + { + if (!SerialPortDevices.ContainsKey(serialPort)) + { + SerialPort newPort = new SerialPort(serialPort, baudRate, Parity.None, 8, StopBits.One); + SerialPortDevices.Add(serialPort, newPort); + ReceivedCache.Add(serialPort, new byte[0]); + if (!SerialPortDevices[serialPort].IsOpen) + { + SerialPortDevices[serialPort].Open(); + SerialPortDevices[serialPort].DataReceived += SerialPort_DataReceived; + // EG.Print("OpenPort " + serialPort + " in " + baudRate); + } + } + else + { + if (!SerialPortDevices[serialPort].IsOpen) + { + SerialPortDevices[serialPort].Open(); + SerialPortDevices[serialPort].BaudRate = baudRate; + } + } + return true; + } + catch (Exception e) + { + ErrorLogs = "[open port error]" + e.ToString(); + EG.Print("Error:"+e); + return false; + } + } + + + /// + /// Close serial port with check if target serial port isOpen. + /// + /// + public void Close(string serialPort) + { + if (SerialPortDevices.ContainsKey(serialPort)) + { + if (SerialPortDevices[serialPort].IsOpen) + { + SerialPortDevices[serialPort].DataReceived -= SerialPort_DataReceived; + SerialPortDevices[serialPort].Close(); + SerialPortDevices.Remove(serialPort); + ReceivedCache.Remove(serialPort); + } + } + else + { + //Not found in SerialPortDevices,need add? + } + } + + public bool SendSerialByteData(string serialPort,byte[] data){ + // if serial port not open,open first + try{ + bool isSuccessPort = Open(serialPort); + if(isSuccessPort){ + SerialPortDevices[serialPort].Write(data, 0, data.Length); + return true; + }else{ + return false; + } + }catch(Exception e){ + ErrorLogs = "[write error]" + e.ToString(); + return false; + } + } + public void SendByteData(string destination,byte[] data){ + SendSerialByteData(destination,data); + } + + public bool SendByteDataOnce(string serialPort,byte[] data){ + bool isSuccessSend = SendSerialByteData(serialPort,data); + if(isSuccessSend){ + SerialPortDevices[serialPort].Close(); + SerialPortDevices.Remove(serialPort); + } + return isSuccessSend; + } + + public bool SendSerialStringData(string serialPort,string data){ + // if serial port not open,open first + try{ + bool isSuccessPort = Open(serialPort); + if(isSuccessPort){ + byte[] encodingData = StringEncoding.GetBytes(data); + SerialPortDevices[serialPort].Write(encodingData, 0, encodingData.Length); + return true; + }else{ + return false; + } + }catch(Exception e){ + ErrorLogs = "[write error]" + e.ToString(); + return false; + } + } + public void SendStringData(string destination,string data){ + SendSerialStringData(destination,data); + } + + public bool SendStringDataOnce(string serialPort,string data){ + bool isSuccessSend = SendSerialStringData(serialPort,data); + if(isSuccessSend){ + SerialPortDevices[serialPort].Close(); + SerialPortDevices.Remove(serialPort); + } + return isSuccessSend; + } + + private void SerialPort_DataReceived(object sender, SerialDataReceivedEventArgs e) + { + //await Task.Run(() => {}).ConfigureAwait(false); + SerialPort serialPort = (SerialPort)sender; + if(serialPort.BytesToRead >= 0){ + int bufferSize = serialPort.BytesToRead; + byte[] buffer = new byte[bufferSize]; + serialPort.Read(buffer,0,serialPort.BytesToRead); + ReceivedCache[serialPort.PortName] = ReceivedCache[serialPort.PortName].Concat(buffer).ToArray(); + } + if(ReceivedCache[serialPort.PortName].Length >= MinDataPackLength){ + string str = StringEncoding.GetString(ReceivedCache[serialPort.PortName]); + EG.Print("[Receive]"+ReceivedCache[serialPort.PortName].ToStringByHex()); + ResponseMsgs.Enqueue(new ResponseMsg(str,ReceivedCache[serialPort.PortName],serialPort.PortName,ProtocolType.SerialPort)); + ReceivedCache[serialPort.PortName] = new byte[0]; + MinDataPackLength = 0; + //this.EGOnReceivedData(new ResponseMsg(str,buffer,serialPort.PortName,ProtocolType.SerialPort)); + }else{ + EG.Print("[Data Get]" + ReceivedCache[serialPort.PortName].Length); + string str = StringEncoding.GetString(ReceivedCache[serialPort.PortName]); + ResponseMsgs.Enqueue(new ResponseMsg(str,ReceivedCache[serialPort.PortName],serialPort.PortName,ProtocolType.SerialPort)); + } + } + + public void SetExpectReceivedDataLength(int leastLength){ + this.MinDataPackLength = leastLength; + } + + public void ClearReceivedCache(string serialPort){ + ReceivedCache[serialPort] = new byte[0]; + } + + public IArchitecture GetArchitecture() + { + return EGArchitectureImplement.Interface; + } + } + public static class CanGetEGSerialPortExtension{ + public static EGSerialPort EGSerialPort(this IEGFramework self){ + return self.GetModule(); + } + + public static SerialPort EGGetSerialPort(this IEGFramework self,string serial){ + return self.GetModule().SerialPortDevices[serial]; + } + } +} diff --git a/EGFramework/Module/SaveTools/Data/EGCsvSave.cs b/EGFramework/Module/SaveTools/Data/EGCsvSave.cs new file mode 100644 index 0000000..4bdb23f --- /dev/null +++ b/EGFramework/Module/SaveTools/Data/EGCsvSave.cs @@ -0,0 +1,499 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Text; +using System.Reflection; +using System.Configuration; + +namespace EGFramework +{ + /// + /// CSV Save tools, support read and write CSV file.The dataKey param is not use.please use "" or any string. + /// + public class EGCsvSave : IEGSaveData, IEGSave, IEGSaveReadOnly + { + public bool IsReadOnly { get; set; } + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + private string DefaultPath { set; get; } + private List CsvDataBlock { get; set; } + private Dictionary CsvDataHeader = new Dictionary(); + public IOCContainer TypeDataContainer = new IOCContainer(); + private string ReadText { set; get; } + + + public void InitSave(string path) + { + ReadDataBlock(path); + } + + public void InitReadOnly(string data) + { + ReadText = data; + if(ReadText != null || ReadText != ""){ + CsvDataBlock = GetCSVDataBlockFromText(ReadText,out CsvDataHeader); + } + } + + public void InitReadOnly(byte[] data) + { + ReadText = StringEncoding.GetString(data); + if(ReadText != null || ReadText != ""){ + CsvDataBlock = GetCSVDataBlockFromText(ReadText,out CsvDataHeader); + } + } + + public void ReadDataBlock(string path){ + DefaultPath = path; + try + { + if (!File.Exists(path)) + { + FileStream newCsvFile = File.Create(path); + CsvDataBlock = new List(); + newCsvFile.Close(); + newCsvFile.Dispose(); + return; + } + FileStream fileStream = new FileStream(path,FileMode.Open); + byte[] buffer = new byte[fileStream.Length]; + fileStream.Read(buffer, 0, (int)fileStream.Length); + fileStream.Close(); + fileStream.Dispose(); + ReadText = StringEncoding.GetString(buffer); + } + catch (System.Exception e) + { + EG.Print("e:" + e); + throw; + } + if(ReadText != null || ReadText != ""){ + CsvDataBlock = GetCSVDataBlockFromText(ReadText,out CsvDataHeader); + } + } + + public void WriteDataBlock(string path){ + try + { + FileStream fileStream = File.Create(path); + string writeText = ""; + string headerText = ""; + foreach(string headStr in CsvDataHeader.Keys){ + headerText+=headStr + ","; + } + headerText = headerText.Remove(headerText.Length-1,1); + writeText = headerText + "\n"; + foreach(string[] lineData in CsvDataBlock){ + string lineText = ""; + foreach(string singleData in lineData){ + lineText += singleData + ","; + } + lineText = lineText.Remove(lineText.Length-1,1); + writeText += lineText + "\n"; + } + writeText = writeText.Remove(writeText.Length-1,1); + byte[] data = StringEncoding.GetBytes(writeText); + fileStream.Write(data,0,data.Length); + fileStream.Close(); + fileStream.Dispose(); + } + catch (System.Exception e) + { + EG.Print("e:" + e); + throw; + } + } + + public List GetCSVDataBlockFromText(string text,out Dictionary header){ + List csvBlock = new List(); + string[] lineData = text.Split('\n'); + string[] headerStr = lineData[0].Split(','); + header = new Dictionary(); + for (int i = 0; i < headerStr.Length; i++) + { + header.Add(headerStr[i],i); + } + for (int lineID = 0; lineID < lineData.Length; lineID++) + { + if (lineID!=0){ + csvBlock.Add(lineData[lineID].Split(',')); + } + } + return csvBlock; + } + + public string[] ReadLine(int id){ + if(CsvDataBlock.Count()>0){ + return CsvDataBlock[id]; + }else{ + return null; + } + } + + public void WriteLine(string[] lineData){ + CsvDataBlock.Add(lineData); + this.WriteDataBlock(DefaultPath); + } + + public void SetData(string dataKey, TData data, object id) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any data to file."); + } + bool IsAdd = false; + int dataID = 0; + if(id.GetType()==typeof(int)){ + dataID = (int)id; + }else if(int.TryParse(id.ToString() ,out dataID)){ + throw new Exception("Id cannot be convert to int!"); + } + if(dataID>=CsvDataBlock.Count() || dataID < 0){ + IsAdd = true; + } + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach(PropertyInfo property in data.GetType().GetProperties()){ + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if(csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)){ + csvSet[CsvDataHeader[csvParam._name]] = property.GetValue(data).ToString(); + } + } + if(IsAdd){ + CsvDataBlock.Add(csvSet); + }else{ + CsvDataBlock[dataID] = csvSet; + } + this.WriteDataBlock(DefaultPath); + } + + public TData GetData(string dataKey, object id) where TData : new() + { + TData data = new TData(); + int dataID = 0; + if(id.GetType()==typeof(int)){ + dataID = (int)id; + }else if(int.TryParse(id.ToString() ,out dataID)){ + throw new Exception("Id cannot be convert to int!"); + } + if(dataID>=CsvDataBlock.Count()){ + throw new IndexOutOfRangeException("Parameter index is out of range."); + } + foreach(PropertyInfo property in data.GetType().GetProperties()){ + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if(csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)){ + string valueStr = CsvDataBlock[dataID][CsvDataHeader[csvParam._name]]; + if(property.PropertyType==typeof(string)){ + property.SetValue(data,valueStr); + }else{ + property.SetValue(data,Convert.ChangeType(valueStr,property.PropertyType)); + } + } + } + return data; + } + public IEnumerable GetAll(string dataKey) where TData : new() + { + // throw new NotImplementedException(); + List DataList = new List(); + PropertyInfo[] properties = typeof(TData).GetProperties(); + for (int dataID = 0; dataID < CsvDataBlock.Count(); dataID++){ + TData data = new TData(); + foreach(PropertyInfo property in properties){ + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if(csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)){ + string valueStr = CsvDataBlock[dataID][CsvDataHeader[csvParam._name]]; + if(property.PropertyType==typeof(string)){ + property.SetValue(data,valueStr); + }else{ + property.SetValue(data,Convert.ChangeType(valueStr,property.PropertyType)); + } + } + } + DataList.Add(data); + } + TypeDataContainer.Register(DataList); + return DataList; + } + + public IEnumerable GetPage(string dataKey, int pageIndex, int pageSize) where TData : new() + { + if(pageIndex <= 0){ + pageIndex = 1; + } + int startPointer = (pageIndex - 1) * pageSize; + List DataList = new List(); + PropertyInfo[] properties = typeof(TData).GetProperties(); + for (int dataID = startPointer; dataID < startPointer+pageSize; dataID++){ + TData data = new TData(); + foreach(PropertyInfo property in properties){ + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if(csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)){ + string valueStr = CsvDataBlock[dataID][CsvDataHeader[csvParam._name]]; + if(property.PropertyType==typeof(string)){ + property.SetValue(data,valueStr); + }else{ + property.SetValue(data,Convert.ChangeType(valueStr,property.PropertyType)); + } + } + } + DataList.Add(data); + } + TypeDataContainer.Register(DataList); + return DataList; + } + + public IEnumerable FindData(string dataKey, Expression> expression) where TData : new() + { + List sourceList; + if(TypeDataContainer.self.ContainsKey(typeof(List))){ + sourceList = TypeDataContainer.Get>(); + }else{ + sourceList = (List)GetAll(dataKey); + } + return sourceList.Where(expression.Compile()); + } + + public IEnumerable FindData(string dataKey, string columnName, string keyWords) where TData : new() + { + // throw new NotImplementedException(); + List DataList = new List(); + PropertyInfo[] properties = typeof(TData).GetProperties(); + if (!CsvDataHeader.ContainsKey(columnName)) + { + EG.Print("Column not found!"); + return null; + } + for (int dataID = 0; dataID < CsvDataBlock.Count(); dataID++) + { + TData data = new TData(); + string compareStr = CsvDataBlock[dataID][CsvDataHeader[columnName]]; + if (!compareStr.Contains(keyWords)) + { + continue; + } + foreach (PropertyInfo property in properties) + { + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if (csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)) + { + string valueStr = CsvDataBlock[dataID][CsvDataHeader[csvParam._name]]; + if (property.PropertyType == typeof(string)) + { + property.SetValue(data, valueStr); + } + else + { + property.SetValue(data, Convert.ChangeType(valueStr, property.PropertyType)); + } + } + } + DataList.Add(data); + } + return DataList; + } + + public void AddData(string dataKey, TData data) + { + if (IsReadOnly) + { + throw new Exception("This file is readonly! can't set any data to file."); + } + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach (PropertyInfo property in data.GetType().GetProperties()) + { + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if (csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)) + { + csvSet[CsvDataHeader[csvParam._name]] = property.GetValue(data).ToString(); + } + } + CsvDataBlock.Add(csvSet); + this.WriteDataBlock(DefaultPath); + } + + public void AddData(string dataKey, IEnumerable dataSet) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any data to file."); + } + foreach(TData data in dataSet){ + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach(PropertyInfo property in data.GetType().GetProperties()){ + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if(csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)){ + csvSet[CsvDataHeader[csvParam._name]] = property.GetValue(data).ToString(); + } + } + CsvDataBlock.Add(csvSet); + } + this.WriteDataBlock(DefaultPath); + } + + public void AddData(string datakey, Dictionary data) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any data to file."); + } + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach(KeyValuePair param in data){ + if(CsvDataHeader.ContainsKey(param.Key)){ + csvSet[CsvDataHeader[param.Key]] = param.Value.ToString(); + } + } + CsvDataBlock.Add(csvSet); + this.WriteDataBlock(DefaultPath); + } + + public void AddGroup(string datakey, List> dataGroup) + { + if (IsReadOnly) + { + throw new Exception("This file is readonly! can't set any data to file."); + } + foreach (Dictionary data in dataGroup) + { + if (CsvDataHeader.Count == 0) + { + int id = 0; + foreach (string key in data.Keys) + { + CsvDataHeader.Add(key, id); + id++; + } + } + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach (KeyValuePair param in data) + { + if (CsvDataHeader.ContainsKey(param.Key)) + { + csvSet[CsvDataHeader[param.Key]] = param.Value.ToString(); + } + } + CsvDataBlock.Add(csvSet); + } + this.WriteDataBlock(DefaultPath); + } + + public int RemoveData(string dataKey, object id) + { + if (IsReadOnly) + { + throw new Exception("This file is readonly! can't set any data to file."); + } + bool IsAdd = false; + int dataID = 0; + if (id.GetType() == typeof(int)) + { + dataID = (int)id; + } + else if (int.TryParse(id.ToString(), out dataID)) + { + throw new Exception("Id cannot be convert to int!"); + } + if (dataID >= CsvDataBlock.Count() || dataID < 0) + { + IsAdd = true; + } + if (IsAdd) + { + return 0; + } + else + { + CsvDataBlock.RemoveAt(dataID); + } + this.WriteDataBlock(DefaultPath); + return 1; + } + + public void UpdateData(string dataKey, TData data, object id) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any data to file."); + } + bool IsAdd = false; + int dataID = 0; + if(id.GetType()==typeof(int)){ + dataID = (int)id; + }else if(int.TryParse(id.ToString() ,out dataID)){ + throw new Exception("Id cannot be convert to int!"); + } + if(dataID>=CsvDataBlock.Count() || dataID < 0){ + IsAdd = true; + } + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach(PropertyInfo property in data.GetType().GetProperties()){ + CsvParamAttribute csvParam = property.GetCustomAttribute(); + if(csvParam != null && CsvDataHeader.ContainsKey(csvParam._name)){ + csvSet[CsvDataHeader[csvParam._name]] = property.GetValue(data).ToString(); + } + } + if(!IsAdd){ + CsvDataBlock[dataID] = csvSet; + }else{ + throw new Exception("Data not found!"); + } + this.WriteDataBlock(DefaultPath); + } + + + public void UpdateData(string dataKey, Dictionary data, object id) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any data to file."); + } + bool IsAdd = false; + int dataID = 0; + if(id.GetType()==typeof(int)){ + dataID = (int)id; + }else if(int.TryParse(id.ToString() ,out dataID)){ + throw new Exception("Id cannot be convert to int!"); + } + if(dataID>=CsvDataBlock.Count() || dataID < 0){ + IsAdd = true; + } + string[] csvSet = new string[CsvDataHeader.Keys.Count()]; + foreach(KeyValuePair param in data){ + if(CsvDataHeader.ContainsKey(param.Key)){ + csvSet[CsvDataHeader[param.Key]] = param.Value.ToString(); + } + } + if(!IsAdd){ + CsvDataBlock[dataID] = csvSet; + }else{ + throw new Exception("Data not found!"); + } + this.WriteDataBlock(DefaultPath); + } + + public IEnumerable GetKeys() + { + return CsvDataHeader.Keys; + } + + public bool ContainsKey(string dataKey) + { + return CsvDataHeader.ContainsKey(dataKey); + } + + public bool ContainsData(string dataKey, object id) + { + return CsvDataBlock.Count() > 0 && id.GetType() == typeof(int) && (int)id < CsvDataBlock.Count(); + } + + public int GetDataCount(string dataKey) + { + return CsvDataBlock.Count(); + } + + } + + [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)] + public class CsvParamAttribute: Attribute{ + public string _name { set; get; } + public CsvParamAttribute(string name){ + this._name = name; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Data/EGDapper.cs b/EGFramework/Module/SaveTools/Data/EGDapper.cs new file mode 100644 index 0000000..d73d5df --- /dev/null +++ b/EGFramework/Module/SaveTools/Data/EGDapper.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using Dapper; +//ORM Save tools. First support SQLite and MySQL,In future we will support other Database who implement DBConnection. +namespace EGFramework +{ + public abstract class EGDapper : IEGSave, IEGSaveData, IEGDataBase + { + public DbConnection Connection { get; set; } + public string ExceptionMsg; + + public abstract void InitSave(string conn); + public IEnumerable GetAll(string dataKey) where TData : new() + { + IEnumerable result = Connection.Query("select * from " + dataKey); + return result; + } + + public IEnumerable GetPage(string dataKey, int pageIndex, int pageSize) where TData : new() + { + if (pageIndex <= 0) + { + pageIndex = 1; + } + int startPointer = (pageIndex - 1) * pageSize; + IEnumerable result = Connection.Query("select * from " + dataKey + " limit " + startPointer + "," + pageSize); + return result; + } + + public TData GetData(string dataKey, object id) where TData : new() + { + TData result = Connection.QuerySingle("select * from " + dataKey + " where ID = @ID", new { ID = id }); + return result; + } + + public IEnumerable FindData(string dataKey, Expression> expression) where TData : new() + { + IEnumerable sourceList = Connection.Query("select * from " + dataKey); + return sourceList.Where(expression.Compile()); + } + + public IEnumerable FindData(string dataKey, string columnName, string keyWords) where TData : new() + { + IEnumerable sourceList = Connection.Query("select * from " + dataKey + " where " + columnName + " like " + "'%"+keyWords+"%'"); + return sourceList; + } + + public void SetData(string dataKey, TData data, object id) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + else + { + if (this.ContainsData(dataKey, id)) + { + UpdateData(dataKey, data, id); + } + else + { + AddData(dataKey, data); + } + //EG.Print("data:" + data); + } + } + + public void AddData(string dataKey, TData data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + // throw new System.NotImplementedException(); + Type DataType = typeof(TData); + var properties = DataType.GetProperties(); + var fields = DataType.GetFields(); + string keySet = ""; + string keySetParam = ""; + foreach (PropertyInfo key in properties) + { + if (key.Name == "ID" || key.Name == "Id" || key.Name == "id") + { + continue; + } + if (DataType.IsPrimitive) + { + keySet += key.Name + ","; + keySetParam += "@" + key.Name + ","; + } + else + { + keySet += key.Name + ","; + keySetParam += "'" + key.GetValue(data).ToString() + "',"; + } + } + + foreach (FieldInfo key in fields) + { + if (key.Name == "ID" || key.Name == "Id" || key.Name == "id") + { + continue; + } + if (DataType.IsPrimitive) + { + keySet += key.Name + ","; + keySetParam += "@" + key.Name + ","; + } + else + { + keySet += key.Name + ","; + keySetParam += "'" + key.GetValue(data).ToString() + "',"; + } + } + + keySet = keySet.TrimEnd(','); + keySetParam = keySetParam.TrimEnd(','); + int count = Connection.Execute(@"insert " + dataKey + "(" + keySet + ") values(" + keySetParam + ")", data); + //EG.Print("count:" + count); + } + + /// + /// TData Must be all member to type of properties. such as 'public string Name { set; get; }'. Properties cannot be except string, primitive + /// + /// + /// + /// + public void AddData(string dataKey, IEnumerable data) + { + Type DataType = typeof(TData); + var properties = DataType.GetProperties(); + string keySet = ""; + string keySetParam = ""; + foreach (PropertyInfo key in properties) + { + if (key.Name == "ID" || key.Name == "Id" || key.Name == "id") + { + continue; + } + keySet += key.Name + ","; + keySetParam += "@" + key.Name + ","; + } + + keySet = keySet.TrimEnd(','); + keySetParam = keySetParam.TrimEnd(','); + string sql = @"insert " + dataKey + "(" + keySet + ") values(" + keySetParam + ")"; + EG.Print("sql:" + sql); + int count = Connection.Execute(sql, data); + //EG.Print("count:" + count); + } + public void AddData(string dataKey, Dictionary data) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + string keySet = ""; + string keySetParam = ""; + foreach (KeyValuePair param in data) + { + if (param.Key == "ID" || param.Key == "Id" || param.Key == "id") + { + continue; + } + keySet += param.Key + ","; + if (param.Value is string) + { + keySetParam += "'" + param.Value + "',"; + } + else + { + keySetParam += param.Value + ","; + } + } + keySet = keySet.TrimEnd(','); + keySetParam = keySetParam.TrimEnd(','); + EG.Print("insert into " + dataKey + "(" + keySet + ") values(" + keySetParam + ")"); + int count = Connection.Execute("insert into " + dataKey + "(" + keySet + ") values(" + keySetParam + ")"); + } + + public void AddGroup(string datakey, List> dataGroup) + { + foreach (Dictionary data in dataGroup) + { + this.AddData(datakey, data); + } + } + + public int RemoveData(string dataKey, object id) + { + int count = Connection.Execute(@"delete from " + dataKey + " where id = @ID", new { ID = id }); + return count; + //EG.Print("count:" + count); + } + + public void UpdateData(string dataKey, TData data, object id) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + Type DataType = typeof(TData); + var properties = DataType.GetProperties(); + string keyMap = ""; + foreach (PropertyInfo key in properties) + { + if (key.Name == "ID" || key.Name == "id" || key.Name == "Id") + { + continue; + } + keyMap += key.Name + " = @" + key.Name + ","; + } + keyMap = keyMap.TrimEnd(','); + string sql = @"update " + dataKey + " set " + keyMap + " where ID = " + id; + EG.Print(sql); + int count = Connection.Execute(sql, data); + //EG.Print("count:" + count); + } + + public void UpdateData(string dataKey, Dictionary data, object id) + { + if (data == null) + { + throw new ArgumentNullException(nameof(data)); + } + string keyMap = ""; + foreach (KeyValuePair param in data) + { + if (param.Key == "ID" || param.Key == "Id" || param.Key == "id") + { + continue; + } + if (param.Value is string) + { + keyMap += param.Key + " = '" + param.Value + "',"; + } + else + { + keyMap += param.Key + " = " + param.Value + ","; + } + } + keyMap = keyMap.TrimEnd(','); + if (id is string) + { + id = "'" + id + "'"; + } + string sql = @"update " + dataKey + " set " + keyMap + " where ID = " + id; + EG.Print(sql); + int count = Connection.Execute(sql, data); + } + + public virtual IEnumerable GetKeys() + { + IEnumerable result = Connection.Query("show tables"); + return result; + } + + public bool ContainsKey(string dataKey) + { + return GetKeys().Contains(dataKey); + } + + public bool ContainsData(string dataKey, object id) + { + try + { + var result = Connection.QuerySingle("select * from " + dataKey + " where ID = @ID", new { ID = id }); + if (result == null) + { + return false; + } + else + { + return true; + } + } + catch (System.Exception) + { + return false; + } + } + + public int GetDataCount(string dataKey) + { + int count = Connection.QuerySingle("select COUNT(*) from " + dataKey); + return count; + } + + public DbConnection GetConnection() + { + return Connection; + } + + public void CreateTable(string dataKey) + { + // throw new System.NotImplementedException(); + if (this.ContainsKey(dataKey)) + { + EG.Print("Table " + dataKey + " has been created!"); + return; + } + string createSql = typeof(TData).ToCreateTableSQL(dataKey); + int count = Connection.Execute(createSql); + } + + public void CreateTable(string dataKey, Dictionary tableParam) + { + if (this.ContainsKey(dataKey)) + { + EG.Print("Table " + dataKey + " has been created!"); + return; + } + Dictionary tableType = new Dictionary(); + foreach (KeyValuePair pair in tableParam) + { + tableType.Add(pair.Key, pair.Value.GetType()); + } + string createSql = tableType.ToCreateTableSQL(dataKey); + int count = Connection.Execute(createSql); + } + + public void DropTable(string dataKey) + { + if (this.ContainsKey(dataKey)) + { + Connection.Execute(@"DROP TABLE IF EXISTS "+dataKey+""); + } + } + + + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Data/EGLiteDBSave.cs b/EGFramework/Module/SaveTools/Data/EGLiteDBSave.cs new file mode 100644 index 0000000..292b630 --- /dev/null +++ b/EGFramework/Module/SaveTools/Data/EGLiteDBSave.cs @@ -0,0 +1,162 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; +using LiteDB; + +namespace EGFramework +{ + public class EGLiteDBSave : IEGSave,IEGSaveData + { + private string DefaultPath { set; get; } + private LiteDatabase _Database { set; get; } + private LiteDatabase Database{ + get { + if(_Database == null){ + InitSave(DefaultPath); + } + return _Database; + } + } + + public void InitSave(string path) + { + DefaultPath = path; + if (!Directory.Exists(Path.GetDirectoryName(DefaultPath))) + { + Directory.CreateDirectory(Path.GetDirectoryName(DefaultPath)); + } + // Default is "SaveData/DefaultLiteDBData.db" + _Database = new LiteDatabase(path); + } + + public TData GetData(string dataKey, object id) where TData : new() + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + TData result = collection.FindById((BsonValue)id); + return result; + } + + public void SetData(string dataKey, TData data, object id) + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + if(collection.FindById((BsonValue)id)==null){ + collection.Insert((BsonValue)id, data); + } + collection.Update(data); + } + + public IEnumerable GetAll(string dataKey) where TData : new() + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + return collection.FindAll(); + } + + public IEnumerable GetPage(string dataKey, int pageIndex, int pageSize) where TData : new() + { + if(pageIndex <= 0){ + pageIndex = 1; + } + int startPointer = (pageIndex - 1) * pageSize; + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + IEnumerable result = collection.FindAll().Skip(startPointer).Take(pageSize); + return result; + } + + public IEnumerable FindData(string dataKey, Expression> expression) where TData : new() + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + return collection.Find(expression); + } + + public IEnumerable FindData(string dataKey, string columnName, string keyWords) where TData : new() + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + return collection.Find(Query.EQ(columnName,new BsonValue(keyWords))); + } + + public void AddData(string dataKey, TData data) + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + collection.Insert(data); + } + + public void AddData(string dataKey, IEnumerable data) + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + collection.Insert(data); + } + + public void AddData(string dataKey, Dictionary data) + { + ILiteCollection collection = Database.GetCollection(dataKey); + BsonDocument keyValuePairs = new BsonDocument(); + foreach (var param in data) { + keyValuePairs.Add(param.Key, new BsonValue(param.Value)); + } + collection.Insert(keyValuePairs); + } + + public void AddGroup(string datakey, List> dataGroup) + { + foreach (Dictionary data in dataGroup) + { + this.AddData(datakey, data); + } + } + + public int RemoveData(string dataKey, object id) + { + ILiteCollection collection = Database.GetCollection(dataKey); + if (collection.FindById((BsonValue)id) != null) + { + collection.Delete((BsonValue)id); + return 1; + } + else + { + return 0; + } + + } + + public void UpdateData(string dataKey, TData data, object id) + { + LiteCollection collection = (LiteCollection)Database.GetCollection(dataKey); + collection.Update((BsonValue)id,data); + } + + public void UpdateData(string dataKey, Dictionary data, object id) + { + ILiteCollection collection = Database.GetCollection(dataKey); + BsonDocument keyValuePairs = new BsonDocument(); + foreach (var param in data) { + keyValuePairs.Add(param.Key, new BsonValue(param.Value)); + } + collection.Update((BsonValue)id,keyValuePairs); + } + + public IEnumerable GetKeys() + { + return Database.GetCollectionNames(); + } + + public bool ContainsKey(string dataKey) + { + return GetKeys().Contains(dataKey); + } + + public bool ContainsData(string dataKey, object id) + { + return Database.GetCollection(dataKey).Exists((BsonValue)id); + } + + public int GetDataCount(string dataKey) + { + return Database.GetCollection(dataKey).Count(); + } + + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Data/EGMysqlSave.cs b/EGFramework/Module/SaveTools/Data/EGMysqlSave.cs new file mode 100644 index 0000000..7a6b4d0 --- /dev/null +++ b/EGFramework/Module/SaveTools/Data/EGMysqlSave.cs @@ -0,0 +1,29 @@ +using MySql.Data.MySqlClient; + +namespace EGFramework{ + /// + /// This Class used Dapper for operate MySQL database. + /// + public class EGMysqlSave : EGDapper + { + public string Conn { set; get; } + public bool IsInit { set; get; } + /// + /// "server="+Address+";port="+Port+";uid="+UserName+";pwd="+Password+";database="+DataBase+";" + /// + /// files conn Str or address ip port,username and passwd + public override void InitSave(string conn) + { + try + { + Connection = new MySqlConnection(conn); + IsInit = true; + this.Conn = conn; + } + catch (System.Exception e) + { + EG.Print("e:" + e); + } + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Data/EGSqliteSave.cs b/EGFramework/Module/SaveTools/Data/EGSqliteSave.cs new file mode 100644 index 0000000..c06f138 --- /dev/null +++ b/EGFramework/Module/SaveTools/Data/EGSqliteSave.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Dapper; +using Microsoft.Data.Sqlite; + +namespace EGFramework{ + public class EGSqliteSave : EGDapper + { + public string Conn { set; get; } + public bool IsInit { set; get; } + + /// + /// If path not exist, create it. + /// + /// please add *.db suffix or your db file suffix + public override void InitSave(string path) + { + if (!File.Exists(path)) + { + Directory.CreateDirectory(path); + } + InitDatabase(path); + } + + /// + /// Init database with path. + /// + /// name is the file path.such as SaveData.db + public void InitDatabase(string dataBaseName) + { + Connection = new SqliteConnection("Data Source=" + dataBaseName + ";Mode=ReadWriteCreate;"); // Open the connection: + try + { + // Connection.Open(); + this.Conn = dataBaseName; + IsInit = true; + } + catch (Exception ex) + { + ExceptionMsg = ex.ToString(); + } + } + public override IEnumerable GetKeys() + { + IEnumerable result = Connection.Query("SELECT name FROM sqlite_schema WHERE type='table' AND name NOT LIKE 'sqlite_%'"); + return result; + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/EGSave.cs b/EGFramework/Module/SaveTools/EGSave.cs new file mode 100644 index 0000000..90c91a0 --- /dev/null +++ b/EGFramework/Module/SaveTools/EGSave.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Collections.Generic; +using Dapper; +using System.Linq; + +namespace EGFramework +{ + public class EGSave : EGModule + { + #region About Godot File's PATH + // Godot's Path has res:// and user:// + // UserPath is used for every platform such as android. + // You can use ProjectSettings.GlobalizePath("") to convert a "local" path like res://path/to/file.txt to an absolute OS path. + #endregion + + public HashSet KeySet = new HashSet(); + public Dictionary SaveMapping = new Dictionary(); + + public EGJsonSave DefaultJsonSave { set; get; } + public EGSave() { } + public override void Init() + { + DefaultJsonSave = Load(Directory.GetCurrentDirectory()+"/SaveData/DefaultJsonSave.json"); + } + #region Load Data or Object and Unload + public TSaveData Load(string path) where TSaveData : IEGSave, new() + { + KeySet.Add(path); + TSaveData saveData = new TSaveData(); + saveData.InitSave(path); + SaveMapping.Add(path, saveData); + return saveData; + } + + public TReadOnlyData Read(string key, string data) where TReadOnlyData : IEGSaveReadOnly, new() + { + KeySet.Add(key); + TReadOnlyData readOnlyData = new TReadOnlyData(); + readOnlyData.InitReadOnly(data); + SaveMapping.Add(key, readOnlyData); + return readOnlyData; + } + + public TReadOnlyData Read(string key, byte[] data) where TReadOnlyData : IEGSaveReadOnly, new() + { + KeySet.Add(key); + TReadOnlyData readOnlyData = new TReadOnlyData(); + readOnlyData.InitReadOnly(data); + SaveMapping.Add(key, readOnlyData); + return readOnlyData; + } + + #endregion + + + #region Default Json Operation + public void SetObjectToJson(TObject obj) + { + DefaultJsonSave.SetObject(typeof(TObject).ToString(), obj); + } + public TObject GetObjectFromJson() where TObject : new(){ + return DefaultJsonSave.GetObject(typeof(TObject).ToString()); + } + #endregion + + } + + public static class CanGetEGSaveExtension{ + public static EGSave EGSave(this IEGFramework self){ + return self.GetModule(); + } + + public static string GetDirectoryName(this string path){ + return Path.GetDirectoryName(path); + } + + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/File/EGFtpSave.cs b/EGFramework/Module/SaveTools/File/EGFtpSave.cs new file mode 100644 index 0000000..0114ccd --- /dev/null +++ b/EGFramework/Module/SaveTools/File/EGFtpSave.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using System.IO; +using FluentFTP; + +namespace EGFramework{ + public class EGFtpSave : IEGSave, IEGSaveFile + { + public FtpClient FTPClient { set; get; } + + public void InitSave(string host) + { + this.FTPClient = new FtpClient(host); + } + public void InitUser(string user, string password) + { + this.FTPClient.Credentials = new System.Net.NetworkCredential(user, password); + } + + public void CopyFile(string sourcePath, string copyPath) + { + FTPClient.Connect(); + Stream fileStream = FTPClient.OpenRead(sourcePath); + FTPClient.UploadStream(fileStream, copyPath, FtpRemoteExists.Overwrite, true); + fileStream.Close(); + FTPClient.Disconnect(); + } + + public void DownloadFile(string remotePath, string localPath) + { + FTPClient.Connect(); + FTPClient.DownloadFile(localPath, remotePath, FtpLocalExists.Overwrite); + FTPClient.Disconnect(); + } + + public Stream DownloadFile(string remotePath) + { + FTPClient.Connect(); + Stream fileStream = FTPClient.OpenRead(remotePath); + FTPClient.Disconnect(); + return fileStream; + } + + public bool IsRemoteDirectoryExist(string remotePath) + { + FTPClient.Connect(); + bool isExist = FTPClient.DirectoryExists(remotePath); + FTPClient.Disconnect(); + return isExist; + } + + public bool IsRemoteFileExist(string remotePath) + { + FTPClient.Connect(); + bool isExist = FTPClient.FileExists(remotePath); + FTPClient.Disconnect(); + return isExist; + } + + public IEnumerable ListRemoteFilePath(string remotePath) + { + FTPClient.Connect(); + FtpListItem[] nameList = FTPClient.GetListing(remotePath); + List fileList = new List(); + foreach (var item in nameList) + { + IEGFileMsg fileMsg = new EGFileMsg(); + fileMsg.Init(item.Name, item.Type == FtpObjectType.Directory, item.FullName, item.Size/1024, item.Modified); + fileList.Add(fileMsg); + } + FTPClient.Disconnect(); + return fileList; + } + + public void MakeDirectory(string remotePath) + { + FTPClient.Connect(); + FTPClient.CreateDirectory(remotePath, true); + FTPClient.Disconnect(); + } + + public void MoveFile(string sourcePath, string movePath) + { + FTPClient.Connect(); + FTPClient.MoveFile(sourcePath, movePath ,FtpRemoteExists.Overwrite); + FTPClient.Disconnect(); + } + + public void RemoveFile(string remotePath) + { + FTPClient.Connect(); + FTPClient.DeleteFile(remotePath); + FTPClient.Disconnect(); + } + + public void SyncFile(string remotePath, string localPath) + { + FTPClient.Connect(); + FTPClient.DownloadFile(localPath, remotePath, FtpLocalExists.Overwrite); + FTPClient.Disconnect(); + } + + public void UploadFile(FileStream localFileStream, string remotePath) + { + FTPClient.Connect(); + FTPClient.UploadStream(localFileStream, remotePath, FtpRemoteExists.Overwrite, true); + FTPClient.Disconnect(); + } + + public void UploadFile(string localPath, string remotePath) + { + FTPClient.Connect(); + FTPClient.UploadFile(localPath, remotePath, FtpRemoteExists.Overwrite, true, FtpVerify.Retry); + FTPClient.Disconnect(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/File/EGLocalFileSave.cs b/EGFramework/Module/SaveTools/File/EGLocalFileSave.cs new file mode 100644 index 0000000..db35faf --- /dev/null +++ b/EGFramework/Module/SaveTools/File/EGLocalFileSave.cs @@ -0,0 +1,173 @@ +using System.Collections.Generic; +using System.IO; +using System.Runtime.InteropServices; + +namespace EGFramework{ + /// + /// This class is used to save files locally. such as u disk or other local path. used for backup or other purpose. + /// + public class EGLocalFileSave : IEGSave, IEGSaveFile + { + public string RootPath { get; set; } + + public void InitSave(string path) + { + this.RootPath = path; + } + + public void CopyFile(string sourcePath, string copyPath) + { + if(IsRemoteFileExist(sourcePath)){ + string sourceCombinedPath = Path.Combine(RootPath, sourcePath); + string copyCombinedPath = Path.Combine(RootPath, copyPath); + File.Copy(sourceCombinedPath,copyCombinedPath,true); + }else{ + throw new FileNotFoundException("File not exist!"); + } + } + + public void DownloadFile(string remotePath, string localPath) + { + if(IsRemoteFileExist(remotePath)){ + string combinedPath = Path.Combine(RootPath, remotePath); + File.Copy(combinedPath,localPath,true); + }else{ + throw new FileNotFoundException("File not exist!"); + } + } + + public Stream DownloadFile(string remotePath) + { + if(IsRemoteFileExist(remotePath)){ + string combinedPath = Path.Combine(RootPath, remotePath); + FileStream fileStream = new FileStream(combinedPath,FileMode.Open,FileAccess.Read); + return fileStream; + }else{ + throw new FileNotFoundException("File not exist!"); + } + } + + public bool IsRemoteDirectoryExist(string remotePath) + { + string combinedPath = Path.Combine(RootPath, remotePath); + return Directory.Exists(combinedPath); + } + + public bool IsRemoteFileExist(string remotePath) + { + string combinedPath = Path.Combine(RootPath, remotePath); + return File.Exists(combinedPath); + } + + public IEnumerable ListLocalFilePath(string localPath) + { + string [] filePaths = Directory.GetFiles(localPath); + string [] directoryPaths = Directory.GetDirectories(localPath); + List fileMsgs = new List(); + foreach(string filePath in filePaths){ + FileInfo fileInfo = new FileInfo(filePath); + EGFileMsg msg = new EGFileMsg(); + msg.Init(fileInfo.Name,false,filePath,fileInfo.Length,fileInfo.LastWriteTime); + fileMsgs.Add(msg); + } + foreach(string directoryPath in directoryPaths){ + DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath); + EGFileMsg msg = new EGFileMsg(); + msg.Init(directoryInfo.Name,true,directoryPath,null,directoryInfo.LastWriteTime); + fileMsgs.Add(msg); + } + return fileMsgs; + } + + public IEnumerable ListRemoteFilePath(string remotePath) + { + string combinedPath = Path.Combine(RootPath, remotePath); + string [] filePaths = Directory.GetFiles(combinedPath); + string [] directoryPaths = Directory.GetDirectories(combinedPath); + List fileMsgs = new List(); + foreach(string filePath in filePaths){ + FileInfo fileInfo = new FileInfo(filePath); + EGFileMsg msg = new EGFileMsg(); + msg.Init(fileInfo.Name,false,filePath,fileInfo.Length,fileInfo.LastWriteTime); + fileMsgs.Add(msg); + } + foreach(string directoryPath in directoryPaths){ + DirectoryInfo directoryInfo = new DirectoryInfo(directoryPath); + EGFileMsg msg = new EGFileMsg(); + msg.Init(directoryInfo.Name,true,directoryPath,null,directoryInfo.LastWriteTime); + fileMsgs.Add(msg); + } + return fileMsgs; + } + + public void MakeDirectory(string remotePath) + { + string combinedPath = Path.Combine(RootPath, remotePath); + if(Directory.Exists(combinedPath) == false){ + Directory.CreateDirectory(combinedPath); + } + } + + public void MoveFile(string sourcePath, string movePath) + { + if(IsRemoteFileExist(sourcePath)){ + string sourceCombinedPath = Path.Combine(RootPath, sourcePath); + string moveCombinedPath = Path.Combine(RootPath, movePath); + File.Move(sourceCombinedPath,moveCombinedPath); + }else{ + throw new FileNotFoundException("File not exist!"); + } + } + + public void RemoveFile(string remotePath) + { + if(IsRemoteFileExist(remotePath)){ + string combinedPath = Path.Combine(RootPath, remotePath); + File.Delete(combinedPath); + }else{ + throw new FileNotFoundException("File not exist!"); + } + } + + public void SyncFile(string remotePath, string localPath) + { + FileInfo fileInfo = new FileInfo(localPath); + if(fileInfo.Exists == false){ + throw new FileNotFoundException("Local File not exist!"); + } + FileInfo remoteFileInfo = new FileInfo(remotePath); + if(remoteFileInfo.Exists == false){ + throw new FileNotFoundException("Remote File not exist!"); + } + if(fileInfo.LastWriteTime > remoteFileInfo.LastWriteTime){ + string combinedPath = Path.Combine(RootPath, remotePath); + File.Copy(localPath,combinedPath,true); + }else if(fileInfo.LastWriteTime < remoteFileInfo.LastWriteTime){ + string combinedPath = Path.Combine(RootPath, remotePath); + File.Copy(combinedPath,localPath,true); + }else{ + EG.Print("File is same, no need to sync!"); + } + } + + public void UploadFile(FileStream localFileStream, string remotePath) + { + string combinedPath = Path.Combine(RootPath, remotePath); + FileStream fileStream = new FileStream(combinedPath,FileMode.Create,FileAccess.Write); + localFileStream.CopyTo(fileStream); + fileStream.Close(); + localFileStream.Close(); + fileStream.Dispose(); + } + + public void UploadFile(string localPath, string remotePath) + { + if(File.Exists(localPath)){ + string combinedPath = Path.Combine(RootPath, remotePath); + File.Copy(localPath,combinedPath,true); + }else{ + throw new FileNotFoundException("File not exist!"); + } + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/File/EGSftpSave.cs b/EGFramework/Module/SaveTools/File/EGSftpSave.cs new file mode 100644 index 0000000..aedbc27 --- /dev/null +++ b/EGFramework/Module/SaveTools/File/EGSftpSave.cs @@ -0,0 +1,98 @@ +using System.Collections.Generic; +using System.IO; +using Newtonsoft.Json; +using Renci.SshNet; + +namespace EGFramework{ + public struct EGSftpHost + { + public string Host { set; get; } + public string User { set; get; } + public string Password { set; get; } + public int Port { set; get; } + } + public class EGSftpSave : IEGSave,IEGSaveFile + { + public SftpClient Sftp { set; get; } + + /// + /// Host is a json string, such as {"Host":"","User":"","Password":"","Port":22} + /// + /// + public void InitSave(string hostJson) + { + EGSftpHost host = JsonConvert.DeserializeObject(hostJson); + if(host.Port == 0){ + host.Port = 22; + } + this.Sftp = new SftpClient(host.Host, host.Port, host.User, host.Password); + // this.Sftp = new SftpClient(path); + } + + public void CopyFile(string sourcePath, string copyPath) + { + throw new System.NotImplementedException(); + } + + public void DownloadFile(string remotePath, string localPath) + { + throw new System.NotImplementedException(); + } + + public Stream DownloadFile(string remotePath) + { + throw new System.NotImplementedException(); + } + + + public bool IsRemoteDirectoryExist(string remotePath) + { + throw new System.NotImplementedException(); + } + + public bool IsRemoteFileExist(string remotePath) + { + throw new System.NotImplementedException(); + } + + public IEnumerable ListLocalFilePath(string localPath) + { + throw new System.NotImplementedException(); + } + + public IEnumerable ListRemoteFilePath(string remotePath) + { + throw new System.NotImplementedException(); + } + + public void MakeDirectory(string remotePath) + { + throw new System.NotImplementedException(); + } + + public void MoveFile(string sourcePath, string movePath) + { + throw new System.NotImplementedException(); + } + + public void RemoveFile(string remotePath) + { + throw new System.NotImplementedException(); + } + + public void SyncFile(string remotePath, string localPath) + { + throw new System.NotImplementedException(); + } + + public void UploadFile(FileStream localFileStream, string remotePath) + { + throw new System.NotImplementedException(); + } + + public void UploadFile(string localPath, string remotePath) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/File/EGWebDavSave.cs b/EGFramework/Module/SaveTools/File/EGWebDavSave.cs new file mode 100644 index 0000000..257c130 --- /dev/null +++ b/EGFramework/Module/SaveTools/File/EGWebDavSave.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.IO; + +namespace EGFramework{ + public class EGWebDavSave : IEGSave, IEGSaveFile + { + public void InitSave(string path) + { + throw new System.NotImplementedException(); + } + + public void CopyFile(string sourcePath, string copyPath) + { + throw new System.NotImplementedException(); + } + + public void DownloadFile(string remotePath, string localPath) + { + throw new System.NotImplementedException(); + } + + public Stream DownloadFile(string remotePath) + { + throw new System.NotImplementedException(); + } + + public bool IsRemoteDirectoryExist(string remotePath) + { + throw new System.NotImplementedException(); + } + + public bool IsRemoteFileExist(string remotePath) + { + throw new System.NotImplementedException(); + } + + public IEnumerable ListLocalFilePath(string localPath) + { + throw new System.NotImplementedException(); + } + + public IEnumerable ListRemoteFilePath(string remotePath) + { + throw new System.NotImplementedException(); + } + + public void MakeDirectory(string remotePath) + { + throw new System.NotImplementedException(); + } + + public void MoveFile(string sourcePath, string movePath) + { + throw new System.NotImplementedException(); + } + + public void RemoveFile(string remotePath) + { + throw new System.NotImplementedException(); + } + + public void SyncFile(string remotePath, string localPath) + { + throw new System.NotImplementedException(); + } + + public void UploadFile(FileStream localFileStream, string remotePath) + { + throw new System.NotImplementedException(); + } + + public void UploadFile(string localPath, string remotePath) + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Object/EGByteSave.cs b/EGFramework/Module/SaveTools/Object/EGByteSave.cs new file mode 100644 index 0000000..a99e3a6 --- /dev/null +++ b/EGFramework/Module/SaveTools/Object/EGByteSave.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; + +namespace EGFramework +{ + /// Pointer max length is 4294967295. + /// Data protocol: + /// [XX XX XX XX](Pointer length) [XX XX XX XX]...[XX XX XX XX](Pointers) [DataBlock 1]...[DataBlock N](Data) + /// 1. The first (four byte) is the length of the data pointers (uint type). + /// 2. The pointer List is the position to the data block. + /// 3. The data block is the data you want to save. + public interface IEGByteObject{ + byte[] GetBytes(); + void SetBytes(byte[] byteData); + } + [Obsolete("This class is not comlpete, please not use it!")] + public class EGByteObjectSave : IEGSave, IEGSaveObject + { + public Encoding StringEncoding { set; get; } = Encoding.ASCII; + private string DefaultPath { set; get; } + private uint PointerLength { get; set; } + private uint[] Pointer { get; set; } + private Dictionary Data { get; set; } + private byte[] _Data; + + public void ReadDataBlock(string path) + { + DefaultPath = path; + try + { + FileStream fileStream = new FileStream(path, FileMode.OpenOrCreate); + byte[] buffer = new byte[fileStream.Length]; + fileStream.Read(buffer, 0, (int)fileStream.Length); + fileStream.Close(); + fileStream.Dispose(); + _Data = buffer; + } + catch (System.Exception e) + { + EG.Print("e:" + e); + throw; + } + } + public void WriteDataBlock(string path) + { + try + { + FileStream fileStream = File.Create(path); + fileStream.Write(_Data, 0, _Data.Length); + fileStream.Close(); + fileStream.Dispose(); + } + catch (System.Exception e) + { + EG.Print("e:" + e); + throw; + } + } + + public void InitSave(string path) + { + ReadDataBlock(path); + } + + /// + /// Set object to the file,the pointer + /// + /// + /// + /// + public void SetObject(string objectKey, TObject obj) + { + if (typeof(TObject).GetInterfaces().Contains(typeof(IEGByteObject))) + { + _Data = ((IEGByteObject)obj).GetBytes(); + } + else + { + throw new Exception("This byte class cannot be serialized! you should implement IRequest first!"); + } + WriteDataBlock(DefaultPath); + } + + + + public void RemoveObject(string objectKey) + { + throw new NotImplementedException(); + } + + public void AddObject(string objectKey, TObject obj) + { + throw new NotImplementedException(); + } + + public void UpdateObject(string objectKey, TObject obj) + { + throw new NotImplementedException(); + } + + public IEnumerable GetKeys() + { + throw new NotImplementedException(); + } + + public bool ContainsKey(string objectKey) + { + throw new NotImplementedException(); + } + + public TObject GetObject(string objectKey) + { + throw new NotImplementedException(); + // if(typeof(TObject).GetInterfaces().Contains(typeof(IEGByteObject))){ + // TObject result = new TObject(); + // ((IEGByteObject)result).SetBytes(_Data); + // return result; + // }else{ + // throw new Exception("This byte class cannot be serialized! you should implement IRequest first!"); + // } + } + + } + + public interface IEGByteInit{ + void Init(byte[] data); + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Object/EGJsonSave.cs b/EGFramework/Module/SaveTools/Object/EGJsonSave.cs new file mode 100644 index 0000000..93cdbcb --- /dev/null +++ b/EGFramework/Module/SaveTools/Object/EGJsonSave.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace EGFramework +{ + public class EGJsonSave : IEGSave,IEGSaveReadOnly,IEGSaveObject + { + public bool IsReadOnly { get; set; } + public Encoding StringEncoding { set; get; } = Encoding.UTF8; + private string DefaultPath { set; get; } + private JObject _SaveObject; + private JObject SaveObject{ + get { + if(_SaveObject == null){ + InitSave(DefaultPath); + } + return _SaveObject; + } + } + + /// + /// Init a new save data file or load an other file with json suffix, if you want to load other save data, please use this function to reload; + /// + public void InitSave(string path) + { + DefaultPath = path; + if(!File.Exists(path)){ + if (!Directory.Exists(Path.GetDirectoryName(DefaultPath))) + { + Directory.CreateDirectory(Path.GetDirectoryName(DefaultPath)); + File.WriteAllText(DefaultPath,"{}"); + }else if(!File.Exists(DefaultPath)){ + File.WriteAllText(DefaultPath,"{}"); + } + } + using (StreamReader reader = File.OpenText(path)) + { + _SaveObject = (JObject)JToken.ReadFrom(new JsonTextReader(reader)); + } + } + + public void InitReadOnly(string data) + { + _SaveObject = JObject.Parse(data); + IsReadOnly = true; + } + + public void InitReadOnly(byte[] data) + { + _SaveObject = JObject.Parse(StringEncoding.GetString(data)); + IsReadOnly = true; + } + + public void SetObject(string objectKey,TObject obj) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any object to file."); + } + if(SaveObject.ContainsKey(objectKey)){ + SaveObject[objectKey] = JToken.FromObject(obj); + }else{ + SaveObject.Add(objectKey,JToken.FromObject(obj)); + } + File.WriteAllText(DefaultPath,JsonConvert.SerializeObject(SaveObject,Formatting.Indented)); + } + + /// + /// Get data from file, if your data is not in file, then throw an exception. + /// + public TObject GetObject(string objectKey) + { + if(!SaveObject.ContainsKey(objectKey)){ + throw new Exception("Key not found!"); + } + TObject data = SaveObject[objectKey].ToObject(); + return data; + } + + public void RemoveObject(string objectKey) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any object to file."); + } + if(SaveObject.ContainsKey(objectKey)){ + SaveObject.Remove(objectKey); + } + File.WriteAllText(DefaultPath,JsonConvert.SerializeObject(SaveObject,Formatting.Indented)); + } + + public void AddObject(string objectKey, TObject obj) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any object to file."); + } + if(!SaveObject.ContainsKey(objectKey)){ + SaveObject.Add(objectKey,JToken.FromObject(obj)); + }else{ + throw new Exception("Key already exists!"); + } + File.WriteAllText(DefaultPath,JsonConvert.SerializeObject(SaveObject,Formatting.Indented)); + } + + public void UpdateObject(string objectKey, TObject obj) + { + if(IsReadOnly){ + throw new Exception("This file is readonly! can't set any object to file."); + } + if(SaveObject.ContainsKey(objectKey)){ + SaveObject[objectKey] = JToken.FromObject(obj); + }else{ + throw new Exception("Key not found!"); + } + File.WriteAllText(DefaultPath,JsonConvert.SerializeObject(SaveObject,Formatting.Indented)); + } + + public IEnumerable GetKeys() + { + List keys = new List(); + foreach(string key in SaveObject.Properties()) + { + keys.Add(key); + } + return keys; + } + + public bool ContainsKey(string objectKey) + { + return SaveObject.ContainsKey(objectKey); + } + + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/Object/EGRedisSave.cs b/EGFramework/Module/SaveTools/Object/EGRedisSave.cs new file mode 100644 index 0000000..bfe618b --- /dev/null +++ b/EGFramework/Module/SaveTools/Object/EGRedisSave.cs @@ -0,0 +1,88 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using StackExchange.Redis; + +namespace EGFramework{ + public class EGRedisSave : IEGSave, IEGSaveObject + { + public string Conn { set; get; } + public bool IsInit { set; get; } + public ConnectionMultiplexer Redis { set; get; } + public IDatabase Database { set; get; } + public ISubscriber Subscriber { set; get; } + + /// + /// connect to redis server + /// + /// such as server1:6379,server2:6379 + public void InitSave(string conn) + { + try + { + Redis = ConnectionMultiplexer.Connect(conn); + IsInit = true; + this.Conn = conn; + Redis = ConnectionMultiplexer.Connect(conn); + Database = Redis.GetDatabase(); + Subscriber = Redis.GetSubscriber(); + } + catch (System.Exception) + { + //EG.Print("e:" + e); + } + } + + public void AddObject(string objectKey, TObject obj) + { + if(!Database.KeyExists(objectKey)){ + Database.SetAdd(objectKey, JsonConvert.SerializeObject(obj)); + }else{ + throw new System.Exception("Key already exists in redis database."); + } + + } + + public void UpdateObject(string objectKey, TObject obj) + { + if(Database.KeyExists(objectKey)){ + Database.SetAdd(objectKey, JsonConvert.SerializeObject(obj)); + }else{ + throw new System.Exception("Key not exists in redis database."); + } + } + + public void RemoveObject(string objectKey) + { + Database.KeyDelete(objectKey); + } + + public void SetObject(string objectKey, TObject obj) + { + Database.SetAdd(objectKey, JsonConvert.SerializeObject(obj)); + } + + public TObject GetObject(string objectKey) + { + try + { + string result = Database.StringGet(objectKey); + TObject resultObj = JsonConvert.DeserializeObject(result); + return resultObj; + } + catch (System.Exception) + { + throw; + } + } + + public bool ContainsKey(string objectKey) + { + return Database.KeyExists(objectKey); + } + + public IEnumerable GetKeys() + { + throw new System.NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/EGFramework/Module/SaveTools/SaveToolsInterface.cs b/EGFramework/Module/SaveTools/SaveToolsInterface.cs new file mode 100644 index 0000000..9868c70 --- /dev/null +++ b/EGFramework/Module/SaveTools/SaveToolsInterface.cs @@ -0,0 +1,151 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.IO; +using System.Linq; +using System.Linq.Expressions; +using System.Threading.Tasks; + +namespace EGFramework +{ + #region SaveInit + + public interface IEGSave + { + void InitSave(string path); + } + public interface IEGSaveReadOnly { + void InitReadOnly(string data); + void InitReadOnly(byte[] data); + } + + public interface IEGSaveAsync { + Task InitSaveFileAsync(string path); + } + public interface IEGSaveReadOnlyAsync { + Task InitReadOnlyAsync(string data); + Task InitReadOnlyAsync(byte[] data); + } + + #endregion + + #region DBConnection + public interface IEGCanGetDBConnection + { + DbConnection GetConnection(); + } + public interface IEGDataBase : IEGCanGetDBConnection + { + void CreateTable(string dataKey); + void CreateTable(string dataKey, Dictionary tableParam); + void DropTable(string dataKey); + } + #endregion + + #region Object + public interface IEGSaveObjectReadOnly + { + TObject GetObject(string objectKey); + IEnumerable GetKeys(); + bool ContainsKey(string objectKey); + } + + public interface IEGSaveObject : IEGSaveObjectReadOnly{ + /// + /// SetObject will add a object if it not exisits, replace the object if it already exists. + /// + /// + /// + /// + void SetObject(string objectKey,TObject obj); + void RemoveObject(string objectKey); + void AddObject(string objectKey,TObject obj); + void UpdateObject(string objectKey,TObject obj); + } + public interface IEGSaveObjectReadOnlyAsync{ + Task GetObjectAsync(string objectKey) where TObject : new(); + } + public interface IEGSaveObjectAsync : IEGSaveObjectReadOnlyAsync{ + Task SetObjectAsync(string objectKey,TObject obj); + } + #endregion + + #region Data + public interface IEGSaveDataReadOnly{ + TData GetData(string dataKey,object id) where TData : new(); + IEnumerable GetAll(string dataKey) where TData : new(); + IEnumerable GetPage(string dataKey,int pageIndex,int pageSize) where TData : new(); + IEnumerable FindData(string dataKey,Expression> expression) where TData : new(); + IEnumerable FindData(string dataKey, string columnName, string keyWords) where TData : new(); + IEnumerable GetKeys(); + bool ContainsKey(string dataKey); + bool ContainsData(string dataKey,object id); + int GetDataCount(string dataKey); + } + + public interface IEGSaveData : IEGSaveDataReadOnly{ + void SetData(string dataKey,TData data,object id); + void AddData(string dataKey,TData data); + void AddData(string dataKey,IEnumerable data); + void AddData(string datakey, Dictionary data); + void AddGroup(string datakey, List> dataGroup); + int RemoveData(string dataKey,object id); + void UpdateData(string dataKey, Dictionary data, object id); + void UpdateData(string dataKey,TData data,object id); + } + + public interface IEGSaveDataReadOnlyAsync{ + Task GetDataAsync(string dataKey,object id) where TData : new(); + Task> GetAllAsync(string dataKey) where TData : new(); + Task> FindDataAsync(string dataKey,Expression> expression) where TData : new(); + } + + public interface IEGSaveDataAsync : IEGSaveDataReadOnlyAsync{ + Task SetDataAsync(string dataKey,TData data,object id); + } + #endregion + + #region File + public interface IEGFileMsg{ + public string FileName { get; } + public bool IsCollection { get; } + /// + /// unit is kb + /// + public long? Size { get; } + public string Uri { get; } + public DateTime? LastModify { get; } + public void Init(string fileName,bool isCollection,string uri,long? size,DateTime? lastmodify); + } + public struct EGFileMsg : IEGFileMsg{ + public string FileName { get; set; } + public bool IsCollection { get; set; } + public long? Size { get; set; } + public string Uri { get; set; } + public DateTime? LastModify { get; set; } + public void Init(string fileName,bool isCollection,string uri,long? size,DateTime? lastmodify){ + this.FileName = fileName; + this.IsCollection = isCollection; + this.Uri = uri; + this.Size = size; + this.LastModify = lastmodify; + } + } + public interface IEGSaveFileReadOnly{ + IEnumerable ListRemoteFilePath(string remotePath); + bool IsRemoteFileExist(string remotePath); + bool IsRemoteDirectoryExist(string remotePath); + void DownloadFile(string remotePath,string localPath); + Stream DownloadFile(string remotePath); + void SyncFile(string remotePath,string localPath); + } + public interface IEGSaveFile:IEGSaveFileReadOnly{ + void UploadFile(FileStream localFileStream,string remotePath); + void UploadFile(string localPath,string remotePath); + void CopyFile(string sourcePath,string copyPath); + void MoveFile(string sourcePath,string movePath); + void RemoveFile(string remotePath); + void MakeDirectory(string remotePath); + } + #endregion +} \ No newline at end of file diff --git a/InstallManual.md b/InstallManual.md new file mode 100644 index 0000000..96fe887 --- /dev/null +++ b/InstallManual.md @@ -0,0 +1,158 @@ +# 靶机程序安装手册 + + + +Version`V0.1`试行版 + +修订记录 + +| 日期 | 版本 | 说明 | 作者 | +| ------------- | ---- | -------- | ------ | +| 2025年12月4日 | V0.1 | 首次编写 | 朱冠臣 | +| | | | | +| | | | | +| | | | | + +## 1. 概述 + +> 本文档为安装手册,用于任何形式的轨道靶机,不限于地轨,空轨,竖轨等等。 +> +> 靶机运行环境为如下 +> +> - 架构为AMD64 +> - 需要预装.net8 运行时 +> - 设备具备ch341或ch340驱动 +> +> 靶机需提前配置好串口编号并测试串口信息输出 + + + +## 2. 安装系统环境 + +1. Ubuntu20.04 +2. openssh-server +3. net-tools +4. git +5. dotnet8 +6. 串口授权 + +默认安装环境已完成 + +### 2.1 安装openssh-server(镜像可能自带) + +```shell +sudo apt-get install openssh-server +``` + +### 2.2 安装net-tools(镜像可能自带) + +```shell +sudo apt-get install net-tools +``` + +### 2.3 安装git + +```shell +sudo apt-get install git +``` + +### 2.4 安装dotnet8 + +参考链接-微软安装手册 https://learn.microsoft.com/zh-cn/dotnet/core/install/linux-ubuntu-decision#register-the-microsoft-package-repository + +```shell +# Get OS version info which adds the $ID and $VERSION_ID variables +source /etc/os-release + +# Download Microsoft signing key and repository +wget https://packages.microsoft.com/config/$ID/$VERSION_ID/packages-microsoft-prod.deb -O packages-microsoft-prod.deb + +# Install Microsoft signing key and repository +sudo dpkg -i packages-microsoft-prod.deb + +# Clean up +rm packages-microsoft-prod.deb + +# Update packages +sudo apt update +``` + +安装dotnet + +```shell +sudo apt-get install dotnet-sdk-8.0 +``` + +### 2.5 串口授权(如果用户名为其它则修改为u) + +```shell +sudo usermod -aG dialout u +``` + +### 2.6 关闭防火墙&允许访问端口 + +检查防火墙状态 + +```shell +sudo ufw status +``` + +如果关闭 + +Status: inactive + +如果开启,关闭防火墙 + +```shell +sudo ufw disable +``` + +或者开启对应端口 + +```shell +sudo ufw allow +``` + + + +## 3. 编译安装软件 + +1. 克隆项目(项目地址以实际地址为准) +2. 打release包 +3. 注册程序为Systemctl 服务 + +### 3.1 克隆项目 + +```shell +git clone http://byserver:3000/TargetMachine/TrackTargetService +``` + +### 3.2 发布release包 + +```shell +dotnet publish --configuration Release +``` + +### 3.3 注册程序为Systemctl 服务 + +```shell +# 切换到工程对应路径SystemCtl下 +cd SystemCtl +# 启动install.sh脚本 +sudo chmod 777 install.sh +./install.sh +``` + +### 3.4 开启或关闭服务 + +```shell +# 开启服务 +sudo systemctl start TrackTarget.service +# 关闭服务 +sudo systemctl stop TrackTarget.service +# 重启服务 +sudo systemctl restart TrackTarget.service +# 查看运行状态 +sudo systemctl status TrackTarget.service +``` + diff --git a/Program.cs b/Program.cs new file mode 100644 index 0000000..bd92a0f --- /dev/null +++ b/Program.cs @@ -0,0 +1,23 @@ +using TargetService.Components; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddRazorComponents() + .AddInteractiveServerComponents(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error", createScopeForErrors: true); +} + +app.UseStaticFiles(); +app.UseAntiforgery(); + +app.MapRazorComponents() + .AddInteractiveServerRenderMode(); + +app.Run(); diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json new file mode 100644 index 0000000..1534944 --- /dev/null +++ b/Properties/launchSettings.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:16619", + "sslPort": 0 + } + }, + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "applicationUrl": "http://localhost:5257", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } + } diff --git a/Script/Architecture/EGBlazorController.cs b/Script/Architecture/EGBlazorController.cs new file mode 100644 index 0000000..44a24a9 --- /dev/null +++ b/Script/Architecture/EGBlazorController.cs @@ -0,0 +1,41 @@ +using System.Timers; + +namespace EGFramework +{ + public class EGBlazorController : IEGBehavior, IEGFramework + { + private EGProtocolSchedule? ProtocolSchedule { set; get; } + private System.Timers.Timer ProcessTimer { set; get; } = new System.Timers.Timer(20); + + public void Init() + { + ProtocolSchedule = this.GetModule(); + this.GetModule().SetDelay(100); + ProcessTimer.Elapsed += this.Process; + ProcessTimer.AutoReset = true; + ProcessTimer.Enabled = true; + } + + public void Start() + { + ProtocolSchedule?.EnabledTool(); + // ProtocolSchedule?.EnabledTool(); + ProtocolSchedule?.EnabledTool(); + ProtocolSchedule?.EnabledTool(); + this.GetModule(); + this.GetModule(); + this.GetModule(); + } + + public void Process(object? source, ElapsedEventArgs e) + { + ProtocolSchedule?.CheckedProcess(); + } + + public void Exit() + { + + } + + } +} \ No newline at end of file diff --git a/Script/Data/DataActionStatus.cs b/Script/Data/DataActionStatus.cs new file mode 100644 index 0000000..dac0006 --- /dev/null +++ b/Script/Data/DataActionStatus.cs @@ -0,0 +1,14 @@ +public class DataActionStatus +{ + /// + /// 0-200 + /// + /// + public int Position { set; get; } + + /// + /// 0-90 + /// + /// + public int Rotate { set; get; } +} \ No newline at end of file diff --git a/Script/Data/DataSetting.cs b/Script/Data/DataSetting.cs new file mode 100644 index 0000000..5862357 --- /dev/null +++ b/Script/Data/DataSetting.cs @@ -0,0 +1,19 @@ +using System.Numerics; + +public class DataSetting +{ + public int DeviceID { set; get; } + public int MoveSpeed { set; get; } + public int MoveLowSpeed { set; get; } + public int RotateSpeed { set; get; } + public int TimeoutStop { set; get; } + public float ColorOffset { set; get; } + public Dictionary ColorMapping { set; get; } = new Dictionary(); +} + +public class DataSerialPortSetting +{ + public string MotorDevicePort { set; get; } + public string ColorSensorDevicePort { set; get; } +} + diff --git a/Script/Extension/ColorConvertExtension.cs b/Script/Extension/ColorConvertExtension.cs new file mode 100644 index 0000000..cbd2298 --- /dev/null +++ b/Script/Extension/ColorConvertExtension.cs @@ -0,0 +1,98 @@ +using System; +using System.Numerics; + +public static class ColorConvertExtension +{ + /// + /// 将RGB颜色转换为HSV颜色空间 + /// + /// RGB颜色向量,X=R, Y=G, Z=B,取值范围0-1 + /// HSV颜色向量,X=H(0-360), Y=S(0-1), Z=V(0-1) + public static Vector3 RGBToHSV(this Vector3 rgb) + { + float r = rgb.X; + float g = rgb.Y; + float b = rgb.Z; + + float max = Math.Max(r, Math.Max(g, b)); + float min = Math.Min(r, Math.Min(g, b)); + float delta = max - min; + + float h = 0f; + float s = 0f; + float v = max; + + // 计算色相 (H) + if (delta > 0f) + { + if (max == r) + { + h = (g - b) / delta; + if (h < 0f) h += 6f; + } + else if (max == g) + { + h = 2f + (b - r) / delta; + } + else // max == b + { + h = 4f + (r - g) / delta; + } + + h *= 60f; // 转换为度数 + } + + // 计算饱和度 (S) + if (max > 0f) + { + s = delta / max; + } + + return new Vector3(h, s, v); + } + + /// + /// 将HSV颜色转换为RGB颜色空间 + /// + /// HSV颜色向量,X=H(0-360), Y=S(0-1), Z=V(0-1) + /// RGB颜色向量,X=R, Y=G, Z=B,取值范围0-1 + public static Vector3 HSVToRGB(this Vector3 hsv) + { + float h = hsv.X; + float s = hsv.Y; + float v = hsv.Z; + + // 处理色相超出范围的情况 + h = h % 360f; + if (h < 0f) h += 360f; + + // 限制饱和度和明度在0-1范围内 + s = Math.Clamp(s, 0f, 1f); + v = Math.Clamp(v, 0f, 1f); + + // 如果饱和度为0,直接返回灰度值 + if (s <= 0f) + { + return new Vector3(v, v, v); + } + + // 将色相转换为0-6的范围 + float hNormalized = h / 60f; + int sector = (int)hNormalized; + float fractional = hNormalized - sector; + + float p = v * (1f - s); + float q = v * (1f - s * fractional); + float t = v * (1f - s * (1f - fractional)); + + return sector switch + { + 0 => new Vector3(v, t, p), + 1 => new Vector3(q, v, p), + 2 => new Vector3(p, v, t), + 3 => new Vector3(p, q, v), + 4 => new Vector3(t, p, v), + _ => new Vector3(v, p, q) // 5 或 其他情况 + }; + } +} \ No newline at end of file diff --git a/Script/Interface/IEGBehavior.cs b/Script/Interface/IEGBehavior.cs new file mode 100644 index 0000000..a668285 --- /dev/null +++ b/Script/Interface/IEGBehavior.cs @@ -0,0 +1,12 @@ +using System.Timers; + +namespace EGFramework +{ + public interface IEGBehavior + { + public void Init(); + public void Start(); + public void Process(object source, ElapsedEventArgs e); + public void Exit(); + } +} diff --git a/Script/Model/ModelActionStatus.cs b/Script/Model/ModelActionStatus.cs new file mode 100644 index 0000000..a5a963f --- /dev/null +++ b/Script/Model/ModelActionStatus.cs @@ -0,0 +1,41 @@ +using System.Data; +using EGFramework; + + +public class ModelActionStatus : EGModule, IEGFramework +{ + public DataActionStatus Status { set; get; } + public override void Init() + { + if (!this.EGSave().DefaultJsonSave.ContainsKey("DataActionStatus")) + { + DataActionStatus newStatus = new DataActionStatus(); + this.EGSave().DefaultJsonSave.SetObject("DataActionStatus", newStatus); + Status = newStatus; + // this.EGRegisterObject(newSetting); + } + else + { + DataActionStatus settings = this.EGSave().DefaultJsonSave.GetObject("DataActionStatus"); + this.EGRegisterObject(settings); + Status = settings; + } + } + + public void Save(DataActionStatus status) + { + this.EGSave().DefaultJsonSave.SetObject("DataActionStatus", status); + } + + public void SetPosition(int position) + { + this.Status.Position = position; + this.Save(Status); + } + + public void SetRotation(int rotation) + { + this.Status.Rotate = rotation; + this.Save(Status); + } +} diff --git a/Script/Model/ModelControlServer.cs b/Script/Model/ModelControlServer.cs new file mode 100644 index 0000000..5905a9d --- /dev/null +++ b/Script/Model/ModelControlServer.cs @@ -0,0 +1,51 @@ +using EGFramework; + +public class ModelControlServer : EGModule, IEGFramework +{ + public const int TCPListenPort = 6557; + public const int UDPListenPort = 6558; + + public override void Init() + { + this.EGTCPServerListen(TCPListenPort); + this.EGUDPListen(UDPListenPort); + this.EGOnMessage(); + this.EGRegisterMessageEvent(OnResponseControl); + // throw new NotImplementedException(); + } + + public void OnResponseControl(ResponseControl data, string sender, ProtocolType type) + { + if (type == ProtocolType.TCPServer || type == ProtocolType.UDP) + { + switch (data.FunctionCode) + { + case (int)TypeControlFunction.StartShooting: + this.GetModule().StartTarget(); + break; + case (int)TypeControlFunction.StopShooting: + this.GetModule().RevertTarget(); + break; + case (int)TypeControlFunction.Status: + this.EGSendMessage(new RequestControlStatus() + { + TargetID = this.GetModule().Setting.DeviceID, + FunctionCode = data.FunctionCode, + IsStart = this.GetModule().CurrentRotation > 0, + Position = this.GetModule().CurrentPosition + }, sender, type); + return; + case (int)TypeControlFunction.TrackMove: + this.GetModule().MoveToPosition(data.Position); + break; + } + } + this.EGSendMessage(new RequestControl() + { + TargetID = this.GetModule().Setting.DeviceID, + FunctionCode = data.FunctionCode, + ExecuteSuccess = true, + ErrorCode = 0 + }, sender, type); + } +} \ No newline at end of file diff --git a/Script/Model/ModelParamSetting.cs b/Script/Model/ModelParamSetting.cs new file mode 100644 index 0000000..6b95788 --- /dev/null +++ b/Script/Model/ModelParamSetting.cs @@ -0,0 +1,77 @@ +using System.Numerics; +using EGFramework; + +public class ModelParamSetting : EGModule, IEGFramework +{ + public DataSetting Setting { set; get; } + public DataSerialPortSetting SerialPortSetting { set; get; } + public override void Init() + { + if (!this.EGSave().DefaultJsonSave.ContainsKey("DataSetting")) + { + DataSetting newSetting = CreateDefault(); + this.EGSave().DefaultJsonSave.SetObject("DataSetting", newSetting); + Setting = newSetting; + // this.EGRegisterObject(newSetting); + } + else + { + DataSetting settings = this.EGSave().DefaultJsonSave.GetObject("DataSetting"); + this.EGRegisterObject(settings); + Setting = settings; + } + + if (!this.EGSave().DefaultJsonSave.ContainsKey("DataSerialPortSetting")) + { + DataSerialPortSetting newPortSetting = CreateSerialPortSetting(); + this.EGSave().DefaultJsonSave.SetObject("DataSerialPortSetting", newPortSetting); + SerialPortSetting = newPortSetting; + // this.EGRegisterObject(newSetting); + } + else + { + DataSerialPortSetting portSettings = this.EGSave().DefaultJsonSave.GetObject("DataSerialPortSetting"); + this.EGRegisterObject(portSettings); + SerialPortSetting = portSettings; + } + } + + public DataSetting CreateDefault() + { + DataSetting newSetting = new DataSetting() + { + DeviceID = 0, + MoveSpeed = 200, + MoveLowSpeed = 100, + RotateSpeed = 2000, + TimeoutStop = 5000, + ColorOffset = 5, + ColorMapping = new Dictionary() + }; + newSetting.ColorMapping.Add(0, new Vector3(107, 100, 100)); + newSetting.ColorMapping.Add(7, new Vector3(83, 100, 100)); + newSetting.ColorMapping.Add(10, new Vector3(194, 100, 100)); + newSetting.ColorMapping.Add(15, new Vector3(334, 100, 100)); + newSetting.ColorMapping.Add(25, new Vector3(349, 100, 100)); + return newSetting; + } + + public DataSerialPortSetting CreateSerialPortSetting() + { + return new DataSerialPortSetting(); + } + + public void Save(DataSetting editSetting) + { + this.EGSave().DefaultJsonSave.SetObject("DataSetting", editSetting); + this.Setting = editSetting; + } + + public void Save(DataSetting editSetting, DataSerialPortSetting portSetting) + { + this.EGSave().DefaultJsonSave.SetObject("DataSetting", editSetting); + this.EGSave().DefaultJsonSave.SetObject("DataSerialPortSetting", portSetting); + this.Setting = editSetting; + this.SerialPortSetting = portSetting; + } +} diff --git a/Script/Model/ModelSerialServer.cs b/Script/Model/ModelSerialServer.cs new file mode 100644 index 0000000..13c75f9 --- /dev/null +++ b/Script/Model/ModelSerialServer.cs @@ -0,0 +1,56 @@ +using EGFramework; + +public class ModelSerialServer : EGModule,IEGFramework +{ + public int SerialServerPort { set; get; } = 6550; + public int SerialServerPortBackUp { set; get; } = 6551; + public List SerialNames { set; get; } = new List(); + public override void Init() + { + this.EGTCPServerListen(SerialServerPort); + this.EGOnMessage(); + this.EGOnMessage(); + this.EGRegisterMessageEvent(OnReceivedFromTCP); + this.EGRegisterMessageEvent(OnReceivedFromSerialPort); + EG.Print("串口转TCP服务已开启!"); + // throw new NotImplementedException(); + } + + public void SendToSerial(ResponseSerialServer res) + { + if (!SerialNames.Contains(res.SerialPort) && res.BaudRate != 0) + { + this.EGSerialPort().Open(res.SerialPort, res.BaudRate); + SerialNames.Add(res.SerialPort); + } + EG.Print("Send Serial : " + res.StringData); + this.EGSendMessage(new RequestSerialPort(res), res.SerialPort, ProtocolType.SerialPort); + } + + public void SendToClient(byte[] data,string serialPortName) + { + if (this.EGTCPServer().LinkedIPs.ContainsKey(6550)) + { + EG.Print("Send Client : " + data.ToStringByHex()); + foreach (string sender in this.EGTCPServer().LinkedIPs[6550]) + { + this.EGSendMessage(new RequestSerialRevert(data,serialPortName), sender, ProtocolType.TCPServer); + } + } + } + + public void OnReceivedFromTCP(ResponseSerialServer tcpData,string sender,ProtocolType protocol) + { + if(protocol == ProtocolType.TCPServer && this.EGTCPServer().LinkedIPs[6550].Contains(sender)) + { + SendToSerial(tcpData); + } + } + public void OnReceivedFromSerialPort(ResponseSerialPort serialData,string sender,ProtocolType protocol) + { + if(protocol == ProtocolType.SerialPort) + { + SendToClient(serialData.Data,sender); + } + } +} \ No newline at end of file diff --git a/Script/Model/ModelSerialTest.cs b/Script/Model/ModelSerialTest.cs new file mode 100644 index 0000000..ef86654 --- /dev/null +++ b/Script/Model/ModelSerialTest.cs @@ -0,0 +1,108 @@ +using EGFramework; + +/// +/// 检测串口对应的空轨驱动器,或是颜色传感器,检测后记录到串口列表中 +/// +public class ModelSerialTest : EGModule,IEGFramework +{ + public const int BAUD_RATE_ENGINE = 9600; + public const int BAUD_RATE_COLOR = 38400; + + public const string PORT_LINUX_0 = "/dev/ttyUSB0"; + public const string PORT_LINUX_1 = "/dev/ttyUSB1"; + + public const string PORT_WIN_0 = "COM2"; + public const string PORT_WIN_1 = "COM5"; + + public bool IsLinux = true; + + public string TestPort0 { set; get; } = PORT_LINUX_0; + public string TestPort1 { set; get; } = PORT_LINUX_1; + + public string ColorSerialPort { set; get; } = ""; + public string EngineSerialPort { set; get; } = ""; + + public IUnRegister ColorSensorInitEvent { set; get; } + + public IUnRegister EngineInitEvent{ set; get; } + + public override void Init() + { + ColorSensorInitEvent = this.EGRegisterMessageEvent(OnColorSensorInit); + this.EGOnMessage(); + this.EGOnMessage(); + if (!IsLinux) + { + TestPort0 = PORT_WIN_0; + TestPort1 = PORT_WIN_1; + } + InitSerialPort(); + // throw new NotImplementedException(); + } + + public async void InitSerialPort() + { + this.EGSerialPort().Open(TestPort0, 38400); + this.EGSendMessage(new RequestColorSingleRead(), TestPort0, ProtocolType.SerialPort); + await Task.Delay(2000); + if(ColorSerialPort == "") + { + this.EGSerialPort().Close(TestPort0); + this.EGSerialPort().Open(TestPort1, 38400); + this.EGSendMessage(new RequestColorSingleRead(), TestPort1, ProtocolType.SerialPort); + await Task.Delay(2000); + if(ColorSerialPort == "") + { + this.EGSerialPort().Close(TestPort1); + EG.Print("Color sensor init failed,Time out."); + } + else + { + EG.Print("_"+this.EGSerialPort().SerialPortDevices.ContainsKey(TestPort0)); + this.EGSerialPort().Open(TestPort0, 9600); + this.EGSendMessage(new RequestTrackReadEnabled(), TestPort0, ProtocolType.SerialPort); + EngineInitEvent = this.EGRegisterMessageEvent(OnEngineInit); + await Task.Delay(2000); + if(EngineSerialPort == "") + { + this.EGSerialPort().Close(TestPort0); + EG.Print("Engine init failed USB0,Time out."); + } + } + } + else + { + this.EGSerialPort().Open(TestPort1, 9600); + this.EGSendMessage(new RequestTrackReadEnabled(), TestPort1, ProtocolType.SerialPort); + EngineInitEvent = this.EGRegisterMessageEvent(OnEngineInit); + await Task.Delay(2000); + if(EngineSerialPort == "") + { + this.EGSerialPort().Close(TestPort1); + EG.Print("Engine init failed USB1,Time out."); + } + } + } + + public void OnColorSensorInit(ResponseColorSensor res, string sender, ProtocolType type) + { + if (type == ProtocolType.SerialPort) + { + ColorSensorInitEvent.UnRegister(); + this.ColorSerialPort = sender; + EG.Print("Color sensor init success!"); + } + } + + public void OnEngineInit(ResponseTrack res,string sender,ProtocolType type) + { + if (type == ProtocolType.SerialPort) + { + EngineInitEvent.UnRegister(); + this.EngineSerialPort = sender; + EG.Print("Engine sensor init success!"); + this.GetModule().ResetTrack(); + } + } + +} \ No newline at end of file diff --git a/Script/Model/ModelTrackControl.cs b/Script/Model/ModelTrackControl.cs new file mode 100644 index 0000000..bb3942c --- /dev/null +++ b/Script/Model/ModelTrackControl.cs @@ -0,0 +1,317 @@ +using System.Numerics; +using EGFramework; +using Tmds.Linux; + +public class ModelTrackControl : EGModule, IEGFramework +{ + public const int SlaveID = 3; + public bool IsInitTrack { set; get; } = false; + + public bool IsScanning { set; get; } = false; + public string PortName { set; get; } = ""; + + public Vector3 CurrentColorRGB { set; get; } = new Vector3(); + + public Vector3 CurrentColorHSV { set; get; } = new Vector3(); + + private IUnRegister ColorSensorEvent { set; get; } + + public int CurrentPosition { set; get; } = 0; + public int TargetPosition { set; get; } = 0; + + public StatusTrack MoveStatus { set; get; } = StatusTrack.Stop; + + private ushort LowSpeed { set; get; } + private ushort Speed { set; get; } + + public int CurrentRotation { set; get; } = 0; + public override void Init() + { + // List portList = this.EGSerialPort().RefreshSerialPort(); + CurrentPosition = this.GetModule().Status.Position; + CurrentRotation = this.GetModule().Status.Rotate; + LowSpeed = (ushort)this.GetModule().Setting.MoveLowSpeed; + Speed = (ushort)this.GetModule().Setting.MoveSpeed; + ResetTarget(); + } + + public void EnableTrack() + { + this.EGSendMessage(new RequestTrackEnabled(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + IsInitTrack = true; + } + + public void StartTrack() + { + if (!IsInitTrack) + { + EnableTrack(); + } + this.EGSendMessage(new RequestTrackSpeed(Speed), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + this.EGSendMessage(new RequestTrackMove(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + } + + public void StartSlowTrack() + { + if (!IsInitTrack) + { + EnableTrack(); + } + this.EGSendMessage(new RequestTrackSpeed(LowSpeed), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + this.EGSendMessage(new RequestTrackMove(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + } + + public void BackTrack() + { + if (!IsInitTrack) + { + EnableTrack(); + } + this.EGSendMessage(new RequestTrackSpeed(Speed), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + this.EGSendMessage(new RequestTrackBack(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + } + + public void BackSlowTrack() + { + if (!IsInitTrack) + { + EnableTrack(); + } + this.EGSendMessage(new RequestTrackSpeed(LowSpeed), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + this.EGSendMessage(new RequestTrackBack(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + } + + public void StopTrack() + { + if (!IsInitTrack) + { + EnableTrack(); + } + this.EGSendMessage(new RequestTrackSpeed(0), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + this.EGSendMessage(new RequestTrackStop(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + } + + public void BreakTrack() + { + if (!IsInitTrack) + { + EnableTrack(); + } + this.EGSendMessage(new RequestTrackBreak(), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + this.EGSendMessage(new RequestTrackSpeed(0), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + } + + public void MoveToPosition(int position) + { + if (this.CurrentPosition == position) + { + EG.Print("Position is same.Nothing need to move!"); + return; + } + if (this.MoveStatus != StatusTrack.Stop) + { + EG.Print("Machine should be stop first!"); + return; + } + + if (!this.GetModule().Setting.ColorMapping.ContainsKey(position)) + { + EG.Print("Position invalid!Please add color first!"); + return; + } + + TargetPosition = position; + + if (this.CurrentPosition > position) + { + this.StartScanColor(); + this.StartTrack(); + this.MoveStatus = StatusTrack.Start; + } + + if (this.CurrentPosition < position) + { + this.StartScanColor(); + this.BackTrack(); + this.MoveStatus = StatusTrack.Back; + } + } + + public void StartTarget() + { + if (CurrentRotation != 90) + { + this.EGSendMessage(new RequestTargetControl(2000,500,false), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + CurrentRotation = 90; + this.GetModule().SetRotation(CurrentRotation); + } + } + + public void RevertTarget() + { + if (CurrentRotation != 0) + { + this.EGSendMessage(new RequestTargetControl(2000, 0, false), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + CurrentRotation = 0; + this.GetModule().SetRotation(CurrentRotation); + } + } + public void ResetTarget() + { + if (CurrentRotation != 0) + { + this.EGSendMessage(new RequestTargetControl(2000, 500, true), this.GetModule().EngineSerialPort, ProtocolType.SerialPort); + CurrentRotation = 0; + this.GetModule().SetRotation(CurrentRotation); + } + } + + public void StartScanColor() + { + this.EGSendMessage(new RequestColorStartScan(), this.GetModule().ColorSerialPort, ProtocolType.SerialPort); + ColorSensorEvent = this.EGRegisterMessageEvent(OnColorScanned); + IsScanning = true; + } + + public void StopScanColor() + { + this.EGSendMessage(new RequestColorStopScan(), this.GetModule().ColorSerialPort, ProtocolType.SerialPort); + //null exception + ColorSensorEvent.UnRegister(); + IsScanning = false; + } + + public void ResetTrack() + { + this.CurrentPosition = 0; + this.StartScanColor(); + this.StartTrack(); + this.MoveStatus = StatusTrack.Reset; + } + + private float HueCache { set; get; } + public void OnColorScanned(ResponseColorSensor color, string sender, ProtocolType type) + { + if (type == ProtocolType.SerialPort) + { + this.CurrentColorHSV = color.ColorHSV; + this.CurrentColorRGB = color.ColorRGB; + if (this.MoveStatus == StatusTrack.Start) + { + float currentHue = CurrentColorHSV.X; + if (MathF.Abs(HueCache - currentHue) > this.GetModule().Setting.ColorOffset) + { + HueCache = currentHue; + return; + } + float targetHue = this.GetModule().Setting.ColorMapping[TargetPosition].X; + bool compareResult = this.CompareHue(currentHue, targetHue); + if (compareResult) + { + this.StartSlowTrack(); + this.MoveStatus = StatusTrack.StartSlow; + BroadCastMoveStatus(); + } + } + + if (this.MoveStatus == StatusTrack.Back) + { + float currentHue = CurrentColorHSV.X; + if (MathF.Abs(HueCache - currentHue) > this.GetModule().Setting.ColorOffset) + { + HueCache = currentHue; + return; + } + float targetHue = this.GetModule().Setting.ColorMapping[TargetPosition].X; + bool compareResult = this.CompareHue(currentHue, targetHue); + if (compareResult) + { + this.BackSlowTrack(); + this.MoveStatus = StatusTrack.BackSlow; + BroadCastMoveStatus(); + } + } + + if (this.MoveStatus == StatusTrack.BackSlow || this.MoveStatus == StatusTrack.StartSlow) + { + float currentHue = CurrentColorHSV.X; + float targetHue = 4.0f; + bool compareResult = this.CompareHue(currentHue, targetHue); + if (compareResult) + { + this.BreakTrack(); + this.MoveStatus = StatusTrack.Stop; + this.StopScanColor(); + this.CurrentPosition = this.TargetPosition; + BroadCastMoveStatus(); + } + } + + if(this.MoveStatus == StatusTrack.Reset) + { + float currentHue = CurrentColorHSV.X; + float targetHue = this.GetModule().Setting.ColorMapping[0].X; + bool compareResult = this.CompareHue(currentHue, targetHue); + if (compareResult) + { + this.BreakTrack(); + this.MoveStatus = StatusTrack.Stop; + this.StopScanColor(); + this.CurrentPosition = 0; + BroadCastMoveStatus(); + } + } + + HueCache = CurrentColorHSV.X; + } + } + + public bool CompareHue(float hue1,float hue2) + { + if (hue1 > hue2 - this.GetModule().Setting.ColorOffset && + hue1 < hue2 + this.GetModule().Setting.ColorOffset) + { + return true; + } + else + { + return false; + } + } + + public Vector3 GetColor() + { + return CurrentColorRGB; + } + public Vector3 GetColorHSV() + { + return CurrentColorHSV; + } + + public void BroadCastMoveStatus() + { + if (this.EGTCPServer().LinkedIPs.ContainsKey(6557)) + { + foreach (string sender in this.EGTCPServer().LinkedIPs[6557]) + { + this.EGSendMessage(new RequestControlMoveStatus() + { + TargetID = this.GetModule().Setting.DeviceID, + FunctionCode = 301, + Position = this.GetModule().CurrentPosition, + Status = MoveStatus + }, sender, ProtocolType.TCPServer); + } + } + } +} + +public enum StatusTrack +{ + Stop = 0x00, + Start = 0x01, + StartSlow = 0x02, + Back = 0x03, + BackSlow = 0x04, + Reset = 0x05 +} \ No newline at end of file diff --git a/Script/Protocol/RequestSerialServer.cs b/Script/Protocol/RequestSerialServer.cs new file mode 100644 index 0000000..e94a8eb --- /dev/null +++ b/Script/Protocol/RequestSerialServer.cs @@ -0,0 +1,113 @@ +using EGFramework; +using Newtonsoft.Json; + +public class RequestSerialPort : IRequest +{ + public string SerialPort { set; get; } + public string StringData { set; get; } + public byte[] Data { set; get; } + public RequestSerialPort(byte[] data) + { + this.Data = data; + } + public RequestSerialPort(ResponseSerialServer data) + { + this.Data = data.GetBytes(); + } + public string ToProtocolData() + { + return null; + } + + public byte[] ToProtocolByteData() + { + return Data; + } +} + +public class ResponseSerialPort : IResponse +{ + public byte[] Data { set; get; } + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + this.Data = protocolBytes; + if(Data == null) + { + return false; + } + return true; + } + catch (System.Exception) + { + return false; + // throw; + } + } +} + +public class RequestSerialRevert : IRequest, IEGFramework +{ + public string SerialPort { set; get; } + public bool IsByte { set; get; } + public string StringData { set; get; } + + public RequestSerialRevert(byte[] data,string serialPort) + { + this.StringData = data.ToStringByHex(); + this.IsByte = true; + this.SerialPort = serialPort; + } + + public byte[] ToProtocolByteData() + { + return null; + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this); + } +} + +public class ResponseSerialServer : IResponse, IEGFramework +{ + public string SerialPort { set; get; } + public bool IsByte { set; get; } + public string StringData { set; get; } + public int BaudRate { set; get; } + private byte[] Data { set; get; } + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + if (!protocolData.StartsWith('{') && !protocolData.StartsWith('[')) + { + return false; + } + ResponseSerialServer data = JsonConvert.DeserializeObject(protocolData); + if (data != null && data.SerialPort != null && data.SerialPort != "") + { + this.SerialPort = data.SerialPort; + this.IsByte = data.IsByte; + this.StringData = data.StringData; + this.BaudRate = data.BaudRate; + this.Data = data.StringData.ToHexByString(); + return true; + } + else + { + return false; + } + } + catch (System.Exception) + { + return false; + } + } + public byte[] GetBytes() + { + return Data; + } +} \ No newline at end of file diff --git a/Script/Protocol/RequestTargetEngine.cs b/Script/Protocol/RequestTargetEngine.cs new file mode 100644 index 0000000..499599f --- /dev/null +++ b/Script/Protocol/RequestTargetEngine.cs @@ -0,0 +1,31 @@ +using EGFramework; + +public class RequestTargetControl : ModbusRTU_WriteMultiHoldingRegister, IEGFramework +{ + public bool IsBack { set; get; } = false; + public ushort Speed { set; get; } = 2000; + public ushort Position { set; get; } = 0; + + public RequestTargetControl(ushort speed,ushort position,bool isBack) + { + this.DeviceAddress = 0x01; + this.RegisterStartAddress = 68; + this.Speed = speed; + this.Position = position; + this.IsBack = isBack; + this.Values = GetValues(); + EG.Print("[TargetSend]" + this.ToProtocolByteData().ToStringByHex()); + } + public ushort[] GetValues() + { + ushort[] resultValues = { Speed,0x00,0x00,Position}; + if (IsBack) + { + ushort max = 65535; + resultValues[2] = max; + resultValues[3] = (ushort)(max - Position); + } + + return resultValues; + } +} diff --git a/Script/Protocol/RequestTrackEngine.cs b/Script/Protocol/RequestTrackEngine.cs new file mode 100644 index 0000000..fde01cb --- /dev/null +++ b/Script/Protocol/RequestTrackEngine.cs @@ -0,0 +1,103 @@ +using EGFramework; + +public class RequestTrackEnabled : ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackEnabled() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0xB6; + this.Value = 0x01; + } +} +public class RequestTrackDisabled : ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackDisabled() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0xB6; + this.Value = 0x02; + } +} + +public class RequestTrackSpeed: ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackSpeed(ushort speed) + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0x56; + this.Value = speed; + } +} + +public class RequestTrackStop : ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackStop() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0x66; + this.Value = 0x00; + } +} + +public class RequestTrackMove : ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackMove() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0x66; + this.Value = 0x01; + } +} +public class RequestTrackBack : ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackBack() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0x66; + this.Value = 0x02; + } +} + + +public class RequestTrackBreak : ModbusRTU_WriteSingleHoldingRegister, IEGFramework +{ + public RequestTrackBreak() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0x66; + this.Value = 0x03; + } +} + +public class RequestTrackReadEnabled: ModbusRTU_ReadHoldingRegisters, IEGFramework +{ + public RequestTrackReadEnabled() + { + this.DeviceAddress = 0x03; + this.RegisterAddress = 0xB6; + this.ReadCount = 0x01; + } +} + + +public class ResponseTrack : ModbusRTU_Response, IEGFramework +{ + public uint ResValue { set; get; } + public override bool TrySetData(string protocolData, byte[] protocolBytes) + { + bool result = base.TrySetData(protocolData, protocolBytes); + if (!result) + { + return false; + } + if (this.HoldingRegister != null && this.HoldingRegister.Length >= 1) + { + ResValue = HoldingRegister[0]; + } + else + { + return false; + } + return result; + } +} \ No newline at end of file diff --git a/Script/Protocol/ResponseColorSensor.cs b/Script/Protocol/ResponseColorSensor.cs new file mode 100644 index 0000000..6467bee --- /dev/null +++ b/Script/Protocol/ResponseColorSensor.cs @@ -0,0 +1,59 @@ +using System.Numerics; +using EGFramework; + + +public class RequestColorStartScan : ModbusRTU_WriteSingleHoldingRegister +{ + public RequestColorStartScan() + { + this.DeviceAddress = 0x01; + this.RegisterAddress = 0x3007; + this.Value = 0x02; + } +} +public class RequestColorStopScan : ModbusRTU_WriteSingleHoldingRegister +{ + public RequestColorStopScan() + { + this.DeviceAddress = 0x01; + this.RegisterAddress = 0x3007; + this.Value = 0x01; + } +} + +public class RequestColorSingleRead : ModbusRTU_ReadHoldingRegisters +{ + public RequestColorSingleRead() + { + this.DeviceAddress = 0x01; + this.RegisterAddress = 0x00; + this.ReadCount = 3; + } +} + + +public class ResponseColorSensor : ModbusRTU_Response +{ + public Vector3 ColorRGB { set; get; } + + public Vector3 ColorHSV { set; get; } + + public override bool TrySetData(string protocolData, byte[] protocolBytes) + { + bool result = base.TrySetData(protocolData, protocolBytes); + if (!result) + { + return false; + } + if (this.HoldingRegister != null && this.HoldingRegister.Length >= 3) + { + ColorRGB = new Vector3(HoldingRegister[0] / 1000f, HoldingRegister[1] / 1000f, HoldingRegister[2] / 1000f); + ColorHSV = ColorRGB.RGBToHSV(); + } + else + { + return false; + } + return result; + } +} \ No newline at end of file diff --git a/Script/Protocol/ResponseControl.cs b/Script/Protocol/ResponseControl.cs new file mode 100644 index 0000000..f9fdba0 --- /dev/null +++ b/Script/Protocol/ResponseControl.cs @@ -0,0 +1,105 @@ +using EGFramework; +using Newtonsoft.Json; + +//通讯协议类定义 +public class ResponseControl : IResponse +{ + public int FunctionCode { set; get; } + public int Position { set; get; } + public bool TrySetData(string protocolData, byte[] protocolBytes) + { + try + { + if (!protocolData.StartsWith('{') && !protocolData.StartsWith('[')) + { + return false; + } + ResponseControl data = JsonConvert.DeserializeObject(protocolData); + if (data != null && data.FunctionCode != 0) + { + this.FunctionCode = data.FunctionCode; + this.Position = data.Position; + return true; + } + else + { + return false; + } + } + catch (System.Exception) + { + return false; + // throw; + } + } +} + +//数据返回 +public class RequestControl : IRequest +{ + public int TargetID { set; get; } + public int FunctionCode { set; get; } + public bool ExecuteSuccess { set; get; } + public int ErrorCode { set; get; } + public byte[] ToProtocolByteData() + { + // throw new NotImplementedException(); + return null; + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this); + } +} + +//状态查询 +public class RequestControlStatus : IRequest +{ + public int TargetID { set; get; } + public int FunctionCode { set; get; } + public bool IsStart { set; get; } + public int Position { set; get; } + public byte[] ToProtocolByteData() + { + // throw new NotImplementedException(); + return null; + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this); + } +} + +//移动状态反馈 +public class RequestControlMoveStatus : IRequest +{ + public int TargetID { set; get; } + public int FunctionCode { set; get; } + public int Position { set; get; } + public StatusTrack Status { set; get; } + + public byte[] ToProtocolByteData() + { + return null; + } + + public string ToProtocolData() + { + return JsonConvert.SerializeObject(this); + } +} + +public enum TypeControlFunction +{ + StartShooting = 201, + StopShooting = 200, + Status = 100, + TargetMessage = 300, + TrackMessage = 301, + OtherMessage = 302, + TrackMove = 401, + TrackStop = 400, + TrackError = 404 +} \ No newline at end of file diff --git a/SystemCtl/TrackTarget.service b/SystemCtl/TrackTarget.service new file mode 100644 index 0000000..d64c150 --- /dev/null +++ b/SystemCtl/TrackTarget.service @@ -0,0 +1,14 @@ +[Unit] +Description=TrackTargetService +After=network.target + +[Service] +ExecStart=/home/u/TrackTargetService/bin/Release/net8.0/publish/TrackTargetService +WorkingDirectory=/home/u/TrackTargetService +User=u +Group=dialout +Restart=always +Type=simple + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/SystemCtl/install.sh b/SystemCtl/install.sh new file mode 100644 index 0000000..1f1a355 --- /dev/null +++ b/SystemCtl/install.sh @@ -0,0 +1,51 @@ +#!/bin/bash + +# 服务文件名 +SERVICE_FILE="TrackTarget.service" + +# 检查当前目录下是否存在服务文件 +if [ ! -f "$SERVICE_FILE" ]; then + echo "错误: 在当前目录下未找到 $SERVICE_FILE 文件" + exit 1 +fi + +# 检查systemctl是否可用 +if ! command -v systemctl &> /dev/null; then + echo "错误: systemctl 命令未找到,请确保系统使用systemd" + exit 1 +fi + +# 复制服务文件到系统目录 +echo "正在复制 $SERVICE_FILE 到 /etc/systemd/system/" +sudo cp "$SERVICE_FILE" /etc/systemd/system/ + +# 检查复制是否成功 +if [ $? -ne 0 ]; then + echo "错误: 复制服务文件失败" + exit 1 +fi + +# 重新加载systemd配置 +echo "重新加载systemd配置" +sudo systemctl daemon-reload + +# 启用服务开机启动 +echo "启用服务开机启动" +sudo systemctl enable "$SERVICE_FILE" + +# 检查启用是否成功 +if [ $? -ne 0 ]; then + echo "错误: 启用开机启动失败" + exit 1 +fi + +# 显示服务状态 +echo "服务状态:" +sudo systemctl status "$SERVICE_FILE" + +echo "操作完成!$SERVICE_FILE 已安装并设置为开机启动" +echo "你可以使用以下命令管理服务:" +echo "启动服务: sudo systemctl start $SERVICE_FILE" +echo "停止服务: sudo systemctl stop $SERVICE_FILE" +echo "重启服务: sudo systemctl restart $SERVICE_FILE" +echo "查看状态: sudo systemctl status $SERVICE_FILE" \ No newline at end of file diff --git a/TargetService.csproj b/TargetService.csproj new file mode 100644 index 0000000..db82d93 --- /dev/null +++ b/TargetService.csproj @@ -0,0 +1,26 @@ + + + + net8.0 + enable + enable + + + + + + + + + + + + + + + + + + + + diff --git a/TargetService.sln b/TargetService.sln new file mode 100644 index 0000000..f7931e2 --- /dev/null +++ b/TargetService.sln @@ -0,0 +1,24 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.5.2.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TargetService", "TargetService.csproj", "{78C10421-FFB8-A719-CD69-10EB6D9087BF}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {78C10421-FFB8-A719-CD69-10EB6D9087BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78C10421-FFB8-A719-CD69-10EB6D9087BF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78C10421-FFB8-A719-CD69-10EB6D9087BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78C10421-FFB8-A719-CD69-10EB6D9087BF}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4C675282-8DA8-4A2B-BCFE-1C2914B1E8F5} + EndGlobalSection +EndGlobal diff --git a/appsettings.Development.json b/appsettings.Development.json new file mode 100644 index 0000000..0c208ae --- /dev/null +++ b/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/appsettings.json b/appsettings.json new file mode 100644 index 0000000..10f68b8 --- /dev/null +++ b/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/wwwroot/app.css b/wwwroot/app.css new file mode 100644 index 0000000..2bd9b78 --- /dev/null +++ b/wwwroot/app.css @@ -0,0 +1,51 @@ +html, body { + font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; +} + +a, .btn-link { + color: #006bb7; +} + +.btn-primary { + color: #fff; + background-color: #1b6ec2; + border-color: #1861ac; +} + +.btn:focus, .btn:active:focus, .btn-link.nav-link:focus, .form-control:focus, .form-check-input:focus { + box-shadow: 0 0 0 0.1rem white, 0 0 0 0.25rem #258cfb; +} + +.content { + padding-top: 1.1rem; +} + +h1:focus { + outline: none; +} + +.valid.modified:not([type=checkbox]) { + outline: 1px solid #26b050; +} + +.invalid { + outline: 1px solid #e50000; +} + +.validation-message { + color: #e50000; +} + +.blazor-error-boundary { + background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; + padding: 1rem 1rem 1rem 3.7rem; + color: white; +} + + .blazor-error-boundary::after { + content: "An error has occurred." + } + +.darker-border-checkbox.form-check-input { + border-color: #929292; +} diff --git a/wwwroot/bootstrap/bootstrap.min.css b/wwwroot/bootstrap/bootstrap.min.css new file mode 100644 index 0000000..02ae65b --- /dev/null +++ b/wwwroot/bootstrap/bootstrap.min.css @@ -0,0 +1,7 @@ +@charset "UTF-8";/*! + * Bootstrap v5.1.0 (https://getbootstrap.com/) + * Copyright 2011-2021 The Bootstrap Authors + * Copyright 2011-2021 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE) + */:root{--bs-blue:#0d6efd;--bs-indigo:#6610f2;--bs-purple:#6f42c1;--bs-pink:#d63384;--bs-red:#dc3545;--bs-orange:#fd7e14;--bs-yellow:#ffc107;--bs-green:#198754;--bs-teal:#20c997;--bs-cyan:#0dcaf0;--bs-white:#fff;--bs-gray:#6c757d;--bs-gray-dark:#343a40;--bs-gray-100:#f8f9fa;--bs-gray-200:#e9ecef;--bs-gray-300:#dee2e6;--bs-gray-400:#ced4da;--bs-gray-500:#adb5bd;--bs-gray-600:#6c757d;--bs-gray-700:#495057;--bs-gray-800:#343a40;--bs-gray-900:#212529;--bs-primary:#0d6efd;--bs-secondary:#6c757d;--bs-success:#198754;--bs-info:#0dcaf0;--bs-warning:#ffc107;--bs-danger:#dc3545;--bs-light:#f8f9fa;--bs-dark:#212529;--bs-primary-rgb:13,110,253;--bs-secondary-rgb:108,117,125;--bs-success-rgb:25,135,84;--bs-info-rgb:13,202,240;--bs-warning-rgb:255,193,7;--bs-danger-rgb:220,53,69;--bs-light-rgb:248,249,250;--bs-dark-rgb:33,37,41;--bs-white-rgb:255,255,255;--bs-black-rgb:0,0,0;--bs-body-rgb:33,37,41;--bs-font-sans-serif:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans","Liberation Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";--bs-font-monospace:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;--bs-gradient:linear-gradient(180deg, rgba(255, 255, 255, 0.15), rgba(255, 255, 255, 0));--bs-body-font-family:var(--bs-font-sans-serif);--bs-body-font-size:1rem;--bs-body-font-weight:400;--bs-body-line-height:1.5;--bs-body-color:#212529;--bs-body-bg:#fff}*,::after,::before{box-sizing:border-box}@media (prefers-reduced-motion:no-preference){:root{scroll-behavior:smooth}}body{margin:0;font-family:var(--bs-body-font-family);font-size:var(--bs-body-font-size);font-weight:var(--bs-body-font-weight);line-height:var(--bs-body-line-height);color:var(--bs-body-color);text-align:var(--bs-body-text-align);background-color:var(--bs-body-bg);-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}hr{margin:1rem 0;color:inherit;background-color:currentColor;border:0;opacity:.25}hr:not([size]){height:1px}.h1,.h2,.h3,.h4,.h5,.h6,h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem;font-weight:500;line-height:1.2}.h1,h1{font-size:calc(1.375rem + 1.5vw)}@media (min-width:1200px){.h1,h1{font-size:2.5rem}}.h2,h2{font-size:calc(1.325rem + .9vw)}@media (min-width:1200px){.h2,h2{font-size:2rem}}.h3,h3{font-size:calc(1.3rem + .6vw)}@media (min-width:1200px){.h3,h3{font-size:1.75rem}}.h4,h4{font-size:calc(1.275rem + .3vw)}@media (min-width:1200px){.h4,h4{font-size:1.5rem}}.h5,h5{font-size:1.25rem}.h6,h6{font-size:1rem}p{margin-top:0;margin-bottom:1rem}abbr[data-bs-original-title],abbr[title]{-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}ol,ul{padding-left:2rem}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}.small,small{font-size:.875em}.mark,mark{padding:.2em;background-color:#fcf8e3}sub,sup{position:relative;font-size:.75em;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#0d6efd;text-decoration:underline}a:hover{color:#0a58ca}a:not([href]):not([class]),a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:var(--bs-font-monospace);font-size:1em;direction:ltr;unicode-bidi:bidi-override}pre{display:block;margin-top:0;margin-bottom:1rem;overflow:auto;font-size:.875em}pre code{font-size:inherit;color:inherit;word-break:normal}code{font-size:.875em;color:#d63384;word-wrap:break-word}a>code{color:inherit}kbd{padding:.2rem .4rem;font-size:.875em;color:#fff;background-color:#212529;border-radius:.2rem}kbd kbd{padding:0;font-size:1em;font-weight:700}figure{margin:0 0 1rem}img,svg{vertical-align:middle}table{caption-side:bottom;border-collapse:collapse}caption{padding-top:.5rem;padding-bottom:.5rem;color:#6c757d;text-align:left}th{text-align:inherit;text-align:-webkit-match-parent}tbody,td,tfoot,th,thead,tr{border-color:inherit;border-style:solid;border-width:0}label{display:inline-block}button{border-radius:0}button:focus:not(:focus-visible){outline:0}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}select:disabled{opacity:1}[list]::-webkit-calendar-picker-indicator{display:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}::-moz-focus-inner{padding:0;border-style:none}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{float:left;width:100%;padding:0;margin-bottom:.5rem;font-size:calc(1.275rem + .3vw);line-height:inherit}@media (min-width:1200px){legend{font-size:1.5rem}}legend+*{clear:left}::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-fields-wrapper,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-text,::-webkit-datetime-edit-year-field{padding:0}::-webkit-inner-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:textfield}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-color-swatch-wrapper{padding:0}::file-selector-button{font:inherit}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}iframe{border:0}summary{display:list-item;cursor:pointer}progress{vertical-align:baseline}[hidden]{display:none!important}.lead{font-size:1.25rem;font-weight:300}.display-1{font-size:calc(1.625rem + 4.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-1{font-size:5rem}}.display-2{font-size:calc(1.575rem + 3.9vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-2{font-size:4.5rem}}.display-3{font-size:calc(1.525rem + 3.3vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-3{font-size:4rem}}.display-4{font-size:calc(1.475rem + 2.7vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-4{font-size:3.5rem}}.display-5{font-size:calc(1.425rem + 2.1vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-5{font-size:3rem}}.display-6{font-size:calc(1.375rem + 1.5vw);font-weight:300;line-height:1.2}@media (min-width:1200px){.display-6{font-size:2.5rem}}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none}.list-inline-item{display:inline-block}.list-inline-item:not(:last-child){margin-right:.5rem}.initialism{font-size:.875em;text-transform:uppercase}.blockquote{margin-bottom:1rem;font-size:1.25rem}.blockquote>:last-child{margin-bottom:0}.blockquote-footer{margin-top:-1rem;margin-bottom:1rem;font-size:.875em;color:#6c757d}.blockquote-footer::before{content:"— "}.img-fluid{max-width:100%;height:auto}.img-thumbnail{padding:.25rem;background-color:#fff;border:1px solid #dee2e6;border-radius:.25rem;max-width:100%;height:auto}.figure{display:inline-block}.figure-img{margin-bottom:.5rem;line-height:1}.figure-caption{font-size:.875em;color:#6c757d}.container,.container-fluid,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{width:100%;padding-right:var(--bs-gutter-x,.75rem);padding-left:var(--bs-gutter-x,.75rem);margin-right:auto;margin-left:auto}@media (min-width:576px){.container,.container-sm{max-width:540px}}@media (min-width:768px){.container,.container-md,.container-sm{max-width:720px}}@media (min-width:992px){.container,.container-lg,.container-md,.container-sm{max-width:960px}}@media (min-width:1200px){.container,.container-lg,.container-md,.container-sm,.container-xl{max-width:1140px}}@media (min-width:1400px){.container,.container-lg,.container-md,.container-sm,.container-xl,.container-xxl{max-width:1320px}}.row{--bs-gutter-x:1.5rem;--bs-gutter-y:0;display:flex;flex-wrap:wrap;margin-top:calc(var(--bs-gutter-y) * -1);margin-right:calc(var(--bs-gutter-x) * -.5);margin-left:calc(var(--bs-gutter-x) * -.5)}.row>*{flex-shrink:0;width:100%;max-width:100%;padding-right:calc(var(--bs-gutter-x) * .5);padding-left:calc(var(--bs-gutter-x) * .5);margin-top:var(--bs-gutter-y)}.col{flex:1 0 0%}.row-cols-auto>*{flex:0 0 auto;width:auto}.row-cols-1>*{flex:0 0 auto;width:100%}.row-cols-2>*{flex:0 0 auto;width:50%}.row-cols-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-4>*{flex:0 0 auto;width:25%}.row-cols-5>*{flex:0 0 auto;width:20%}.row-cols-6>*{flex:0 0 auto;width:16.6666666667%}.col-auto{flex:0 0 auto;width:auto}.col-1{flex:0 0 auto;width:8.33333333%}.col-2{flex:0 0 auto;width:16.66666667%}.col-3{flex:0 0 auto;width:25%}.col-4{flex:0 0 auto;width:33.33333333%}.col-5{flex:0 0 auto;width:41.66666667%}.col-6{flex:0 0 auto;width:50%}.col-7{flex:0 0 auto;width:58.33333333%}.col-8{flex:0 0 auto;width:66.66666667%}.col-9{flex:0 0 auto;width:75%}.col-10{flex:0 0 auto;width:83.33333333%}.col-11{flex:0 0 auto;width:91.66666667%}.col-12{flex:0 0 auto;width:100%}.offset-1{margin-left:8.33333333%}.offset-2{margin-left:16.66666667%}.offset-3{margin-left:25%}.offset-4{margin-left:33.33333333%}.offset-5{margin-left:41.66666667%}.offset-6{margin-left:50%}.offset-7{margin-left:58.33333333%}.offset-8{margin-left:66.66666667%}.offset-9{margin-left:75%}.offset-10{margin-left:83.33333333%}.offset-11{margin-left:91.66666667%}.g-0,.gx-0{--bs-gutter-x:0}.g-0,.gy-0{--bs-gutter-y:0}.g-1,.gx-1{--bs-gutter-x:0.25rem}.g-1,.gy-1{--bs-gutter-y:0.25rem}.g-2,.gx-2{--bs-gutter-x:0.5rem}.g-2,.gy-2{--bs-gutter-y:0.5rem}.g-3,.gx-3{--bs-gutter-x:1rem}.g-3,.gy-3{--bs-gutter-y:1rem}.g-4,.gx-4{--bs-gutter-x:1.5rem}.g-4,.gy-4{--bs-gutter-y:1.5rem}.g-5,.gx-5{--bs-gutter-x:3rem}.g-5,.gy-5{--bs-gutter-y:3rem}@media (min-width:576px){.col-sm{flex:1 0 0%}.row-cols-sm-auto>*{flex:0 0 auto;width:auto}.row-cols-sm-1>*{flex:0 0 auto;width:100%}.row-cols-sm-2>*{flex:0 0 auto;width:50%}.row-cols-sm-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-sm-4>*{flex:0 0 auto;width:25%}.row-cols-sm-5>*{flex:0 0 auto;width:20%}.row-cols-sm-6>*{flex:0 0 auto;width:16.6666666667%}.col-sm-auto{flex:0 0 auto;width:auto}.col-sm-1{flex:0 0 auto;width:8.33333333%}.col-sm-2{flex:0 0 auto;width:16.66666667%}.col-sm-3{flex:0 0 auto;width:25%}.col-sm-4{flex:0 0 auto;width:33.33333333%}.col-sm-5{flex:0 0 auto;width:41.66666667%}.col-sm-6{flex:0 0 auto;width:50%}.col-sm-7{flex:0 0 auto;width:58.33333333%}.col-sm-8{flex:0 0 auto;width:66.66666667%}.col-sm-9{flex:0 0 auto;width:75%}.col-sm-10{flex:0 0 auto;width:83.33333333%}.col-sm-11{flex:0 0 auto;width:91.66666667%}.col-sm-12{flex:0 0 auto;width:100%}.offset-sm-0{margin-left:0}.offset-sm-1{margin-left:8.33333333%}.offset-sm-2{margin-left:16.66666667%}.offset-sm-3{margin-left:25%}.offset-sm-4{margin-left:33.33333333%}.offset-sm-5{margin-left:41.66666667%}.offset-sm-6{margin-left:50%}.offset-sm-7{margin-left:58.33333333%}.offset-sm-8{margin-left:66.66666667%}.offset-sm-9{margin-left:75%}.offset-sm-10{margin-left:83.33333333%}.offset-sm-11{margin-left:91.66666667%}.g-sm-0,.gx-sm-0{--bs-gutter-x:0}.g-sm-0,.gy-sm-0{--bs-gutter-y:0}.g-sm-1,.gx-sm-1{--bs-gutter-x:0.25rem}.g-sm-1,.gy-sm-1{--bs-gutter-y:0.25rem}.g-sm-2,.gx-sm-2{--bs-gutter-x:0.5rem}.g-sm-2,.gy-sm-2{--bs-gutter-y:0.5rem}.g-sm-3,.gx-sm-3{--bs-gutter-x:1rem}.g-sm-3,.gy-sm-3{--bs-gutter-y:1rem}.g-sm-4,.gx-sm-4{--bs-gutter-x:1.5rem}.g-sm-4,.gy-sm-4{--bs-gutter-y:1.5rem}.g-sm-5,.gx-sm-5{--bs-gutter-x:3rem}.g-sm-5,.gy-sm-5{--bs-gutter-y:3rem}}@media (min-width:768px){.col-md{flex:1 0 0%}.row-cols-md-auto>*{flex:0 0 auto;width:auto}.row-cols-md-1>*{flex:0 0 auto;width:100%}.row-cols-md-2>*{flex:0 0 auto;width:50%}.row-cols-md-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-md-4>*{flex:0 0 auto;width:25%}.row-cols-md-5>*{flex:0 0 auto;width:20%}.row-cols-md-6>*{flex:0 0 auto;width:16.6666666667%}.col-md-auto{flex:0 0 auto;width:auto}.col-md-1{flex:0 0 auto;width:8.33333333%}.col-md-2{flex:0 0 auto;width:16.66666667%}.col-md-3{flex:0 0 auto;width:25%}.col-md-4{flex:0 0 auto;width:33.33333333%}.col-md-5{flex:0 0 auto;width:41.66666667%}.col-md-6{flex:0 0 auto;width:50%}.col-md-7{flex:0 0 auto;width:58.33333333%}.col-md-8{flex:0 0 auto;width:66.66666667%}.col-md-9{flex:0 0 auto;width:75%}.col-md-10{flex:0 0 auto;width:83.33333333%}.col-md-11{flex:0 0 auto;width:91.66666667%}.col-md-12{flex:0 0 auto;width:100%}.offset-md-0{margin-left:0}.offset-md-1{margin-left:8.33333333%}.offset-md-2{margin-left:16.66666667%}.offset-md-3{margin-left:25%}.offset-md-4{margin-left:33.33333333%}.offset-md-5{margin-left:41.66666667%}.offset-md-6{margin-left:50%}.offset-md-7{margin-left:58.33333333%}.offset-md-8{margin-left:66.66666667%}.offset-md-9{margin-left:75%}.offset-md-10{margin-left:83.33333333%}.offset-md-11{margin-left:91.66666667%}.g-md-0,.gx-md-0{--bs-gutter-x:0}.g-md-0,.gy-md-0{--bs-gutter-y:0}.g-md-1,.gx-md-1{--bs-gutter-x:0.25rem}.g-md-1,.gy-md-1{--bs-gutter-y:0.25rem}.g-md-2,.gx-md-2{--bs-gutter-x:0.5rem}.g-md-2,.gy-md-2{--bs-gutter-y:0.5rem}.g-md-3,.gx-md-3{--bs-gutter-x:1rem}.g-md-3,.gy-md-3{--bs-gutter-y:1rem}.g-md-4,.gx-md-4{--bs-gutter-x:1.5rem}.g-md-4,.gy-md-4{--bs-gutter-y:1.5rem}.g-md-5,.gx-md-5{--bs-gutter-x:3rem}.g-md-5,.gy-md-5{--bs-gutter-y:3rem}}@media (min-width:992px){.col-lg{flex:1 0 0%}.row-cols-lg-auto>*{flex:0 0 auto;width:auto}.row-cols-lg-1>*{flex:0 0 auto;width:100%}.row-cols-lg-2>*{flex:0 0 auto;width:50%}.row-cols-lg-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-lg-4>*{flex:0 0 auto;width:25%}.row-cols-lg-5>*{flex:0 0 auto;width:20%}.row-cols-lg-6>*{flex:0 0 auto;width:16.6666666667%}.col-lg-auto{flex:0 0 auto;width:auto}.col-lg-1{flex:0 0 auto;width:8.33333333%}.col-lg-2{flex:0 0 auto;width:16.66666667%}.col-lg-3{flex:0 0 auto;width:25%}.col-lg-4{flex:0 0 auto;width:33.33333333%}.col-lg-5{flex:0 0 auto;width:41.66666667%}.col-lg-6{flex:0 0 auto;width:50%}.col-lg-7{flex:0 0 auto;width:58.33333333%}.col-lg-8{flex:0 0 auto;width:66.66666667%}.col-lg-9{flex:0 0 auto;width:75%}.col-lg-10{flex:0 0 auto;width:83.33333333%}.col-lg-11{flex:0 0 auto;width:91.66666667%}.col-lg-12{flex:0 0 auto;width:100%}.offset-lg-0{margin-left:0}.offset-lg-1{margin-left:8.33333333%}.offset-lg-2{margin-left:16.66666667%}.offset-lg-3{margin-left:25%}.offset-lg-4{margin-left:33.33333333%}.offset-lg-5{margin-left:41.66666667%}.offset-lg-6{margin-left:50%}.offset-lg-7{margin-left:58.33333333%}.offset-lg-8{margin-left:66.66666667%}.offset-lg-9{margin-left:75%}.offset-lg-10{margin-left:83.33333333%}.offset-lg-11{margin-left:91.66666667%}.g-lg-0,.gx-lg-0{--bs-gutter-x:0}.g-lg-0,.gy-lg-0{--bs-gutter-y:0}.g-lg-1,.gx-lg-1{--bs-gutter-x:0.25rem}.g-lg-1,.gy-lg-1{--bs-gutter-y:0.25rem}.g-lg-2,.gx-lg-2{--bs-gutter-x:0.5rem}.g-lg-2,.gy-lg-2{--bs-gutter-y:0.5rem}.g-lg-3,.gx-lg-3{--bs-gutter-x:1rem}.g-lg-3,.gy-lg-3{--bs-gutter-y:1rem}.g-lg-4,.gx-lg-4{--bs-gutter-x:1.5rem}.g-lg-4,.gy-lg-4{--bs-gutter-y:1.5rem}.g-lg-5,.gx-lg-5{--bs-gutter-x:3rem}.g-lg-5,.gy-lg-5{--bs-gutter-y:3rem}}@media (min-width:1200px){.col-xl{flex:1 0 0%}.row-cols-xl-auto>*{flex:0 0 auto;width:auto}.row-cols-xl-1>*{flex:0 0 auto;width:100%}.row-cols-xl-2>*{flex:0 0 auto;width:50%}.row-cols-xl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xl-4>*{flex:0 0 auto;width:25%}.row-cols-xl-5>*{flex:0 0 auto;width:20%}.row-cols-xl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xl-auto{flex:0 0 auto;width:auto}.col-xl-1{flex:0 0 auto;width:8.33333333%}.col-xl-2{flex:0 0 auto;width:16.66666667%}.col-xl-3{flex:0 0 auto;width:25%}.col-xl-4{flex:0 0 auto;width:33.33333333%}.col-xl-5{flex:0 0 auto;width:41.66666667%}.col-xl-6{flex:0 0 auto;width:50%}.col-xl-7{flex:0 0 auto;width:58.33333333%}.col-xl-8{flex:0 0 auto;width:66.66666667%}.col-xl-9{flex:0 0 auto;width:75%}.col-xl-10{flex:0 0 auto;width:83.33333333%}.col-xl-11{flex:0 0 auto;width:91.66666667%}.col-xl-12{flex:0 0 auto;width:100%}.offset-xl-0{margin-left:0}.offset-xl-1{margin-left:8.33333333%}.offset-xl-2{margin-left:16.66666667%}.offset-xl-3{margin-left:25%}.offset-xl-4{margin-left:33.33333333%}.offset-xl-5{margin-left:41.66666667%}.offset-xl-6{margin-left:50%}.offset-xl-7{margin-left:58.33333333%}.offset-xl-8{margin-left:66.66666667%}.offset-xl-9{margin-left:75%}.offset-xl-10{margin-left:83.33333333%}.offset-xl-11{margin-left:91.66666667%}.g-xl-0,.gx-xl-0{--bs-gutter-x:0}.g-xl-0,.gy-xl-0{--bs-gutter-y:0}.g-xl-1,.gx-xl-1{--bs-gutter-x:0.25rem}.g-xl-1,.gy-xl-1{--bs-gutter-y:0.25rem}.g-xl-2,.gx-xl-2{--bs-gutter-x:0.5rem}.g-xl-2,.gy-xl-2{--bs-gutter-y:0.5rem}.g-xl-3,.gx-xl-3{--bs-gutter-x:1rem}.g-xl-3,.gy-xl-3{--bs-gutter-y:1rem}.g-xl-4,.gx-xl-4{--bs-gutter-x:1.5rem}.g-xl-4,.gy-xl-4{--bs-gutter-y:1.5rem}.g-xl-5,.gx-xl-5{--bs-gutter-x:3rem}.g-xl-5,.gy-xl-5{--bs-gutter-y:3rem}}@media (min-width:1400px){.col-xxl{flex:1 0 0%}.row-cols-xxl-auto>*{flex:0 0 auto;width:auto}.row-cols-xxl-1>*{flex:0 0 auto;width:100%}.row-cols-xxl-2>*{flex:0 0 auto;width:50%}.row-cols-xxl-3>*{flex:0 0 auto;width:33.3333333333%}.row-cols-xxl-4>*{flex:0 0 auto;width:25%}.row-cols-xxl-5>*{flex:0 0 auto;width:20%}.row-cols-xxl-6>*{flex:0 0 auto;width:16.6666666667%}.col-xxl-auto{flex:0 0 auto;width:auto}.col-xxl-1{flex:0 0 auto;width:8.33333333%}.col-xxl-2{flex:0 0 auto;width:16.66666667%}.col-xxl-3{flex:0 0 auto;width:25%}.col-xxl-4{flex:0 0 auto;width:33.33333333%}.col-xxl-5{flex:0 0 auto;width:41.66666667%}.col-xxl-6{flex:0 0 auto;width:50%}.col-xxl-7{flex:0 0 auto;width:58.33333333%}.col-xxl-8{flex:0 0 auto;width:66.66666667%}.col-xxl-9{flex:0 0 auto;width:75%}.col-xxl-10{flex:0 0 auto;width:83.33333333%}.col-xxl-11{flex:0 0 auto;width:91.66666667%}.col-xxl-12{flex:0 0 auto;width:100%}.offset-xxl-0{margin-left:0}.offset-xxl-1{margin-left:8.33333333%}.offset-xxl-2{margin-left:16.66666667%}.offset-xxl-3{margin-left:25%}.offset-xxl-4{margin-left:33.33333333%}.offset-xxl-5{margin-left:41.66666667%}.offset-xxl-6{margin-left:50%}.offset-xxl-7{margin-left:58.33333333%}.offset-xxl-8{margin-left:66.66666667%}.offset-xxl-9{margin-left:75%}.offset-xxl-10{margin-left:83.33333333%}.offset-xxl-11{margin-left:91.66666667%}.g-xxl-0,.gx-xxl-0{--bs-gutter-x:0}.g-xxl-0,.gy-xxl-0{--bs-gutter-y:0}.g-xxl-1,.gx-xxl-1{--bs-gutter-x:0.25rem}.g-xxl-1,.gy-xxl-1{--bs-gutter-y:0.25rem}.g-xxl-2,.gx-xxl-2{--bs-gutter-x:0.5rem}.g-xxl-2,.gy-xxl-2{--bs-gutter-y:0.5rem}.g-xxl-3,.gx-xxl-3{--bs-gutter-x:1rem}.g-xxl-3,.gy-xxl-3{--bs-gutter-y:1rem}.g-xxl-4,.gx-xxl-4{--bs-gutter-x:1.5rem}.g-xxl-4,.gy-xxl-4{--bs-gutter-y:1.5rem}.g-xxl-5,.gx-xxl-5{--bs-gutter-x:3rem}.g-xxl-5,.gy-xxl-5{--bs-gutter-y:3rem}}.table{--bs-table-bg:transparent;--bs-table-accent-bg:transparent;--bs-table-striped-color:#212529;--bs-table-striped-bg:rgba(0, 0, 0, 0.05);--bs-table-active-color:#212529;--bs-table-active-bg:rgba(0, 0, 0, 0.1);--bs-table-hover-color:#212529;--bs-table-hover-bg:rgba(0, 0, 0, 0.075);width:100%;margin-bottom:1rem;color:#212529;vertical-align:top;border-color:#dee2e6}.table>:not(caption)>*>*{padding:.5rem .5rem;background-color:var(--bs-table-bg);border-bottom-width:1px;box-shadow:inset 0 0 0 9999px var(--bs-table-accent-bg)}.table>tbody{vertical-align:inherit}.table>thead{vertical-align:bottom}.table>:not(:last-child)>:last-child>*{border-bottom-color:currentColor}.caption-top{caption-side:top}.table-sm>:not(caption)>*>*{padding:.25rem .25rem}.table-bordered>:not(caption)>*{border-width:1px 0}.table-bordered>:not(caption)>*>*{border-width:0 1px}.table-borderless>:not(caption)>*>*{border-bottom-width:0}.table-striped>tbody>tr:nth-of-type(odd){--bs-table-accent-bg:var(--bs-table-striped-bg);color:var(--bs-table-striped-color)}.table-active{--bs-table-accent-bg:var(--bs-table-active-bg);color:var(--bs-table-active-color)}.table-hover>tbody>tr:hover{--bs-table-accent-bg:var(--bs-table-hover-bg);color:var(--bs-table-hover-color)}.table-primary{--bs-table-bg:#cfe2ff;--bs-table-striped-bg:#c5d7f2;--bs-table-striped-color:#000;--bs-table-active-bg:#bacbe6;--bs-table-active-color:#000;--bs-table-hover-bg:#bfd1ec;--bs-table-hover-color:#000;color:#000;border-color:#bacbe6}.table-secondary{--bs-table-bg:#e2e3e5;--bs-table-striped-bg:#d7d8da;--bs-table-striped-color:#000;--bs-table-active-bg:#cbccce;--bs-table-active-color:#000;--bs-table-hover-bg:#d1d2d4;--bs-table-hover-color:#000;color:#000;border-color:#cbccce}.table-success{--bs-table-bg:#d1e7dd;--bs-table-striped-bg:#c7dbd2;--bs-table-striped-color:#000;--bs-table-active-bg:#bcd0c7;--bs-table-active-color:#000;--bs-table-hover-bg:#c1d6cc;--bs-table-hover-color:#000;color:#000;border-color:#bcd0c7}.table-info{--bs-table-bg:#cff4fc;--bs-table-striped-bg:#c5e8ef;--bs-table-striped-color:#000;--bs-table-active-bg:#badce3;--bs-table-active-color:#000;--bs-table-hover-bg:#bfe2e9;--bs-table-hover-color:#000;color:#000;border-color:#badce3}.table-warning{--bs-table-bg:#fff3cd;--bs-table-striped-bg:#f2e7c3;--bs-table-striped-color:#000;--bs-table-active-bg:#e6dbb9;--bs-table-active-color:#000;--bs-table-hover-bg:#ece1be;--bs-table-hover-color:#000;color:#000;border-color:#e6dbb9}.table-danger{--bs-table-bg:#f8d7da;--bs-table-striped-bg:#eccccf;--bs-table-striped-color:#000;--bs-table-active-bg:#dfc2c4;--bs-table-active-color:#000;--bs-table-hover-bg:#e5c7ca;--bs-table-hover-color:#000;color:#000;border-color:#dfc2c4}.table-light{--bs-table-bg:#f8f9fa;--bs-table-striped-bg:#ecedee;--bs-table-striped-color:#000;--bs-table-active-bg:#dfe0e1;--bs-table-active-color:#000;--bs-table-hover-bg:#e5e6e7;--bs-table-hover-color:#000;color:#000;border-color:#dfe0e1}.table-dark{--bs-table-bg:#212529;--bs-table-striped-bg:#2c3034;--bs-table-striped-color:#fff;--bs-table-active-bg:#373b3e;--bs-table-active-color:#fff;--bs-table-hover-bg:#323539;--bs-table-hover-color:#fff;color:#fff;border-color:#373b3e}.table-responsive{overflow-x:auto;-webkit-overflow-scrolling:touch}@media (max-width:575.98px){.table-responsive-sm{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:767.98px){.table-responsive-md{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:991.98px){.table-responsive-lg{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1199.98px){.table-responsive-xl{overflow-x:auto;-webkit-overflow-scrolling:touch}}@media (max-width:1399.98px){.table-responsive-xxl{overflow-x:auto;-webkit-overflow-scrolling:touch}}.form-label{margin-bottom:.5rem}.col-form-label{padding-top:calc(.375rem + 1px);padding-bottom:calc(.375rem + 1px);margin-bottom:0;font-size:inherit;line-height:1.5}.col-form-label-lg{padding-top:calc(.5rem + 1px);padding-bottom:calc(.5rem + 1px);font-size:1.25rem}.col-form-label-sm{padding-top:calc(.25rem + 1px);padding-bottom:calc(.25rem + 1px);font-size:.875rem}.form-text{margin-top:.25rem;font-size:.875em;color:#6c757d}.form-control{display:block;width:100%;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-clip:padding-box;border:1px solid #ced4da;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control{transition:none}}.form-control[type=file]{overflow:hidden}.form-control[type=file]:not(:disabled):not([readonly]){cursor:pointer}.form-control:focus{color:#212529;background-color:#fff;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-control::-webkit-date-and-time-value{height:1.5em}.form-control::-moz-placeholder{color:#6c757d;opacity:1}.form-control::placeholder{color:#6c757d;opacity:1}.form-control:disabled,.form-control[readonly]{background-color:#e9ecef;opacity:1}.form-control::file-selector-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::file-selector-button{transition:none}}.form-control:hover:not(:disabled):not([readonly])::file-selector-button{background-color:#dde0e3}.form-control::-webkit-file-upload-button{padding:.375rem .75rem;margin:-.375rem -.75rem;-webkit-margin-end:.75rem;margin-inline-end:.75rem;color:#212529;background-color:#e9ecef;pointer-events:none;border-color:inherit;border-style:solid;border-width:0;border-inline-end-width:1px;border-radius:0;-webkit-transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-control::-webkit-file-upload-button{-webkit-transition:none;transition:none}}.form-control:hover:not(:disabled):not([readonly])::-webkit-file-upload-button{background-color:#dde0e3}.form-control-plaintext{display:block;width:100%;padding:.375rem 0;margin-bottom:0;line-height:1.5;color:#212529;background-color:transparent;border:solid transparent;border-width:1px 0}.form-control-plaintext.form-control-lg,.form-control-plaintext.form-control-sm{padding-right:0;padding-left:0}.form-control-sm{min-height:calc(1.5em + .5rem + 2px);padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.form-control-sm::file-selector-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-sm::-webkit-file-upload-button{padding:.25rem .5rem;margin:-.25rem -.5rem;-webkit-margin-end:.5rem;margin-inline-end:.5rem}.form-control-lg{min-height:calc(1.5em + 1rem + 2px);padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.form-control-lg::file-selector-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}.form-control-lg::-webkit-file-upload-button{padding:.5rem 1rem;margin:-.5rem -1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}textarea.form-control{min-height:calc(1.5em + .75rem + 2px)}textarea.form-control-sm{min-height:calc(1.5em + .5rem + 2px)}textarea.form-control-lg{min-height:calc(1.5em + 1rem + 2px)}.form-control-color{width:3rem;height:auto;padding:.375rem}.form-control-color:not(:disabled):not([readonly]){cursor:pointer}.form-control-color::-moz-color-swatch{height:1.5em;border-radius:.25rem}.form-control-color::-webkit-color-swatch{height:1.5em;border-radius:.25rem}.form-select{display:block;width:100%;padding:.375rem 2.25rem .375rem .75rem;-moz-padding-start:calc(0.75rem - 3px);font-size:1rem;font-weight:400;line-height:1.5;color:#212529;background-color:#fff;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right .75rem center;background-size:16px 12px;border:1px solid #ced4da;border-radius:.25rem;transition:border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-select{transition:none}}.form-select:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-select[multiple],.form-select[size]:not([size="1"]){padding-right:.75rem;background-image:none}.form-select:disabled{background-color:#e9ecef}.form-select:-moz-focusring{color:transparent;text-shadow:0 0 0 #212529}.form-select-sm{padding-top:.25rem;padding-bottom:.25rem;padding-left:.5rem;font-size:.875rem}.form-select-lg{padding-top:.5rem;padding-bottom:.5rem;padding-left:1rem;font-size:1.25rem}.form-check{display:block;min-height:1.5rem;padding-left:1.5em;margin-bottom:.125rem}.form-check .form-check-input{float:left;margin-left:-1.5em}.form-check-input{width:1em;height:1em;margin-top:.25em;vertical-align:top;background-color:#fff;background-repeat:no-repeat;background-position:center;background-size:contain;border:1px solid rgba(0,0,0,.25);-webkit-appearance:none;-moz-appearance:none;appearance:none;-webkit-print-color-adjust:exact;color-adjust:exact}.form-check-input[type=checkbox]{border-radius:.25em}.form-check-input[type=radio]{border-radius:50%}.form-check-input:active{filter:brightness(90%)}.form-check-input:focus{border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.form-check-input:checked{background-color:#0d6efd;border-color:#0d6efd}.form-check-input:checked[type=checkbox]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10l3 3l6-6'/%3e%3c/svg%3e")}.form-check-input:checked[type=radio]{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e")}.form-check-input[type=checkbox]:indeterminate{background-color:#0d6efd;border-color:#0d6efd;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'%3e%3cpath fill='none' stroke='%23fff' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M6 10h8'/%3e%3c/svg%3e")}.form-check-input:disabled{pointer-events:none;filter:none;opacity:.5}.form-check-input:disabled~.form-check-label,.form-check-input[disabled]~.form-check-label{opacity:.5}.form-switch{padding-left:2.5em}.form-switch .form-check-input{width:2em;margin-left:-2.5em;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='rgba%280, 0, 0, 0.25%29'/%3e%3c/svg%3e");background-position:left center;border-radius:2em;transition:background-position .15s ease-in-out}@media (prefers-reduced-motion:reduce){.form-switch .form-check-input{transition:none}}.form-switch .form-check-input:focus{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%2386b7fe'/%3e%3c/svg%3e")}.form-switch .form-check-input:checked{background-position:right center;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='3' fill='%23fff'/%3e%3c/svg%3e")}.form-check-inline{display:inline-block;margin-right:1rem}.btn-check{position:absolute;clip:rect(0,0,0,0);pointer-events:none}.btn-check:disabled+.btn,.btn-check[disabled]+.btn{pointer-events:none;filter:none;opacity:.65}.form-range{width:100%;height:1.5rem;padding:0;background-color:transparent;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-range:focus{outline:0}.form-range:focus::-webkit-slider-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range:focus::-moz-range-thumb{box-shadow:0 0 0 1px #fff,0 0 0 .25rem rgba(13,110,253,.25)}.form-range::-moz-focus-outer{border:0}.form-range::-webkit-slider-thumb{width:1rem;height:1rem;margin-top:-.25rem;background-color:#0d6efd;border:0;border-radius:1rem;-webkit-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-webkit-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-webkit-slider-thumb{-webkit-transition:none;transition:none}}.form-range::-webkit-slider-thumb:active{background-color:#b6d4fe}.form-range::-webkit-slider-runnable-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range::-moz-range-thumb{width:1rem;height:1rem;background-color:#0d6efd;border:0;border-radius:1rem;-moz-transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;transition:background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;-moz-appearance:none;appearance:none}@media (prefers-reduced-motion:reduce){.form-range::-moz-range-thumb{-moz-transition:none;transition:none}}.form-range::-moz-range-thumb:active{background-color:#b6d4fe}.form-range::-moz-range-track{width:100%;height:.5rem;color:transparent;cursor:pointer;background-color:#dee2e6;border-color:transparent;border-radius:1rem}.form-range:disabled{pointer-events:none}.form-range:disabled::-webkit-slider-thumb{background-color:#adb5bd}.form-range:disabled::-moz-range-thumb{background-color:#adb5bd}.form-floating{position:relative}.form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px);line-height:1.25}.form-floating>label{position:absolute;top:0;left:0;height:100%;padding:1rem .75rem;pointer-events:none;border:1px solid transparent;transform-origin:0 0;transition:opacity .1s ease-in-out,transform .1s ease-in-out}@media (prefers-reduced-motion:reduce){.form-floating>label{transition:none}}.form-floating>.form-control{padding:1rem .75rem}.form-floating>.form-control::-moz-placeholder{color:transparent}.form-floating>.form-control::placeholder{color:transparent}.form-floating>.form-control:not(:-moz-placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:focus,.form-floating>.form-control:not(:placeholder-shown){padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:-webkit-autofill{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-select{padding-top:1.625rem;padding-bottom:.625rem}.form-floating>.form-control:not(:-moz-placeholder-shown)~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:focus~label,.form-floating>.form-control:not(:placeholder-shown)~label,.form-floating>.form-select~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.form-floating>.form-control:-webkit-autofill~label{opacity:.65;transform:scale(.85) translateY(-.5rem) translateX(.15rem)}.input-group{position:relative;display:flex;flex-wrap:wrap;align-items:stretch;width:100%}.input-group>.form-control,.input-group>.form-select{position:relative;flex:1 1 auto;width:1%;min-width:0}.input-group>.form-control:focus,.input-group>.form-select:focus{z-index:3}.input-group .btn{position:relative;z-index:2}.input-group .btn:focus{z-index:3}.input-group-text{display:flex;align-items:center;padding:.375rem .75rem;font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:center;white-space:nowrap;background-color:#e9ecef;border:1px solid #ced4da;border-radius:.25rem}.input-group-lg>.btn,.input-group-lg>.form-control,.input-group-lg>.form-select,.input-group-lg>.input-group-text{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.input-group-sm>.btn,.input-group-sm>.form-control,.input-group-sm>.form-select,.input-group-sm>.input-group-text{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.input-group-lg>.form-select,.input-group-sm>.form-select{padding-right:3rem}.input-group:not(.has-validation)>.dropdown-toggle:nth-last-child(n+3),.input-group:not(.has-validation)>:not(:last-child):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group.has-validation>.dropdown-toggle:nth-last-child(n+4),.input-group.has-validation>:nth-last-child(n+3):not(.dropdown-toggle):not(.dropdown-menu){border-top-right-radius:0;border-bottom-right-radius:0}.input-group>:not(:first-child):not(.dropdown-menu):not(.valid-tooltip):not(.valid-feedback):not(.invalid-tooltip):not(.invalid-feedback){margin-left:-1px;border-top-left-radius:0;border-bottom-left-radius:0}.valid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#198754}.valid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(25,135,84,.9);border-radius:.25rem}.is-valid~.valid-feedback,.is-valid~.valid-tooltip,.was-validated :valid~.valid-feedback,.was-validated :valid~.valid-tooltip{display:block}.form-control.is-valid,.was-validated .form-control:valid{border-color:#198754;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-valid:focus,.was-validated .form-control:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.was-validated textarea.form-control:valid,textarea.form-control.is-valid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-valid,.was-validated .form-select:valid{border-color:#198754}.form-select.is-valid:not([multiple]):not([size]),.form-select.is-valid:not([multiple])[size="1"],.was-validated .form-select:valid:not([multiple]):not([size]),.was-validated .form-select:valid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 8 8'%3e%3cpath fill='%23198754' d='M2.3 6.73L.6 4.53c-.4-1.04.46-1.4 1.1-.8l1.1 1.4 3.4-3.8c.6-.63 1.6-.27 1.2.7l-4 4.6c-.43.5-.8.4-1.1.1z'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-valid:focus,.was-validated .form-select:valid:focus{border-color:#198754;box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid,.was-validated .form-check-input:valid{border-color:#198754}.form-check-input.is-valid:checked,.was-validated .form-check-input:valid:checked{background-color:#198754}.form-check-input.is-valid:focus,.was-validated .form-check-input:valid:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.25)}.form-check-input.is-valid~.form-check-label,.was-validated .form-check-input:valid~.form-check-label{color:#198754}.form-check-inline .form-check-input~.valid-feedback{margin-left:.5em}.input-group .form-control.is-valid,.input-group .form-select.is-valid,.was-validated .input-group .form-control:valid,.was-validated .input-group .form-select:valid{z-index:1}.input-group .form-control.is-valid:focus,.input-group .form-select.is-valid:focus,.was-validated .input-group .form-control:valid:focus,.was-validated .input-group .form-select:valid:focus{z-index:3}.invalid-feedback{display:none;width:100%;margin-top:.25rem;font-size:.875em;color:#dc3545}.invalid-tooltip{position:absolute;top:100%;z-index:5;display:none;max-width:100%;padding:.25rem .5rem;margin-top:.1rem;font-size:.875rem;color:#fff;background-color:rgba(220,53,69,.9);border-radius:.25rem}.is-invalid~.invalid-feedback,.is-invalid~.invalid-tooltip,.was-validated :invalid~.invalid-feedback,.was-validated :invalid~.invalid-tooltip{display:block}.form-control.is-invalid,.was-validated .form-control:invalid{border-color:#dc3545;padding-right:calc(1.5em + .75rem);background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-repeat:no-repeat;background-position:right calc(.375em + .1875rem) center;background-size:calc(.75em + .375rem) calc(.75em + .375rem)}.form-control.is-invalid:focus,.was-validated .form-control:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.was-validated textarea.form-control:invalid,textarea.form-control.is-invalid{padding-right:calc(1.5em + .75rem);background-position:top calc(.375em + .1875rem) right calc(.375em + .1875rem)}.form-select.is-invalid,.was-validated .form-select:invalid{border-color:#dc3545}.form-select.is-invalid:not([multiple]):not([size]),.form-select.is-invalid:not([multiple])[size="1"],.was-validated .form-select:invalid:not([multiple]):not([size]),.was-validated .form-select:invalid:not([multiple])[size="1"]{padding-right:4.125rem;background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3e%3cpath fill='none' stroke='%23343a40' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M2 5l6 6 6-6'/%3e%3c/svg%3e"),url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' width='12' height='12' fill='none' stroke='%23dc3545'%3e%3ccircle cx='6' cy='6' r='4.5'/%3e%3cpath stroke-linejoin='round' d='M5.8 3.6h.4L6 6.5z'/%3e%3ccircle cx='6' cy='8.2' r='.6' fill='%23dc3545' stroke='none'/%3e%3c/svg%3e");background-position:right .75rem center,center right 2.25rem;background-size:16px 12px,calc(.75em + .375rem) calc(.75em + .375rem)}.form-select.is-invalid:focus,.was-validated .form-select:invalid:focus{border-color:#dc3545;box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid,.was-validated .form-check-input:invalid{border-color:#dc3545}.form-check-input.is-invalid:checked,.was-validated .form-check-input:invalid:checked{background-color:#dc3545}.form-check-input.is-invalid:focus,.was-validated .form-check-input:invalid:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.25)}.form-check-input.is-invalid~.form-check-label,.was-validated .form-check-input:invalid~.form-check-label{color:#dc3545}.form-check-inline .form-check-input~.invalid-feedback{margin-left:.5em}.input-group .form-control.is-invalid,.input-group .form-select.is-invalid,.was-validated .input-group .form-control:invalid,.was-validated .input-group .form-select:invalid{z-index:2}.input-group .form-control.is-invalid:focus,.input-group .form-select.is-invalid:focus,.was-validated .input-group .form-control:invalid:focus,.was-validated .input-group .form-select:invalid:focus{z-index:3}.btn{display:inline-block;font-weight:400;line-height:1.5;color:#212529;text-align:center;text-decoration:none;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;user-select:none;background-color:transparent;border:1px solid transparent;padding:.375rem .75rem;font-size:1rem;border-radius:.25rem;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.btn{transition:none}}.btn:hover{color:#212529}.btn-check:focus+.btn,.btn:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.btn.disabled,.btn:disabled,fieldset:disabled .btn{pointer-events:none;opacity:.65}.btn-primary{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-primary:hover{color:#fff;background-color:#0b5ed7;border-color:#0a58ca}.btn-check:focus+.btn-primary,.btn-primary:focus{color:#fff;background-color:#0b5ed7;border-color:#0a58ca;box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-check:active+.btn-primary,.btn-check:checked+.btn-primary,.btn-primary.active,.btn-primary:active,.show>.btn-primary.dropdown-toggle{color:#fff;background-color:#0a58ca;border-color:#0a53be}.btn-check:active+.btn-primary:focus,.btn-check:checked+.btn-primary:focus,.btn-primary.active:focus,.btn-primary:active:focus,.show>.btn-primary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(49,132,253,.5)}.btn-primary.disabled,.btn-primary:disabled{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-secondary{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-secondary:hover{color:#fff;background-color:#5c636a;border-color:#565e64}.btn-check:focus+.btn-secondary,.btn-secondary:focus{color:#fff;background-color:#5c636a;border-color:#565e64;box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-check:active+.btn-secondary,.btn-check:checked+.btn-secondary,.btn-secondary.active,.btn-secondary:active,.show>.btn-secondary.dropdown-toggle{color:#fff;background-color:#565e64;border-color:#51585e}.btn-check:active+.btn-secondary:focus,.btn-check:checked+.btn-secondary:focus,.btn-secondary.active:focus,.btn-secondary:active:focus,.show>.btn-secondary.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(130,138,145,.5)}.btn-secondary.disabled,.btn-secondary:disabled{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-success{color:#fff;background-color:#198754;border-color:#198754}.btn-success:hover{color:#fff;background-color:#157347;border-color:#146c43}.btn-check:focus+.btn-success,.btn-success:focus{color:#fff;background-color:#157347;border-color:#146c43;box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-check:active+.btn-success,.btn-check:checked+.btn-success,.btn-success.active,.btn-success:active,.show>.btn-success.dropdown-toggle{color:#fff;background-color:#146c43;border-color:#13653f}.btn-check:active+.btn-success:focus,.btn-check:checked+.btn-success:focus,.btn-success.active:focus,.btn-success:active:focus,.show>.btn-success.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(60,153,110,.5)}.btn-success.disabled,.btn-success:disabled{color:#fff;background-color:#198754;border-color:#198754}.btn-info{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-info:hover{color:#000;background-color:#31d2f2;border-color:#25cff2}.btn-check:focus+.btn-info,.btn-info:focus{color:#000;background-color:#31d2f2;border-color:#25cff2;box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-check:active+.btn-info,.btn-check:checked+.btn-info,.btn-info.active,.btn-info:active,.show>.btn-info.dropdown-toggle{color:#000;background-color:#3dd5f3;border-color:#25cff2}.btn-check:active+.btn-info:focus,.btn-check:checked+.btn-info:focus,.btn-info.active:focus,.btn-info:active:focus,.show>.btn-info.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(11,172,204,.5)}.btn-info.disabled,.btn-info:disabled{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-warning{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-warning:hover{color:#000;background-color:#ffca2c;border-color:#ffc720}.btn-check:focus+.btn-warning,.btn-warning:focus{color:#000;background-color:#ffca2c;border-color:#ffc720;box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-check:active+.btn-warning,.btn-check:checked+.btn-warning,.btn-warning.active,.btn-warning:active,.show>.btn-warning.dropdown-toggle{color:#000;background-color:#ffcd39;border-color:#ffc720}.btn-check:active+.btn-warning:focus,.btn-check:checked+.btn-warning:focus,.btn-warning.active:focus,.btn-warning:active:focus,.show>.btn-warning.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(217,164,6,.5)}.btn-warning.disabled,.btn-warning:disabled{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-danger{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-danger:hover{color:#fff;background-color:#bb2d3b;border-color:#b02a37}.btn-check:focus+.btn-danger,.btn-danger:focus{color:#fff;background-color:#bb2d3b;border-color:#b02a37;box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-check:active+.btn-danger,.btn-check:checked+.btn-danger,.btn-danger.active,.btn-danger:active,.show>.btn-danger.dropdown-toggle{color:#fff;background-color:#b02a37;border-color:#a52834}.btn-check:active+.btn-danger:focus,.btn-check:checked+.btn-danger:focus,.btn-danger.active:focus,.btn-danger:active:focus,.show>.btn-danger.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(225,83,97,.5)}.btn-danger.disabled,.btn-danger:disabled{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-light{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-light:hover{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:focus+.btn-light,.btn-light:focus{color:#000;background-color:#f9fafb;border-color:#f9fafb;box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-check:active+.btn-light,.btn-check:checked+.btn-light,.btn-light.active,.btn-light:active,.show>.btn-light.dropdown-toggle{color:#000;background-color:#f9fafb;border-color:#f9fafb}.btn-check:active+.btn-light:focus,.btn-check:checked+.btn-light:focus,.btn-light.active:focus,.btn-light:active:focus,.show>.btn-light.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(211,212,213,.5)}.btn-light.disabled,.btn-light:disabled{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-dark{color:#fff;background-color:#212529;border-color:#212529}.btn-dark:hover{color:#fff;background-color:#1c1f23;border-color:#1a1e21}.btn-check:focus+.btn-dark,.btn-dark:focus{color:#fff;background-color:#1c1f23;border-color:#1a1e21;box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-check:active+.btn-dark,.btn-check:checked+.btn-dark,.btn-dark.active,.btn-dark:active,.show>.btn-dark.dropdown-toggle{color:#fff;background-color:#1a1e21;border-color:#191c1f}.btn-check:active+.btn-dark:focus,.btn-check:checked+.btn-dark:focus,.btn-dark.active:focus,.btn-dark:active:focus,.show>.btn-dark.dropdown-toggle:focus{box-shadow:0 0 0 .25rem rgba(66,70,73,.5)}.btn-dark.disabled,.btn-dark:disabled{color:#fff;background-color:#212529;border-color:#212529}.btn-outline-primary{color:#0d6efd;border-color:#0d6efd}.btn-outline-primary:hover{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:focus+.btn-outline-primary,.btn-outline-primary:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-check:active+.btn-outline-primary,.btn-check:checked+.btn-outline-primary,.btn-outline-primary.active,.btn-outline-primary.dropdown-toggle.show,.btn-outline-primary:active{color:#fff;background-color:#0d6efd;border-color:#0d6efd}.btn-check:active+.btn-outline-primary:focus,.btn-check:checked+.btn-outline-primary:focus,.btn-outline-primary.active:focus,.btn-outline-primary.dropdown-toggle.show:focus,.btn-outline-primary:active:focus{box-shadow:0 0 0 .25rem rgba(13,110,253,.5)}.btn-outline-primary.disabled,.btn-outline-primary:disabled{color:#0d6efd;background-color:transparent}.btn-outline-secondary{color:#6c757d;border-color:#6c757d}.btn-outline-secondary:hover{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:focus+.btn-outline-secondary,.btn-outline-secondary:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-check:active+.btn-outline-secondary,.btn-check:checked+.btn-outline-secondary,.btn-outline-secondary.active,.btn-outline-secondary.dropdown-toggle.show,.btn-outline-secondary:active{color:#fff;background-color:#6c757d;border-color:#6c757d}.btn-check:active+.btn-outline-secondary:focus,.btn-check:checked+.btn-outline-secondary:focus,.btn-outline-secondary.active:focus,.btn-outline-secondary.dropdown-toggle.show:focus,.btn-outline-secondary:active:focus{box-shadow:0 0 0 .25rem rgba(108,117,125,.5)}.btn-outline-secondary.disabled,.btn-outline-secondary:disabled{color:#6c757d;background-color:transparent}.btn-outline-success{color:#198754;border-color:#198754}.btn-outline-success:hover{color:#fff;background-color:#198754;border-color:#198754}.btn-check:focus+.btn-outline-success,.btn-outline-success:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-check:active+.btn-outline-success,.btn-check:checked+.btn-outline-success,.btn-outline-success.active,.btn-outline-success.dropdown-toggle.show,.btn-outline-success:active{color:#fff;background-color:#198754;border-color:#198754}.btn-check:active+.btn-outline-success:focus,.btn-check:checked+.btn-outline-success:focus,.btn-outline-success.active:focus,.btn-outline-success.dropdown-toggle.show:focus,.btn-outline-success:active:focus{box-shadow:0 0 0 .25rem rgba(25,135,84,.5)}.btn-outline-success.disabled,.btn-outline-success:disabled{color:#198754;background-color:transparent}.btn-outline-info{color:#0dcaf0;border-color:#0dcaf0}.btn-outline-info:hover{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:focus+.btn-outline-info,.btn-outline-info:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-check:active+.btn-outline-info,.btn-check:checked+.btn-outline-info,.btn-outline-info.active,.btn-outline-info.dropdown-toggle.show,.btn-outline-info:active{color:#000;background-color:#0dcaf0;border-color:#0dcaf0}.btn-check:active+.btn-outline-info:focus,.btn-check:checked+.btn-outline-info:focus,.btn-outline-info.active:focus,.btn-outline-info.dropdown-toggle.show:focus,.btn-outline-info:active:focus{box-shadow:0 0 0 .25rem rgba(13,202,240,.5)}.btn-outline-info.disabled,.btn-outline-info:disabled{color:#0dcaf0;background-color:transparent}.btn-outline-warning{color:#ffc107;border-color:#ffc107}.btn-outline-warning:hover{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:focus+.btn-outline-warning,.btn-outline-warning:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-check:active+.btn-outline-warning,.btn-check:checked+.btn-outline-warning,.btn-outline-warning.active,.btn-outline-warning.dropdown-toggle.show,.btn-outline-warning:active{color:#000;background-color:#ffc107;border-color:#ffc107}.btn-check:active+.btn-outline-warning:focus,.btn-check:checked+.btn-outline-warning:focus,.btn-outline-warning.active:focus,.btn-outline-warning.dropdown-toggle.show:focus,.btn-outline-warning:active:focus{box-shadow:0 0 0 .25rem rgba(255,193,7,.5)}.btn-outline-warning.disabled,.btn-outline-warning:disabled{color:#ffc107;background-color:transparent}.btn-outline-danger{color:#dc3545;border-color:#dc3545}.btn-outline-danger:hover{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:focus+.btn-outline-danger,.btn-outline-danger:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-check:active+.btn-outline-danger,.btn-check:checked+.btn-outline-danger,.btn-outline-danger.active,.btn-outline-danger.dropdown-toggle.show,.btn-outline-danger:active{color:#fff;background-color:#dc3545;border-color:#dc3545}.btn-check:active+.btn-outline-danger:focus,.btn-check:checked+.btn-outline-danger:focus,.btn-outline-danger.active:focus,.btn-outline-danger.dropdown-toggle.show:focus,.btn-outline-danger:active:focus{box-shadow:0 0 0 .25rem rgba(220,53,69,.5)}.btn-outline-danger.disabled,.btn-outline-danger:disabled{color:#dc3545;background-color:transparent}.btn-outline-light{color:#f8f9fa;border-color:#f8f9fa}.btn-outline-light:hover{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:focus+.btn-outline-light,.btn-outline-light:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-check:active+.btn-outline-light,.btn-check:checked+.btn-outline-light,.btn-outline-light.active,.btn-outline-light.dropdown-toggle.show,.btn-outline-light:active{color:#000;background-color:#f8f9fa;border-color:#f8f9fa}.btn-check:active+.btn-outline-light:focus,.btn-check:checked+.btn-outline-light:focus,.btn-outline-light.active:focus,.btn-outline-light.dropdown-toggle.show:focus,.btn-outline-light:active:focus{box-shadow:0 0 0 .25rem rgba(248,249,250,.5)}.btn-outline-light.disabled,.btn-outline-light:disabled{color:#f8f9fa;background-color:transparent}.btn-outline-dark{color:#212529;border-color:#212529}.btn-outline-dark:hover{color:#fff;background-color:#212529;border-color:#212529}.btn-check:focus+.btn-outline-dark,.btn-outline-dark:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-check:active+.btn-outline-dark,.btn-check:checked+.btn-outline-dark,.btn-outline-dark.active,.btn-outline-dark.dropdown-toggle.show,.btn-outline-dark:active{color:#fff;background-color:#212529;border-color:#212529}.btn-check:active+.btn-outline-dark:focus,.btn-check:checked+.btn-outline-dark:focus,.btn-outline-dark.active:focus,.btn-outline-dark.dropdown-toggle.show:focus,.btn-outline-dark:active:focus{box-shadow:0 0 0 .25rem rgba(33,37,41,.5)}.btn-outline-dark.disabled,.btn-outline-dark:disabled{color:#212529;background-color:transparent}.btn-link{font-weight:400;color:#0d6efd;text-decoration:underline}.btn-link:hover{color:#0a58ca}.btn-link.disabled,.btn-link:disabled{color:#6c757d}.btn-group-lg>.btn,.btn-lg{padding:.5rem 1rem;font-size:1.25rem;border-radius:.3rem}.btn-group-sm>.btn,.btn-sm{padding:.25rem .5rem;font-size:.875rem;border-radius:.2rem}.fade{transition:opacity .15s linear}@media (prefers-reduced-motion:reduce){.fade{transition:none}}.fade:not(.show){opacity:0}.collapse:not(.show){display:none}.collapsing{height:0;overflow:hidden;transition:height .35s ease}@media (prefers-reduced-motion:reduce){.collapsing{transition:none}}.collapsing.collapse-horizontal{width:0;height:auto;transition:width .35s ease}@media (prefers-reduced-motion:reduce){.collapsing.collapse-horizontal{transition:none}}.dropdown,.dropend,.dropstart,.dropup{position:relative}.dropdown-toggle{white-space:nowrap}.dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid;border-right:.3em solid transparent;border-bottom:0;border-left:.3em solid transparent}.dropdown-toggle:empty::after{margin-left:0}.dropdown-menu{position:absolute;z-index:1000;display:none;min-width:10rem;padding:.5rem 0;margin:0;font-size:1rem;color:#212529;text-align:left;list-style:none;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.15);border-radius:.25rem}.dropdown-menu[data-bs-popper]{top:100%;left:0;margin-top:.125rem}.dropdown-menu-start{--bs-position:start}.dropdown-menu-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-end{--bs-position:end}.dropdown-menu-end[data-bs-popper]{right:0;left:auto}@media (min-width:576px){.dropdown-menu-sm-start{--bs-position:start}.dropdown-menu-sm-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-sm-end{--bs-position:end}.dropdown-menu-sm-end[data-bs-popper]{right:0;left:auto}}@media (min-width:768px){.dropdown-menu-md-start{--bs-position:start}.dropdown-menu-md-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-md-end{--bs-position:end}.dropdown-menu-md-end[data-bs-popper]{right:0;left:auto}}@media (min-width:992px){.dropdown-menu-lg-start{--bs-position:start}.dropdown-menu-lg-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-lg-end{--bs-position:end}.dropdown-menu-lg-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1200px){.dropdown-menu-xl-start{--bs-position:start}.dropdown-menu-xl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xl-end{--bs-position:end}.dropdown-menu-xl-end[data-bs-popper]{right:0;left:auto}}@media (min-width:1400px){.dropdown-menu-xxl-start{--bs-position:start}.dropdown-menu-xxl-start[data-bs-popper]{right:auto;left:0}.dropdown-menu-xxl-end{--bs-position:end}.dropdown-menu-xxl-end[data-bs-popper]{right:0;left:auto}}.dropup .dropdown-menu[data-bs-popper]{top:auto;bottom:100%;margin-top:0;margin-bottom:.125rem}.dropup .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:0;border-right:.3em solid transparent;border-bottom:.3em solid;border-left:.3em solid transparent}.dropup .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-menu[data-bs-popper]{top:0;right:auto;left:100%;margin-top:0;margin-left:.125rem}.dropend .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:0;border-bottom:.3em solid transparent;border-left:.3em solid}.dropend .dropdown-toggle:empty::after{margin-left:0}.dropend .dropdown-toggle::after{vertical-align:0}.dropstart .dropdown-menu[data-bs-popper]{top:0;right:100%;left:auto;margin-top:0;margin-right:.125rem}.dropstart .dropdown-toggle::after{display:inline-block;margin-left:.255em;vertical-align:.255em;content:""}.dropstart .dropdown-toggle::after{display:none}.dropstart .dropdown-toggle::before{display:inline-block;margin-right:.255em;vertical-align:.255em;content:"";border-top:.3em solid transparent;border-right:.3em solid;border-bottom:.3em solid transparent}.dropstart .dropdown-toggle:empty::after{margin-left:0}.dropstart .dropdown-toggle::before{vertical-align:0}.dropdown-divider{height:0;margin:.5rem 0;overflow:hidden;border-top:1px solid rgba(0,0,0,.15)}.dropdown-item{display:block;width:100%;padding:.25rem 1rem;clear:both;font-weight:400;color:#212529;text-align:inherit;text-decoration:none;white-space:nowrap;background-color:transparent;border:0}.dropdown-item:focus,.dropdown-item:hover{color:#1e2125;background-color:#e9ecef}.dropdown-item.active,.dropdown-item:active{color:#fff;text-decoration:none;background-color:#0d6efd}.dropdown-item.disabled,.dropdown-item:disabled{color:#adb5bd;pointer-events:none;background-color:transparent}.dropdown-menu.show{display:block}.dropdown-header{display:block;padding:.5rem 1rem;margin-bottom:0;font-size:.875rem;color:#6c757d;white-space:nowrap}.dropdown-item-text{display:block;padding:.25rem 1rem;color:#212529}.dropdown-menu-dark{color:#dee2e6;background-color:#343a40;border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item{color:#dee2e6}.dropdown-menu-dark .dropdown-item:focus,.dropdown-menu-dark .dropdown-item:hover{color:#fff;background-color:rgba(255,255,255,.15)}.dropdown-menu-dark .dropdown-item.active,.dropdown-menu-dark .dropdown-item:active{color:#fff;background-color:#0d6efd}.dropdown-menu-dark .dropdown-item.disabled,.dropdown-menu-dark .dropdown-item:disabled{color:#adb5bd}.dropdown-menu-dark .dropdown-divider{border-color:rgba(0,0,0,.15)}.dropdown-menu-dark .dropdown-item-text{color:#dee2e6}.dropdown-menu-dark .dropdown-header{color:#adb5bd}.btn-group,.btn-group-vertical{position:relative;display:inline-flex;vertical-align:middle}.btn-group-vertical>.btn,.btn-group>.btn{position:relative;flex:1 1 auto}.btn-group-vertical>.btn-check:checked+.btn,.btn-group-vertical>.btn-check:focus+.btn,.btn-group-vertical>.btn.active,.btn-group-vertical>.btn:active,.btn-group-vertical>.btn:focus,.btn-group-vertical>.btn:hover,.btn-group>.btn-check:checked+.btn,.btn-group>.btn-check:focus+.btn,.btn-group>.btn.active,.btn-group>.btn:active,.btn-group>.btn:focus,.btn-group>.btn:hover{z-index:1}.btn-toolbar{display:flex;flex-wrap:wrap;justify-content:flex-start}.btn-toolbar .input-group{width:auto}.btn-group>.btn-group:not(:first-child),.btn-group>.btn:not(:first-child){margin-left:-1px}.btn-group>.btn-group:not(:last-child)>.btn,.btn-group>.btn:not(:last-child):not(.dropdown-toggle){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group>.btn-group:not(:first-child)>.btn,.btn-group>.btn:nth-child(n+3),.btn-group>:not(.btn-check)+.btn{border-top-left-radius:0;border-bottom-left-radius:0}.dropdown-toggle-split{padding-right:.5625rem;padding-left:.5625rem}.dropdown-toggle-split::after,.dropend .dropdown-toggle-split::after,.dropup .dropdown-toggle-split::after{margin-left:0}.dropstart .dropdown-toggle-split::before{margin-right:0}.btn-group-sm>.btn+.dropdown-toggle-split,.btn-sm+.dropdown-toggle-split{padding-right:.375rem;padding-left:.375rem}.btn-group-lg>.btn+.dropdown-toggle-split,.btn-lg+.dropdown-toggle-split{padding-right:.75rem;padding-left:.75rem}.btn-group-vertical{flex-direction:column;align-items:flex-start;justify-content:center}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group{width:100%}.btn-group-vertical>.btn-group:not(:first-child),.btn-group-vertical>.btn:not(:first-child){margin-top:-1px}.btn-group-vertical>.btn-group:not(:last-child)>.btn,.btn-group-vertical>.btn:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child)>.btn,.btn-group-vertical>.btn~.btn{border-top-left-radius:0;border-top-right-radius:0}.nav{display:flex;flex-wrap:wrap;padding-left:0;margin-bottom:0;list-style:none}.nav-link{display:block;padding:.5rem 1rem;color:#0d6efd;text-decoration:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out}@media (prefers-reduced-motion:reduce){.nav-link{transition:none}}.nav-link:focus,.nav-link:hover{color:#0a58ca}.nav-link.disabled{color:#6c757d;pointer-events:none;cursor:default}.nav-tabs{border-bottom:1px solid #dee2e6}.nav-tabs .nav-link{margin-bottom:-1px;background:0 0;border:1px solid transparent;border-top-left-radius:.25rem;border-top-right-radius:.25rem}.nav-tabs .nav-link:focus,.nav-tabs .nav-link:hover{border-color:#e9ecef #e9ecef #dee2e6;isolation:isolate}.nav-tabs .nav-link.disabled{color:#6c757d;background-color:transparent;border-color:transparent}.nav-tabs .nav-item.show .nav-link,.nav-tabs .nav-link.active{color:#495057;background-color:#fff;border-color:#dee2e6 #dee2e6 #fff}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-left-radius:0;border-top-right-radius:0}.nav-pills .nav-link{background:0 0;border:0;border-radius:.25rem}.nav-pills .nav-link.active,.nav-pills .show>.nav-link{color:#fff;background-color:#0d6efd}.nav-fill .nav-item,.nav-fill>.nav-link{flex:1 1 auto;text-align:center}.nav-justified .nav-item,.nav-justified>.nav-link{flex-basis:0;flex-grow:1;text-align:center}.nav-fill .nav-item .nav-link,.nav-justified .nav-item .nav-link{width:100%}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.navbar{position:relative;display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;padding-top:.5rem;padding-bottom:.5rem}.navbar>.container,.navbar>.container-fluid,.navbar>.container-lg,.navbar>.container-md,.navbar>.container-sm,.navbar>.container-xl,.navbar>.container-xxl{display:flex;flex-wrap:inherit;align-items:center;justify-content:space-between}.navbar-brand{padding-top:.3125rem;padding-bottom:.3125rem;margin-right:1rem;font-size:1.25rem;text-decoration:none;white-space:nowrap}.navbar-nav{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;list-style:none}.navbar-nav .nav-link{padding-right:0;padding-left:0}.navbar-nav .dropdown-menu{position:static}.navbar-text{padding-top:.5rem;padding-bottom:.5rem}.navbar-collapse{flex-basis:100%;flex-grow:1;align-items:center}.navbar-toggler{padding:.25rem .75rem;font-size:1.25rem;line-height:1;background-color:transparent;border:1px solid transparent;border-radius:.25rem;transition:box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.navbar-toggler{transition:none}}.navbar-toggler:hover{text-decoration:none}.navbar-toggler:focus{text-decoration:none;outline:0;box-shadow:0 0 0 .25rem}.navbar-toggler-icon{display:inline-block;width:1.5em;height:1.5em;vertical-align:middle;background-repeat:no-repeat;background-position:center;background-size:100%}.navbar-nav-scroll{max-height:var(--bs-scroll-height,75vh);overflow-y:auto}@media (min-width:576px){.navbar-expand-sm{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-sm .navbar-nav{flex-direction:row}.navbar-expand-sm .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-sm .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-sm .navbar-nav-scroll{overflow:visible}.navbar-expand-sm .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-sm .navbar-toggler{display:none}.navbar-expand-sm .offcanvas-header{display:none}.navbar-expand-sm .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-sm .offcanvas-bottom,.navbar-expand-sm .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-sm .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:768px){.navbar-expand-md{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-md .navbar-nav{flex-direction:row}.navbar-expand-md .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-md .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-md .navbar-nav-scroll{overflow:visible}.navbar-expand-md .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-md .navbar-toggler{display:none}.navbar-expand-md .offcanvas-header{display:none}.navbar-expand-md .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-md .offcanvas-bottom,.navbar-expand-md .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-md .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:992px){.navbar-expand-lg{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-lg .navbar-nav{flex-direction:row}.navbar-expand-lg .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-lg .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-lg .navbar-nav-scroll{overflow:visible}.navbar-expand-lg .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-lg .navbar-toggler{display:none}.navbar-expand-lg .offcanvas-header{display:none}.navbar-expand-lg .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-lg .offcanvas-bottom,.navbar-expand-lg .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-lg .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1200px){.navbar-expand-xl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xl .navbar-nav{flex-direction:row}.navbar-expand-xl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xl .navbar-nav-scroll{overflow:visible}.navbar-expand-xl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xl .navbar-toggler{display:none}.navbar-expand-xl .offcanvas-header{display:none}.navbar-expand-xl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xl .offcanvas-bottom,.navbar-expand-xl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}@media (min-width:1400px){.navbar-expand-xxl{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand-xxl .navbar-nav{flex-direction:row}.navbar-expand-xxl .navbar-nav .dropdown-menu{position:absolute}.navbar-expand-xxl .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand-xxl .navbar-nav-scroll{overflow:visible}.navbar-expand-xxl .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand-xxl .navbar-toggler{display:none}.navbar-expand-xxl .offcanvas-header{display:none}.navbar-expand-xxl .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand-xxl .offcanvas-bottom,.navbar-expand-xxl .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand-xxl .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}}.navbar-expand{flex-wrap:nowrap;justify-content:flex-start}.navbar-expand .navbar-nav{flex-direction:row}.navbar-expand .navbar-nav .dropdown-menu{position:absolute}.navbar-expand .navbar-nav .nav-link{padding-right:.5rem;padding-left:.5rem}.navbar-expand .navbar-nav-scroll{overflow:visible}.navbar-expand .navbar-collapse{display:flex!important;flex-basis:auto}.navbar-expand .navbar-toggler{display:none}.navbar-expand .offcanvas-header{display:none}.navbar-expand .offcanvas{position:inherit;bottom:0;z-index:1000;flex-grow:1;visibility:visible!important;background-color:transparent;border-right:0;border-left:0;transition:none;transform:none}.navbar-expand .offcanvas-bottom,.navbar-expand .offcanvas-top{height:auto;border-top:0;border-bottom:0}.navbar-expand .offcanvas-body{display:flex;flex-grow:0;padding:0;overflow-y:visible}.navbar-light .navbar-brand{color:rgba(0,0,0,.9)}.navbar-light .navbar-brand:focus,.navbar-light .navbar-brand:hover{color:rgba(0,0,0,.9)}.navbar-light .navbar-nav .nav-link{color:rgba(0,0,0,.55)}.navbar-light .navbar-nav .nav-link:focus,.navbar-light .navbar-nav .nav-link:hover{color:rgba(0,0,0,.7)}.navbar-light .navbar-nav .nav-link.disabled{color:rgba(0,0,0,.3)}.navbar-light .navbar-nav .nav-link.active,.navbar-light .navbar-nav .show>.nav-link{color:rgba(0,0,0,.9)}.navbar-light .navbar-toggler{color:rgba(0,0,0,.55);border-color:rgba(0,0,0,.1)}.navbar-light .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%280, 0, 0, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-light .navbar-text{color:rgba(0,0,0,.55)}.navbar-light .navbar-text a,.navbar-light .navbar-text a:focus,.navbar-light .navbar-text a:hover{color:rgba(0,0,0,.9)}.navbar-dark .navbar-brand{color:#fff}.navbar-dark .navbar-brand:focus,.navbar-dark .navbar-brand:hover{color:#fff}.navbar-dark .navbar-nav .nav-link{color:rgba(255,255,255,.55)}.navbar-dark .navbar-nav .nav-link:focus,.navbar-dark .navbar-nav .nav-link:hover{color:rgba(255,255,255,.75)}.navbar-dark .navbar-nav .nav-link.disabled{color:rgba(255,255,255,.25)}.navbar-dark .navbar-nav .nav-link.active,.navbar-dark .navbar-nav .show>.nav-link{color:#fff}.navbar-dark .navbar-toggler{color:rgba(255,255,255,.55);border-color:rgba(255,255,255,.1)}.navbar-dark .navbar-toggler-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.55%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e")}.navbar-dark .navbar-text{color:rgba(255,255,255,.55)}.navbar-dark .navbar-text a,.navbar-dark .navbar-text a:focus,.navbar-dark .navbar-text a:hover{color:#fff}.card{position:relative;display:flex;flex-direction:column;min-width:0;word-wrap:break-word;background-color:#fff;background-clip:border-box;border:1px solid rgba(0,0,0,.125);border-radius:.25rem}.card>hr{margin-right:0;margin-left:0}.card>.list-group{border-top:inherit;border-bottom:inherit}.card>.list-group:first-child{border-top-width:0;border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card>.list-group:last-child{border-bottom-width:0;border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card>.card-header+.list-group,.card>.list-group+.card-footer{border-top:0}.card-body{flex:1 1 auto;padding:1rem 1rem}.card-title{margin-bottom:.5rem}.card-subtitle{margin-top:-.25rem;margin-bottom:0}.card-text:last-child{margin-bottom:0}.card-link+.card-link{margin-left:1rem}.card-header{padding:.5rem 1rem;margin-bottom:0;background-color:rgba(0,0,0,.03);border-bottom:1px solid rgba(0,0,0,.125)}.card-header:first-child{border-radius:calc(.25rem - 1px) calc(.25rem - 1px) 0 0}.card-footer{padding:.5rem 1rem;background-color:rgba(0,0,0,.03);border-top:1px solid rgba(0,0,0,.125)}.card-footer:last-child{border-radius:0 0 calc(.25rem - 1px) calc(.25rem - 1px)}.card-header-tabs{margin-right:-.5rem;margin-bottom:-.5rem;margin-left:-.5rem;border-bottom:0}.card-header-pills{margin-right:-.5rem;margin-left:-.5rem}.card-img-overlay{position:absolute;top:0;right:0;bottom:0;left:0;padding:1rem;border-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom,.card-img-top{width:100%}.card-img,.card-img-top{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.card-img,.card-img-bottom{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.card-group>.card{margin-bottom:.75rem}@media (min-width:576px){.card-group{display:flex;flex-flow:row wrap}.card-group>.card{flex:1 0 0%;margin-bottom:0}.card-group>.card+.card{margin-left:0;border-left:0}.card-group>.card:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.card-group>.card:not(:last-child) .card-header,.card-group>.card:not(:last-child) .card-img-top{border-top-right-radius:0}.card-group>.card:not(:last-child) .card-footer,.card-group>.card:not(:last-child) .card-img-bottom{border-bottom-right-radius:0}.card-group>.card:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.card-group>.card:not(:first-child) .card-header,.card-group>.card:not(:first-child) .card-img-top{border-top-left-radius:0}.card-group>.card:not(:first-child) .card-footer,.card-group>.card:not(:first-child) .card-img-bottom{border-bottom-left-radius:0}}.accordion-button{position:relative;display:flex;align-items:center;width:100%;padding:1rem 1.25rem;font-size:1rem;color:#212529;text-align:left;background-color:#fff;border:0;border-radius:0;overflow-anchor:none;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out,border-radius .15s ease}@media (prefers-reduced-motion:reduce){.accordion-button{transition:none}}.accordion-button:not(.collapsed){color:#0c63e4;background-color:#e7f1ff;box-shadow:inset 0 -1px 0 rgba(0,0,0,.125)}.accordion-button:not(.collapsed)::after{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%230c63e4'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");transform:rotate(-180deg)}.accordion-button::after{flex-shrink:0;width:1.25rem;height:1.25rem;margin-left:auto;content:"";background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23212529'%3e%3cpath fill-rule='evenodd' d='M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e");background-repeat:no-repeat;background-size:1.25rem;transition:transform .2s ease-in-out}@media (prefers-reduced-motion:reduce){.accordion-button::after{transition:none}}.accordion-button:hover{z-index:2}.accordion-button:focus{z-index:3;border-color:#86b7fe;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.accordion-header{margin-bottom:0}.accordion-item{background-color:#fff;border:1px solid rgba(0,0,0,.125)}.accordion-item:first-of-type{border-top-left-radius:.25rem;border-top-right-radius:.25rem}.accordion-item:first-of-type .accordion-button{border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.accordion-item:not(:first-of-type){border-top:0}.accordion-item:last-of-type{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-item:last-of-type .accordion-button.collapsed{border-bottom-right-radius:calc(.25rem - 1px);border-bottom-left-radius:calc(.25rem - 1px)}.accordion-item:last-of-type .accordion-collapse{border-bottom-right-radius:.25rem;border-bottom-left-radius:.25rem}.accordion-body{padding:1rem 1.25rem}.accordion-flush .accordion-collapse{border-width:0}.accordion-flush .accordion-item{border-right:0;border-left:0;border-radius:0}.accordion-flush .accordion-item:first-child{border-top:0}.accordion-flush .accordion-item:last-child{border-bottom:0}.accordion-flush .accordion-item .accordion-button{border-radius:0}.breadcrumb{display:flex;flex-wrap:wrap;padding:0 0;margin-bottom:1rem;list-style:none}.breadcrumb-item+.breadcrumb-item{padding-left:.5rem}.breadcrumb-item+.breadcrumb-item::before{float:left;padding-right:.5rem;color:#6c757d;content:var(--bs-breadcrumb-divider, "/")}.breadcrumb-item.active{color:#6c757d}.pagination{display:flex;padding-left:0;list-style:none}.page-link{position:relative;display:block;color:#0d6efd;text-decoration:none;background-color:#fff;border:1px solid #dee2e6;transition:color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out}@media (prefers-reduced-motion:reduce){.page-link{transition:none}}.page-link:hover{z-index:2;color:#0a58ca;background-color:#e9ecef;border-color:#dee2e6}.page-link:focus{z-index:3;color:#0a58ca;background-color:#e9ecef;outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25)}.page-item:not(:first-child) .page-link{margin-left:-1px}.page-item.active .page-link{z-index:3;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.page-item.disabled .page-link{color:#6c757d;pointer-events:none;background-color:#fff;border-color:#dee2e6}.page-link{padding:.375rem .75rem}.page-item:first-child .page-link{border-top-left-radius:.25rem;border-bottom-left-radius:.25rem}.page-item:last-child .page-link{border-top-right-radius:.25rem;border-bottom-right-radius:.25rem}.pagination-lg .page-link{padding:.75rem 1.5rem;font-size:1.25rem}.pagination-lg .page-item:first-child .page-link{border-top-left-radius:.3rem;border-bottom-left-radius:.3rem}.pagination-lg .page-item:last-child .page-link{border-top-right-radius:.3rem;border-bottom-right-radius:.3rem}.pagination-sm .page-link{padding:.25rem .5rem;font-size:.875rem}.pagination-sm .page-item:first-child .page-link{border-top-left-radius:.2rem;border-bottom-left-radius:.2rem}.pagination-sm .page-item:last-child .page-link{border-top-right-radius:.2rem;border-bottom-right-radius:.2rem}.badge{display:inline-block;padding:.35em .65em;font-size:.75em;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25rem}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.alert{position:relative;padding:1rem 1rem;margin-bottom:1rem;border:1px solid transparent;border-radius:.25rem}.alert-heading{color:inherit}.alert-link{font-weight:700}.alert-dismissible{padding-right:3rem}.alert-dismissible .btn-close{position:absolute;top:0;right:0;z-index:2;padding:1.25rem 1rem}.alert-primary{color:#084298;background-color:#cfe2ff;border-color:#b6d4fe}.alert-primary .alert-link{color:#06357a}.alert-secondary{color:#41464b;background-color:#e2e3e5;border-color:#d3d6d8}.alert-secondary .alert-link{color:#34383c}.alert-success{color:#0f5132;background-color:#d1e7dd;border-color:#badbcc}.alert-success .alert-link{color:#0c4128}.alert-info{color:#055160;background-color:#cff4fc;border-color:#b6effb}.alert-info .alert-link{color:#04414d}.alert-warning{color:#664d03;background-color:#fff3cd;border-color:#ffecb5}.alert-warning .alert-link{color:#523e02}.alert-danger{color:#842029;background-color:#f8d7da;border-color:#f5c2c7}.alert-danger .alert-link{color:#6a1a21}.alert-light{color:#636464;background-color:#fefefe;border-color:#fdfdfe}.alert-light .alert-link{color:#4f5050}.alert-dark{color:#141619;background-color:#d3d3d4;border-color:#bcbebf}.alert-dark .alert-link{color:#101214}@-webkit-keyframes progress-bar-stripes{0%{background-position-x:1rem}}@keyframes progress-bar-stripes{0%{background-position-x:1rem}}.progress{display:flex;height:1rem;overflow:hidden;font-size:.75rem;background-color:#e9ecef;border-radius:.25rem}.progress-bar{display:flex;flex-direction:column;justify-content:center;overflow:hidden;color:#fff;text-align:center;white-space:nowrap;background-color:#0d6efd;transition:width .6s ease}@media (prefers-reduced-motion:reduce){.progress-bar{transition:none}}.progress-bar-striped{background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:1rem 1rem}.progress-bar-animated{-webkit-animation:1s linear infinite progress-bar-stripes;animation:1s linear infinite progress-bar-stripes}@media (prefers-reduced-motion:reduce){.progress-bar-animated{-webkit-animation:none;animation:none}}.list-group{display:flex;flex-direction:column;padding-left:0;margin-bottom:0;border-radius:.25rem}.list-group-numbered{list-style-type:none;counter-reset:section}.list-group-numbered>li::before{content:counters(section, ".") ". ";counter-increment:section}.list-group-item-action{width:100%;color:#495057;text-align:inherit}.list-group-item-action:focus,.list-group-item-action:hover{z-index:1;color:#495057;text-decoration:none;background-color:#f8f9fa}.list-group-item-action:active{color:#212529;background-color:#e9ecef}.list-group-item{position:relative;display:block;padding:.5rem 1rem;color:#212529;text-decoration:none;background-color:#fff;border:1px solid rgba(0,0,0,.125)}.list-group-item:first-child{border-top-left-radius:inherit;border-top-right-radius:inherit}.list-group-item:last-child{border-bottom-right-radius:inherit;border-bottom-left-radius:inherit}.list-group-item.disabled,.list-group-item:disabled{color:#6c757d;pointer-events:none;background-color:#fff}.list-group-item.active{z-index:2;color:#fff;background-color:#0d6efd;border-color:#0d6efd}.list-group-item+.list-group-item{border-top-width:0}.list-group-item+.list-group-item.active{margin-top:-1px;border-top-width:1px}.list-group-horizontal{flex-direction:row}.list-group-horizontal>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal>.list-group-item.active{margin-top:0}.list-group-horizontal>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}@media (min-width:576px){.list-group-horizontal-sm{flex-direction:row}.list-group-horizontal-sm>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-sm>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-sm>.list-group-item.active{margin-top:0}.list-group-horizontal-sm>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-sm>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:768px){.list-group-horizontal-md{flex-direction:row}.list-group-horizontal-md>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-md>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-md>.list-group-item.active{margin-top:0}.list-group-horizontal-md>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-md>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:992px){.list-group-horizontal-lg{flex-direction:row}.list-group-horizontal-lg>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-lg>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-lg>.list-group-item.active{margin-top:0}.list-group-horizontal-lg>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-lg>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1200px){.list-group-horizontal-xl{flex-direction:row}.list-group-horizontal-xl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xl>.list-group-item.active{margin-top:0}.list-group-horizontal-xl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}@media (min-width:1400px){.list-group-horizontal-xxl{flex-direction:row}.list-group-horizontal-xxl>.list-group-item:first-child{border-bottom-left-radius:.25rem;border-top-right-radius:0}.list-group-horizontal-xxl>.list-group-item:last-child{border-top-right-radius:.25rem;border-bottom-left-radius:0}.list-group-horizontal-xxl>.list-group-item.active{margin-top:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item{border-top-width:1px;border-left-width:0}.list-group-horizontal-xxl>.list-group-item+.list-group-item.active{margin-left:-1px;border-left-width:1px}}.list-group-flush{border-radius:0}.list-group-flush>.list-group-item{border-width:0 0 1px}.list-group-flush>.list-group-item:last-child{border-bottom-width:0}.list-group-item-primary{color:#084298;background-color:#cfe2ff}.list-group-item-primary.list-group-item-action:focus,.list-group-item-primary.list-group-item-action:hover{color:#084298;background-color:#bacbe6}.list-group-item-primary.list-group-item-action.active{color:#fff;background-color:#084298;border-color:#084298}.list-group-item-secondary{color:#41464b;background-color:#e2e3e5}.list-group-item-secondary.list-group-item-action:focus,.list-group-item-secondary.list-group-item-action:hover{color:#41464b;background-color:#cbccce}.list-group-item-secondary.list-group-item-action.active{color:#fff;background-color:#41464b;border-color:#41464b}.list-group-item-success{color:#0f5132;background-color:#d1e7dd}.list-group-item-success.list-group-item-action:focus,.list-group-item-success.list-group-item-action:hover{color:#0f5132;background-color:#bcd0c7}.list-group-item-success.list-group-item-action.active{color:#fff;background-color:#0f5132;border-color:#0f5132}.list-group-item-info{color:#055160;background-color:#cff4fc}.list-group-item-info.list-group-item-action:focus,.list-group-item-info.list-group-item-action:hover{color:#055160;background-color:#badce3}.list-group-item-info.list-group-item-action.active{color:#fff;background-color:#055160;border-color:#055160}.list-group-item-warning{color:#664d03;background-color:#fff3cd}.list-group-item-warning.list-group-item-action:focus,.list-group-item-warning.list-group-item-action:hover{color:#664d03;background-color:#e6dbb9}.list-group-item-warning.list-group-item-action.active{color:#fff;background-color:#664d03;border-color:#664d03}.list-group-item-danger{color:#842029;background-color:#f8d7da}.list-group-item-danger.list-group-item-action:focus,.list-group-item-danger.list-group-item-action:hover{color:#842029;background-color:#dfc2c4}.list-group-item-danger.list-group-item-action.active{color:#fff;background-color:#842029;border-color:#842029}.list-group-item-light{color:#636464;background-color:#fefefe}.list-group-item-light.list-group-item-action:focus,.list-group-item-light.list-group-item-action:hover{color:#636464;background-color:#e5e5e5}.list-group-item-light.list-group-item-action.active{color:#fff;background-color:#636464;border-color:#636464}.list-group-item-dark{color:#141619;background-color:#d3d3d4}.list-group-item-dark.list-group-item-action:focus,.list-group-item-dark.list-group-item-action:hover{color:#141619;background-color:#bebebf}.list-group-item-dark.list-group-item-action.active{color:#fff;background-color:#141619;border-color:#141619}.btn-close{box-sizing:content-box;width:1em;height:1em;padding:.25em .25em;color:#000;background:transparent url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23000'%3e%3cpath d='M.293.293a1 1 0 011.414 0L8 6.586 14.293.293a1 1 0 111.414 1.414L9.414 8l6.293 6.293a1 1 0 01-1.414 1.414L8 9.414l-6.293 6.293a1 1 0 01-1.414-1.414L6.586 8 .293 1.707a1 1 0 010-1.414z'/%3e%3c/svg%3e") center/1em auto no-repeat;border:0;border-radius:.25rem;opacity:.5}.btn-close:hover{color:#000;text-decoration:none;opacity:.75}.btn-close:focus{outline:0;box-shadow:0 0 0 .25rem rgba(13,110,253,.25);opacity:1}.btn-close.disabled,.btn-close:disabled{pointer-events:none;-webkit-user-select:none;-moz-user-select:none;user-select:none;opacity:.25}.btn-close-white{filter:invert(1) grayscale(100%) brightness(200%)}.toast{width:350px;max-width:100%;font-size:.875rem;pointer-events:auto;background-color:rgba(255,255,255,.85);background-clip:padding-box;border:1px solid rgba(0,0,0,.1);box-shadow:0 .5rem 1rem rgba(0,0,0,.15);border-radius:.25rem}.toast.showing{opacity:0}.toast:not(.show){display:none}.toast-container{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:100%;pointer-events:none}.toast-container>:not(:last-child){margin-bottom:.75rem}.toast-header{display:flex;align-items:center;padding:.5rem .75rem;color:#6c757d;background-color:rgba(255,255,255,.85);background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,.05);border-top-left-radius:calc(.25rem - 1px);border-top-right-radius:calc(.25rem - 1px)}.toast-header .btn-close{margin-right:-.375rem;margin-left:.75rem}.toast-body{padding:.75rem;word-wrap:break-word}.modal{position:fixed;top:0;left:0;z-index:1055;display:none;width:100%;height:100%;overflow-x:hidden;overflow-y:auto;outline:0}.modal-dialog{position:relative;width:auto;margin:.5rem;pointer-events:none}.modal.fade .modal-dialog{transition:transform .3s ease-out;transform:translate(0,-50px)}@media (prefers-reduced-motion:reduce){.modal.fade .modal-dialog{transition:none}}.modal.show .modal-dialog{transform:none}.modal.modal-static .modal-dialog{transform:scale(1.02)}.modal-dialog-scrollable{height:calc(100% - 1rem)}.modal-dialog-scrollable .modal-content{max-height:100%;overflow:hidden}.modal-dialog-scrollable .modal-body{overflow-y:auto}.modal-dialog-centered{display:flex;align-items:center;min-height:calc(100% - 1rem)}.modal-content{position:relative;display:flex;flex-direction:column;width:100%;pointer-events:auto;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem;outline:0}.modal-backdrop{position:fixed;top:0;left:0;z-index:1050;width:100vw;height:100vh;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop.show{opacity:.5}.modal-header{display:flex;flex-shrink:0;align-items:center;justify-content:space-between;padding:1rem 1rem;border-bottom:1px solid #dee2e6;border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.modal-header .btn-close{padding:.5rem .5rem;margin:-.5rem -.5rem -.5rem auto}.modal-title{margin-bottom:0;line-height:1.5}.modal-body{position:relative;flex:1 1 auto;padding:1rem}.modal-footer{display:flex;flex-wrap:wrap;flex-shrink:0;align-items:center;justify-content:flex-end;padding:.75rem;border-top:1px solid #dee2e6;border-bottom-right-radius:calc(.3rem - 1px);border-bottom-left-radius:calc(.3rem - 1px)}.modal-footer>*{margin:.25rem}@media (min-width:576px){.modal-dialog{max-width:500px;margin:1.75rem auto}.modal-dialog-scrollable{height:calc(100% - 3.5rem)}.modal-dialog-centered{min-height:calc(100% - 3.5rem)}.modal-sm{max-width:300px}}@media (min-width:992px){.modal-lg,.modal-xl{max-width:800px}}@media (min-width:1200px){.modal-xl{max-width:1140px}}.modal-fullscreen{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen .modal-header{border-radius:0}.modal-fullscreen .modal-body{overflow-y:auto}.modal-fullscreen .modal-footer{border-radius:0}@media (max-width:575.98px){.modal-fullscreen-sm-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-sm-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-sm-down .modal-header{border-radius:0}.modal-fullscreen-sm-down .modal-body{overflow-y:auto}.modal-fullscreen-sm-down .modal-footer{border-radius:0}}@media (max-width:767.98px){.modal-fullscreen-md-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-md-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-md-down .modal-header{border-radius:0}.modal-fullscreen-md-down .modal-body{overflow-y:auto}.modal-fullscreen-md-down .modal-footer{border-radius:0}}@media (max-width:991.98px){.modal-fullscreen-lg-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-lg-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-lg-down .modal-header{border-radius:0}.modal-fullscreen-lg-down .modal-body{overflow-y:auto}.modal-fullscreen-lg-down .modal-footer{border-radius:0}}@media (max-width:1199.98px){.modal-fullscreen-xl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xl-down .modal-header{border-radius:0}.modal-fullscreen-xl-down .modal-body{overflow-y:auto}.modal-fullscreen-xl-down .modal-footer{border-radius:0}}@media (max-width:1399.98px){.modal-fullscreen-xxl-down{width:100vw;max-width:none;height:100%;margin:0}.modal-fullscreen-xxl-down .modal-content{height:100%;border:0;border-radius:0}.modal-fullscreen-xxl-down .modal-header{border-radius:0}.modal-fullscreen-xxl-down .modal-body{overflow-y:auto}.modal-fullscreen-xxl-down .modal-footer{border-radius:0}}.tooltip{position:absolute;z-index:1080;display:block;margin:0;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;opacity:0}.tooltip.show{opacity:.9}.tooltip .tooltip-arrow{position:absolute;display:block;width:.8rem;height:.4rem}.tooltip .tooltip-arrow::before{position:absolute;content:"";border-color:transparent;border-style:solid}.bs-tooltip-auto[data-popper-placement^=top],.bs-tooltip-top{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow,.bs-tooltip-top .tooltip-arrow{bottom:0}.bs-tooltip-auto[data-popper-placement^=top] .tooltip-arrow::before,.bs-tooltip-top .tooltip-arrow::before{top:-1px;border-width:.4rem .4rem 0;border-top-color:#000}.bs-tooltip-auto[data-popper-placement^=right],.bs-tooltip-end{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow,.bs-tooltip-end .tooltip-arrow{left:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=right] .tooltip-arrow::before,.bs-tooltip-end .tooltip-arrow::before{right:-1px;border-width:.4rem .4rem .4rem 0;border-right-color:#000}.bs-tooltip-auto[data-popper-placement^=bottom],.bs-tooltip-bottom{padding:.4rem 0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow,.bs-tooltip-bottom .tooltip-arrow{top:0}.bs-tooltip-auto[data-popper-placement^=bottom] .tooltip-arrow::before,.bs-tooltip-bottom .tooltip-arrow::before{bottom:-1px;border-width:0 .4rem .4rem;border-bottom-color:#000}.bs-tooltip-auto[data-popper-placement^=left],.bs-tooltip-start{padding:0 .4rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow,.bs-tooltip-start .tooltip-arrow{right:0;width:.4rem;height:.8rem}.bs-tooltip-auto[data-popper-placement^=left] .tooltip-arrow::before,.bs-tooltip-start .tooltip-arrow::before{left:-1px;border-width:.4rem 0 .4rem .4rem;border-left-color:#000}.tooltip-inner{max-width:200px;padding:.25rem .5rem;color:#fff;text-align:center;background-color:#000;border-radius:.25rem}.popover{position:absolute;top:0;left:0;z-index:1070;display:block;max-width:276px;font-family:var(--bs-font-sans-serif);font-style:normal;font-weight:400;line-height:1.5;text-align:left;text-align:start;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-break:normal;word-spacing:normal;white-space:normal;line-break:auto;font-size:.875rem;word-wrap:break-word;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,.2);border-radius:.3rem}.popover .popover-arrow{position:absolute;display:block;width:1rem;height:.5rem}.popover .popover-arrow::after,.popover .popover-arrow::before{position:absolute;display:block;content:"";border-color:transparent;border-style:solid}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow,.bs-popover-top>.popover-arrow{bottom:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::before,.bs-popover-top>.popover-arrow::before{bottom:0;border-width:.5rem .5rem 0;border-top-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=top]>.popover-arrow::after,.bs-popover-top>.popover-arrow::after{bottom:1px;border-width:.5rem .5rem 0;border-top-color:#fff}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow,.bs-popover-end>.popover-arrow{left:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::before,.bs-popover-end>.popover-arrow::before{left:0;border-width:.5rem .5rem .5rem 0;border-right-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=right]>.popover-arrow::after,.bs-popover-end>.popover-arrow::after{left:1px;border-width:.5rem .5rem .5rem 0;border-right-color:#fff}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow,.bs-popover-bottom>.popover-arrow{top:calc(-.5rem - 1px)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::before,.bs-popover-bottom>.popover-arrow::before{top:0;border-width:0 .5rem .5rem .5rem;border-bottom-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=bottom]>.popover-arrow::after,.bs-popover-bottom>.popover-arrow::after{top:1px;border-width:0 .5rem .5rem .5rem;border-bottom-color:#fff}.bs-popover-auto[data-popper-placement^=bottom] .popover-header::before,.bs-popover-bottom .popover-header::before{position:absolute;top:0;left:50%;display:block;width:1rem;margin-left:-.5rem;content:"";border-bottom:1px solid #f0f0f0}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow,.bs-popover-start>.popover-arrow{right:calc(-.5rem - 1px);width:.5rem;height:1rem}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::before,.bs-popover-start>.popover-arrow::before{right:0;border-width:.5rem 0 .5rem .5rem;border-left-color:rgba(0,0,0,.25)}.bs-popover-auto[data-popper-placement^=left]>.popover-arrow::after,.bs-popover-start>.popover-arrow::after{right:1px;border-width:.5rem 0 .5rem .5rem;border-left-color:#fff}.popover-header{padding:.5rem 1rem;margin-bottom:0;font-size:1rem;background-color:#f0f0f0;border-bottom:1px solid rgba(0,0,0,.2);border-top-left-radius:calc(.3rem - 1px);border-top-right-radius:calc(.3rem - 1px)}.popover-header:empty{display:none}.popover-body{padding:1rem 1rem;color:#212529}.carousel{position:relative}.carousel.pointer-event{touch-action:pan-y}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel-inner::after{display:block;clear:both;content:""}.carousel-item{position:relative;display:none;float:left;width:100%;margin-right:-100%;-webkit-backface-visibility:hidden;backface-visibility:hidden;transition:transform .6s ease-in-out}@media (prefers-reduced-motion:reduce){.carousel-item{transition:none}}.carousel-item-next,.carousel-item-prev,.carousel-item.active{display:block}.active.carousel-item-end,.carousel-item-next:not(.carousel-item-start){transform:translateX(100%)}.active.carousel-item-start,.carousel-item-prev:not(.carousel-item-end){transform:translateX(-100%)}.carousel-fade .carousel-item{opacity:0;transition-property:opacity;transform:none}.carousel-fade .carousel-item-next.carousel-item-start,.carousel-fade .carousel-item-prev.carousel-item-end,.carousel-fade .carousel-item.active{z-index:1;opacity:1}.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{z-index:0;opacity:0;transition:opacity 0s .6s}@media (prefers-reduced-motion:reduce){.carousel-fade .active.carousel-item-end,.carousel-fade .active.carousel-item-start{transition:none}}.carousel-control-next,.carousel-control-prev{position:absolute;top:0;bottom:0;z-index:1;display:flex;align-items:center;justify-content:center;width:15%;padding:0;color:#fff;text-align:center;background:0 0;border:0;opacity:.5;transition:opacity .15s ease}@media (prefers-reduced-motion:reduce){.carousel-control-next,.carousel-control-prev{transition:none}}.carousel-control-next:focus,.carousel-control-next:hover,.carousel-control-prev:focus,.carousel-control-prev:hover{color:#fff;text-decoration:none;outline:0;opacity:.9}.carousel-control-prev{left:0}.carousel-control-next{right:0}.carousel-control-next-icon,.carousel-control-prev-icon{display:inline-block;width:2rem;height:2rem;background-repeat:no-repeat;background-position:50%;background-size:100% 100%}.carousel-control-prev-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M11.354 1.646a.5.5 0 0 1 0 .708L5.707 8l5.647 5.646a.5.5 0 0 1-.708.708l-6-6a.5.5 0 0 1 0-.708l6-6a.5.5 0 0 1 .708 0z'/%3e%3c/svg%3e")}.carousel-control-next-icon{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='%23fff'%3e%3cpath d='M4.646 1.646a.5.5 0 0 1 .708 0l6 6a.5.5 0 0 1 0 .708l-6 6a.5.5 0 0 1-.708-.708L10.293 8 4.646 2.354a.5.5 0 0 1 0-.708z'/%3e%3c/svg%3e")}.carousel-indicators{position:absolute;right:0;bottom:0;left:0;z-index:2;display:flex;justify-content:center;padding:0;margin-right:15%;margin-bottom:1rem;margin-left:15%;list-style:none}.carousel-indicators [data-bs-target]{box-sizing:content-box;flex:0 1 auto;width:30px;height:3px;padding:0;margin-right:3px;margin-left:3px;text-indent:-999px;cursor:pointer;background-color:#fff;background-clip:padding-box;border:0;border-top:10px solid transparent;border-bottom:10px solid transparent;opacity:.5;transition:opacity .6s ease}@media (prefers-reduced-motion:reduce){.carousel-indicators [data-bs-target]{transition:none}}.carousel-indicators .active{opacity:1}.carousel-caption{position:absolute;right:15%;bottom:1.25rem;left:15%;padding-top:1.25rem;padding-bottom:1.25rem;color:#fff;text-align:center}.carousel-dark .carousel-control-next-icon,.carousel-dark .carousel-control-prev-icon{filter:invert(1) grayscale(100)}.carousel-dark .carousel-indicators [data-bs-target]{background-color:#000}.carousel-dark .carousel-caption{color:#000}@-webkit-keyframes spinner-border{to{transform:rotate(360deg)}}@keyframes spinner-border{to{transform:rotate(360deg)}}.spinner-border{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;border:.25em solid currentColor;border-right-color:transparent;border-radius:50%;-webkit-animation:.75s linear infinite spinner-border;animation:.75s linear infinite spinner-border}.spinner-border-sm{width:1rem;height:1rem;border-width:.2em}@-webkit-keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}@keyframes spinner-grow{0%{transform:scale(0)}50%{opacity:1;transform:none}}.spinner-grow{display:inline-block;width:2rem;height:2rem;vertical-align:-.125em;background-color:currentColor;border-radius:50%;opacity:0;-webkit-animation:.75s linear infinite spinner-grow;animation:.75s linear infinite spinner-grow}.spinner-grow-sm{width:1rem;height:1rem}@media (prefers-reduced-motion:reduce){.spinner-border,.spinner-grow{-webkit-animation-duration:1.5s;animation-duration:1.5s}}.offcanvas{position:fixed;bottom:0;z-index:1045;display:flex;flex-direction:column;max-width:100%;visibility:hidden;background-color:#fff;background-clip:padding-box;outline:0;transition:transform .3s ease-in-out}@media (prefers-reduced-motion:reduce){.offcanvas{transition:none}}.offcanvas-backdrop{position:fixed;top:0;left:0;z-index:1040;width:100vw;height:100vh;background-color:#000}.offcanvas-backdrop.fade{opacity:0}.offcanvas-backdrop.show{opacity:.5}.offcanvas-header{display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem}.offcanvas-header .btn-close{padding:.5rem .5rem;margin-top:-.5rem;margin-right:-.5rem;margin-bottom:-.5rem}.offcanvas-title{margin-bottom:0;line-height:1.5}.offcanvas-body{flex-grow:1;padding:1rem 1rem;overflow-y:auto}.offcanvas-start{top:0;left:0;width:400px;border-right:1px solid rgba(0,0,0,.2);transform:translateX(-100%)}.offcanvas-end{top:0;right:0;width:400px;border-left:1px solid rgba(0,0,0,.2);transform:translateX(100%)}.offcanvas-top{top:0;right:0;left:0;height:30vh;max-height:100%;border-bottom:1px solid rgba(0,0,0,.2);transform:translateY(-100%)}.offcanvas-bottom{right:0;left:0;height:30vh;max-height:100%;border-top:1px solid rgba(0,0,0,.2);transform:translateY(100%)}.offcanvas.show{transform:none}.placeholder{display:inline-block;min-height:1em;vertical-align:middle;cursor:wait;background-color:currentColor;opacity:.5}.placeholder.btn::before{display:inline-block;content:""}.placeholder-xs{min-height:.6em}.placeholder-sm{min-height:.8em}.placeholder-lg{min-height:1.2em}.placeholder-glow .placeholder{-webkit-animation:placeholder-glow 2s ease-in-out infinite;animation:placeholder-glow 2s ease-in-out infinite}@-webkit-keyframes placeholder-glow{50%{opacity:.2}}@keyframes placeholder-glow{50%{opacity:.2}}.placeholder-wave{-webkit-mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);mask-image:linear-gradient(130deg,#000 55%,rgba(0,0,0,0.8) 75%,#000 95%);-webkit-mask-size:200% 100%;mask-size:200% 100%;-webkit-animation:placeholder-wave 2s linear infinite;animation:placeholder-wave 2s linear infinite}@-webkit-keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}@keyframes placeholder-wave{100%{-webkit-mask-position:-200% 0%;mask-position:-200% 0%}}.clearfix::after{display:block;clear:both;content:""}.link-primary{color:#0d6efd}.link-primary:focus,.link-primary:hover{color:#0a58ca}.link-secondary{color:#6c757d}.link-secondary:focus,.link-secondary:hover{color:#565e64}.link-success{color:#198754}.link-success:focus,.link-success:hover{color:#146c43}.link-info{color:#0dcaf0}.link-info:focus,.link-info:hover{color:#3dd5f3}.link-warning{color:#ffc107}.link-warning:focus,.link-warning:hover{color:#ffcd39}.link-danger{color:#dc3545}.link-danger:focus,.link-danger:hover{color:#b02a37}.link-light{color:#f8f9fa}.link-light:focus,.link-light:hover{color:#f9fafb}.link-dark{color:#212529}.link-dark:focus,.link-dark:hover{color:#1a1e21}.ratio{position:relative;width:100%}.ratio::before{display:block;padding-top:var(--bs-aspect-ratio);content:""}.ratio>*{position:absolute;top:0;left:0;width:100%;height:100%}.ratio-1x1{--bs-aspect-ratio:100%}.ratio-4x3{--bs-aspect-ratio:calc(3 / 4 * 100%)}.ratio-16x9{--bs-aspect-ratio:calc(9 / 16 * 100%)}.ratio-21x9{--bs-aspect-ratio:calc(9 / 21 * 100%)}.fixed-top{position:fixed;top:0;right:0;left:0;z-index:1030}.fixed-bottom{position:fixed;right:0;bottom:0;left:0;z-index:1030}.sticky-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}@media (min-width:576px){.sticky-sm-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:768px){.sticky-md-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:992px){.sticky-lg-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1200px){.sticky-xl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}@media (min-width:1400px){.sticky-xxl-top{position:-webkit-sticky;position:sticky;top:0;z-index:1020}}.hstack{display:flex;flex-direction:row;align-items:center;align-self:stretch}.vstack{display:flex;flex:1 1 auto;flex-direction:column;align-self:stretch}.visually-hidden,.visually-hidden-focusable:not(:focus):not(:focus-within){position:absolute!important;width:1px!important;height:1px!important;padding:0!important;margin:-1px!important;overflow:hidden!important;clip:rect(0,0,0,0)!important;white-space:nowrap!important;border:0!important}.stretched-link::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:""}.text-truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.vr{display:inline-block;align-self:stretch;width:1px;min-height:1em;background-color:currentColor;opacity:.25}.align-baseline{vertical-align:baseline!important}.align-top{vertical-align:top!important}.align-middle{vertical-align:middle!important}.align-bottom{vertical-align:bottom!important}.align-text-bottom{vertical-align:text-bottom!important}.align-text-top{vertical-align:text-top!important}.float-start{float:left!important}.float-end{float:right!important}.float-none{float:none!important}.opacity-0{opacity:0!important}.opacity-25{opacity:.25!important}.opacity-50{opacity:.5!important}.opacity-75{opacity:.75!important}.opacity-100{opacity:1!important}.overflow-auto{overflow:auto!important}.overflow-hidden{overflow:hidden!important}.overflow-visible{overflow:visible!important}.overflow-scroll{overflow:scroll!important}.d-inline{display:inline!important}.d-inline-block{display:inline-block!important}.d-block{display:block!important}.d-grid{display:grid!important}.d-table{display:table!important}.d-table-row{display:table-row!important}.d-table-cell{display:table-cell!important}.d-flex{display:flex!important}.d-inline-flex{display:inline-flex!important}.d-none{display:none!important}.shadow{box-shadow:0 .5rem 1rem rgba(0,0,0,.15)!important}.shadow-sm{box-shadow:0 .125rem .25rem rgba(0,0,0,.075)!important}.shadow-lg{box-shadow:0 1rem 3rem rgba(0,0,0,.175)!important}.shadow-none{box-shadow:none!important}.position-static{position:static!important}.position-relative{position:relative!important}.position-absolute{position:absolute!important}.position-fixed{position:fixed!important}.position-sticky{position:-webkit-sticky!important;position:sticky!important}.top-0{top:0!important}.top-50{top:50%!important}.top-100{top:100%!important}.bottom-0{bottom:0!important}.bottom-50{bottom:50%!important}.bottom-100{bottom:100%!important}.start-0{left:0!important}.start-50{left:50%!important}.start-100{left:100%!important}.end-0{right:0!important}.end-50{right:50%!important}.end-100{right:100%!important}.translate-middle{transform:translate(-50%,-50%)!important}.translate-middle-x{transform:translateX(-50%)!important}.translate-middle-y{transform:translateY(-50%)!important}.border{border:1px solid #dee2e6!important}.border-0{border:0!important}.border-top{border-top:1px solid #dee2e6!important}.border-top-0{border-top:0!important}.border-end{border-right:1px solid #dee2e6!important}.border-end-0{border-right:0!important}.border-bottom{border-bottom:1px solid #dee2e6!important}.border-bottom-0{border-bottom:0!important}.border-start{border-left:1px solid #dee2e6!important}.border-start-0{border-left:0!important}.border-primary{border-color:#0d6efd!important}.border-secondary{border-color:#6c757d!important}.border-success{border-color:#198754!important}.border-info{border-color:#0dcaf0!important}.border-warning{border-color:#ffc107!important}.border-danger{border-color:#dc3545!important}.border-light{border-color:#f8f9fa!important}.border-dark{border-color:#212529!important}.border-white{border-color:#fff!important}.border-1{border-width:1px!important}.border-2{border-width:2px!important}.border-3{border-width:3px!important}.border-4{border-width:4px!important}.border-5{border-width:5px!important}.w-25{width:25%!important}.w-50{width:50%!important}.w-75{width:75%!important}.w-100{width:100%!important}.w-auto{width:auto!important}.mw-100{max-width:100%!important}.vw-100{width:100vw!important}.min-vw-100{min-width:100vw!important}.h-25{height:25%!important}.h-50{height:50%!important}.h-75{height:75%!important}.h-100{height:100%!important}.h-auto{height:auto!important}.mh-100{max-height:100%!important}.vh-100{height:100vh!important}.min-vh-100{min-height:100vh!important}.flex-fill{flex:1 1 auto!important}.flex-row{flex-direction:row!important}.flex-column{flex-direction:column!important}.flex-row-reverse{flex-direction:row-reverse!important}.flex-column-reverse{flex-direction:column-reverse!important}.flex-grow-0{flex-grow:0!important}.flex-grow-1{flex-grow:1!important}.flex-shrink-0{flex-shrink:0!important}.flex-shrink-1{flex-shrink:1!important}.flex-wrap{flex-wrap:wrap!important}.flex-nowrap{flex-wrap:nowrap!important}.flex-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-0{gap:0!important}.gap-1{gap:.25rem!important}.gap-2{gap:.5rem!important}.gap-3{gap:1rem!important}.gap-4{gap:1.5rem!important}.gap-5{gap:3rem!important}.justify-content-start{justify-content:flex-start!important}.justify-content-end{justify-content:flex-end!important}.justify-content-center{justify-content:center!important}.justify-content-between{justify-content:space-between!important}.justify-content-around{justify-content:space-around!important}.justify-content-evenly{justify-content:space-evenly!important}.align-items-start{align-items:flex-start!important}.align-items-end{align-items:flex-end!important}.align-items-center{align-items:center!important}.align-items-baseline{align-items:baseline!important}.align-items-stretch{align-items:stretch!important}.align-content-start{align-content:flex-start!important}.align-content-end{align-content:flex-end!important}.align-content-center{align-content:center!important}.align-content-between{align-content:space-between!important}.align-content-around{align-content:space-around!important}.align-content-stretch{align-content:stretch!important}.align-self-auto{align-self:auto!important}.align-self-start{align-self:flex-start!important}.align-self-end{align-self:flex-end!important}.align-self-center{align-self:center!important}.align-self-baseline{align-self:baseline!important}.align-self-stretch{align-self:stretch!important}.order-first{order:-1!important}.order-0{order:0!important}.order-1{order:1!important}.order-2{order:2!important}.order-3{order:3!important}.order-4{order:4!important}.order-5{order:5!important}.order-last{order:6!important}.m-0{margin:0!important}.m-1{margin:.25rem!important}.m-2{margin:.5rem!important}.m-3{margin:1rem!important}.m-4{margin:1.5rem!important}.m-5{margin:3rem!important}.m-auto{margin:auto!important}.mx-0{margin-right:0!important;margin-left:0!important}.mx-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-3{margin-right:1rem!important;margin-left:1rem!important}.mx-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-5{margin-right:3rem!important;margin-left:3rem!important}.mx-auto{margin-right:auto!important;margin-left:auto!important}.my-0{margin-top:0!important;margin-bottom:0!important}.my-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-0{margin-top:0!important}.mt-1{margin-top:.25rem!important}.mt-2{margin-top:.5rem!important}.mt-3{margin-top:1rem!important}.mt-4{margin-top:1.5rem!important}.mt-5{margin-top:3rem!important}.mt-auto{margin-top:auto!important}.me-0{margin-right:0!important}.me-1{margin-right:.25rem!important}.me-2{margin-right:.5rem!important}.me-3{margin-right:1rem!important}.me-4{margin-right:1.5rem!important}.me-5{margin-right:3rem!important}.me-auto{margin-right:auto!important}.mb-0{margin-bottom:0!important}.mb-1{margin-bottom:.25rem!important}.mb-2{margin-bottom:.5rem!important}.mb-3{margin-bottom:1rem!important}.mb-4{margin-bottom:1.5rem!important}.mb-5{margin-bottom:3rem!important}.mb-auto{margin-bottom:auto!important}.ms-0{margin-left:0!important}.ms-1{margin-left:.25rem!important}.ms-2{margin-left:.5rem!important}.ms-3{margin-left:1rem!important}.ms-4{margin-left:1.5rem!important}.ms-5{margin-left:3rem!important}.ms-auto{margin-left:auto!important}.p-0{padding:0!important}.p-1{padding:.25rem!important}.p-2{padding:.5rem!important}.p-3{padding:1rem!important}.p-4{padding:1.5rem!important}.p-5{padding:3rem!important}.px-0{padding-right:0!important;padding-left:0!important}.px-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-3{padding-right:1rem!important;padding-left:1rem!important}.px-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-5{padding-right:3rem!important;padding-left:3rem!important}.py-0{padding-top:0!important;padding-bottom:0!important}.py-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-0{padding-top:0!important}.pt-1{padding-top:.25rem!important}.pt-2{padding-top:.5rem!important}.pt-3{padding-top:1rem!important}.pt-4{padding-top:1.5rem!important}.pt-5{padding-top:3rem!important}.pe-0{padding-right:0!important}.pe-1{padding-right:.25rem!important}.pe-2{padding-right:.5rem!important}.pe-3{padding-right:1rem!important}.pe-4{padding-right:1.5rem!important}.pe-5{padding-right:3rem!important}.pb-0{padding-bottom:0!important}.pb-1{padding-bottom:.25rem!important}.pb-2{padding-bottom:.5rem!important}.pb-3{padding-bottom:1rem!important}.pb-4{padding-bottom:1.5rem!important}.pb-5{padding-bottom:3rem!important}.ps-0{padding-left:0!important}.ps-1{padding-left:.25rem!important}.ps-2{padding-left:.5rem!important}.ps-3{padding-left:1rem!important}.ps-4{padding-left:1.5rem!important}.ps-5{padding-left:3rem!important}.font-monospace{font-family:var(--bs-font-monospace)!important}.fs-1{font-size:calc(1.375rem + 1.5vw)!important}.fs-2{font-size:calc(1.325rem + .9vw)!important}.fs-3{font-size:calc(1.3rem + .6vw)!important}.fs-4{font-size:calc(1.275rem + .3vw)!important}.fs-5{font-size:1.25rem!important}.fs-6{font-size:1rem!important}.fst-italic{font-style:italic!important}.fst-normal{font-style:normal!important}.fw-light{font-weight:300!important}.fw-lighter{font-weight:lighter!important}.fw-normal{font-weight:400!important}.fw-bold{font-weight:700!important}.fw-bolder{font-weight:bolder!important}.lh-1{line-height:1!important}.lh-sm{line-height:1.25!important}.lh-base{line-height:1.5!important}.lh-lg{line-height:2!important}.text-start{text-align:left!important}.text-end{text-align:right!important}.text-center{text-align:center!important}.text-decoration-none{text-decoration:none!important}.text-decoration-underline{text-decoration:underline!important}.text-decoration-line-through{text-decoration:line-through!important}.text-lowercase{text-transform:lowercase!important}.text-uppercase{text-transform:uppercase!important}.text-capitalize{text-transform:capitalize!important}.text-wrap{white-space:normal!important}.text-nowrap{white-space:nowrap!important}.text-break{word-wrap:break-word!important;word-break:break-word!important}.text-primary{--bs-text-opacity:1;color:rgba(var(--bs-primary-rgb),var(--bs-text-opacity))!important}.text-secondary{--bs-text-opacity:1;color:rgba(var(--bs-secondary-rgb),var(--bs-text-opacity))!important}.text-success{--bs-text-opacity:1;color:rgba(var(--bs-success-rgb),var(--bs-text-opacity))!important}.text-info{--bs-text-opacity:1;color:rgba(var(--bs-info-rgb),var(--bs-text-opacity))!important}.text-warning{--bs-text-opacity:1;color:rgba(var(--bs-warning-rgb),var(--bs-text-opacity))!important}.text-danger{--bs-text-opacity:1;color:rgba(var(--bs-danger-rgb),var(--bs-text-opacity))!important}.text-light{--bs-text-opacity:1;color:rgba(var(--bs-light-rgb),var(--bs-text-opacity))!important}.text-dark{--bs-text-opacity:1;color:rgba(var(--bs-dark-rgb),var(--bs-text-opacity))!important}.text-black{--bs-text-opacity:1;color:rgba(var(--bs-black-rgb),var(--bs-text-opacity))!important}.text-white{--bs-text-opacity:1;color:rgba(var(--bs-white-rgb),var(--bs-text-opacity))!important}.text-body{--bs-text-opacity:1;color:rgba(var(--bs-body-rgb),var(--bs-text-opacity))!important}.text-muted{--bs-text-opacity:1;color:#6c757d!important}.text-black-50{--bs-text-opacity:1;color:rgba(0,0,0,.5)!important}.text-white-50{--bs-text-opacity:1;color:rgba(255,255,255,.5)!important}.text-reset{--bs-text-opacity:1;color:inherit!important}.text-opacity-25{--bs-text-opacity:0.25}.text-opacity-50{--bs-text-opacity:0.5}.text-opacity-75{--bs-text-opacity:0.75}.text-opacity-100{--bs-text-opacity:1}.bg-primary{--bs-bg-opacity:1;background-color:rgba(var(--bs-primary-rgb),var(--bs-bg-opacity))!important}.bg-secondary{--bs-bg-opacity:1;background-color:rgba(var(--bs-secondary-rgb),var(--bs-bg-opacity))!important}.bg-success{--bs-bg-opacity:1;background-color:rgba(var(--bs-success-rgb),var(--bs-bg-opacity))!important}.bg-info{--bs-bg-opacity:1;background-color:rgba(var(--bs-info-rgb),var(--bs-bg-opacity))!important}.bg-warning{--bs-bg-opacity:1;background-color:rgba(var(--bs-warning-rgb),var(--bs-bg-opacity))!important}.bg-danger{--bs-bg-opacity:1;background-color:rgba(var(--bs-danger-rgb),var(--bs-bg-opacity))!important}.bg-light{--bs-bg-opacity:1;background-color:rgba(var(--bs-light-rgb),var(--bs-bg-opacity))!important}.bg-dark{--bs-bg-opacity:1;background-color:rgba(var(--bs-dark-rgb),var(--bs-bg-opacity))!important}.bg-black{--bs-bg-opacity:1;background-color:rgba(var(--bs-black-rgb),var(--bs-bg-opacity))!important}.bg-white{--bs-bg-opacity:1;background-color:rgba(var(--bs-white-rgb),var(--bs-bg-opacity))!important}.bg-body{--bs-bg-opacity:1;background-color:rgba(var(--bs-body-rgb),var(--bs-bg-opacity))!important}.bg-transparent{--bs-bg-opacity:1;background-color:transparent!important}.bg-opacity-10{--bs-bg-opacity:0.1}.bg-opacity-25{--bs-bg-opacity:0.25}.bg-opacity-50{--bs-bg-opacity:0.5}.bg-opacity-75{--bs-bg-opacity:0.75}.bg-opacity-100{--bs-bg-opacity:1}.bg-gradient{background-image:var(--bs-gradient)!important}.user-select-all{-webkit-user-select:all!important;-moz-user-select:all!important;user-select:all!important}.user-select-auto{-webkit-user-select:auto!important;-moz-user-select:auto!important;user-select:auto!important}.user-select-none{-webkit-user-select:none!important;-moz-user-select:none!important;user-select:none!important}.pe-none{pointer-events:none!important}.pe-auto{pointer-events:auto!important}.rounded{border-radius:.25rem!important}.rounded-0{border-radius:0!important}.rounded-1{border-radius:.2rem!important}.rounded-2{border-radius:.25rem!important}.rounded-3{border-radius:.3rem!important}.rounded-circle{border-radius:50%!important}.rounded-pill{border-radius:50rem!important}.rounded-top{border-top-left-radius:.25rem!important;border-top-right-radius:.25rem!important}.rounded-end{border-top-right-radius:.25rem!important;border-bottom-right-radius:.25rem!important}.rounded-bottom{border-bottom-right-radius:.25rem!important;border-bottom-left-radius:.25rem!important}.rounded-start{border-bottom-left-radius:.25rem!important;border-top-left-radius:.25rem!important}.visible{visibility:visible!important}.invisible{visibility:hidden!important}@media (min-width:576px){.float-sm-start{float:left!important}.float-sm-end{float:right!important}.float-sm-none{float:none!important}.d-sm-inline{display:inline!important}.d-sm-inline-block{display:inline-block!important}.d-sm-block{display:block!important}.d-sm-grid{display:grid!important}.d-sm-table{display:table!important}.d-sm-table-row{display:table-row!important}.d-sm-table-cell{display:table-cell!important}.d-sm-flex{display:flex!important}.d-sm-inline-flex{display:inline-flex!important}.d-sm-none{display:none!important}.flex-sm-fill{flex:1 1 auto!important}.flex-sm-row{flex-direction:row!important}.flex-sm-column{flex-direction:column!important}.flex-sm-row-reverse{flex-direction:row-reverse!important}.flex-sm-column-reverse{flex-direction:column-reverse!important}.flex-sm-grow-0{flex-grow:0!important}.flex-sm-grow-1{flex-grow:1!important}.flex-sm-shrink-0{flex-shrink:0!important}.flex-sm-shrink-1{flex-shrink:1!important}.flex-sm-wrap{flex-wrap:wrap!important}.flex-sm-nowrap{flex-wrap:nowrap!important}.flex-sm-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-sm-0{gap:0!important}.gap-sm-1{gap:.25rem!important}.gap-sm-2{gap:.5rem!important}.gap-sm-3{gap:1rem!important}.gap-sm-4{gap:1.5rem!important}.gap-sm-5{gap:3rem!important}.justify-content-sm-start{justify-content:flex-start!important}.justify-content-sm-end{justify-content:flex-end!important}.justify-content-sm-center{justify-content:center!important}.justify-content-sm-between{justify-content:space-between!important}.justify-content-sm-around{justify-content:space-around!important}.justify-content-sm-evenly{justify-content:space-evenly!important}.align-items-sm-start{align-items:flex-start!important}.align-items-sm-end{align-items:flex-end!important}.align-items-sm-center{align-items:center!important}.align-items-sm-baseline{align-items:baseline!important}.align-items-sm-stretch{align-items:stretch!important}.align-content-sm-start{align-content:flex-start!important}.align-content-sm-end{align-content:flex-end!important}.align-content-sm-center{align-content:center!important}.align-content-sm-between{align-content:space-between!important}.align-content-sm-around{align-content:space-around!important}.align-content-sm-stretch{align-content:stretch!important}.align-self-sm-auto{align-self:auto!important}.align-self-sm-start{align-self:flex-start!important}.align-self-sm-end{align-self:flex-end!important}.align-self-sm-center{align-self:center!important}.align-self-sm-baseline{align-self:baseline!important}.align-self-sm-stretch{align-self:stretch!important}.order-sm-first{order:-1!important}.order-sm-0{order:0!important}.order-sm-1{order:1!important}.order-sm-2{order:2!important}.order-sm-3{order:3!important}.order-sm-4{order:4!important}.order-sm-5{order:5!important}.order-sm-last{order:6!important}.m-sm-0{margin:0!important}.m-sm-1{margin:.25rem!important}.m-sm-2{margin:.5rem!important}.m-sm-3{margin:1rem!important}.m-sm-4{margin:1.5rem!important}.m-sm-5{margin:3rem!important}.m-sm-auto{margin:auto!important}.mx-sm-0{margin-right:0!important;margin-left:0!important}.mx-sm-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-sm-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-sm-3{margin-right:1rem!important;margin-left:1rem!important}.mx-sm-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-sm-5{margin-right:3rem!important;margin-left:3rem!important}.mx-sm-auto{margin-right:auto!important;margin-left:auto!important}.my-sm-0{margin-top:0!important;margin-bottom:0!important}.my-sm-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-sm-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-sm-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-sm-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-sm-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-sm-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-sm-0{margin-top:0!important}.mt-sm-1{margin-top:.25rem!important}.mt-sm-2{margin-top:.5rem!important}.mt-sm-3{margin-top:1rem!important}.mt-sm-4{margin-top:1.5rem!important}.mt-sm-5{margin-top:3rem!important}.mt-sm-auto{margin-top:auto!important}.me-sm-0{margin-right:0!important}.me-sm-1{margin-right:.25rem!important}.me-sm-2{margin-right:.5rem!important}.me-sm-3{margin-right:1rem!important}.me-sm-4{margin-right:1.5rem!important}.me-sm-5{margin-right:3rem!important}.me-sm-auto{margin-right:auto!important}.mb-sm-0{margin-bottom:0!important}.mb-sm-1{margin-bottom:.25rem!important}.mb-sm-2{margin-bottom:.5rem!important}.mb-sm-3{margin-bottom:1rem!important}.mb-sm-4{margin-bottom:1.5rem!important}.mb-sm-5{margin-bottom:3rem!important}.mb-sm-auto{margin-bottom:auto!important}.ms-sm-0{margin-left:0!important}.ms-sm-1{margin-left:.25rem!important}.ms-sm-2{margin-left:.5rem!important}.ms-sm-3{margin-left:1rem!important}.ms-sm-4{margin-left:1.5rem!important}.ms-sm-5{margin-left:3rem!important}.ms-sm-auto{margin-left:auto!important}.p-sm-0{padding:0!important}.p-sm-1{padding:.25rem!important}.p-sm-2{padding:.5rem!important}.p-sm-3{padding:1rem!important}.p-sm-4{padding:1.5rem!important}.p-sm-5{padding:3rem!important}.px-sm-0{padding-right:0!important;padding-left:0!important}.px-sm-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-sm-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-sm-3{padding-right:1rem!important;padding-left:1rem!important}.px-sm-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-sm-5{padding-right:3rem!important;padding-left:3rem!important}.py-sm-0{padding-top:0!important;padding-bottom:0!important}.py-sm-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-sm-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-sm-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-sm-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-sm-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-sm-0{padding-top:0!important}.pt-sm-1{padding-top:.25rem!important}.pt-sm-2{padding-top:.5rem!important}.pt-sm-3{padding-top:1rem!important}.pt-sm-4{padding-top:1.5rem!important}.pt-sm-5{padding-top:3rem!important}.pe-sm-0{padding-right:0!important}.pe-sm-1{padding-right:.25rem!important}.pe-sm-2{padding-right:.5rem!important}.pe-sm-3{padding-right:1rem!important}.pe-sm-4{padding-right:1.5rem!important}.pe-sm-5{padding-right:3rem!important}.pb-sm-0{padding-bottom:0!important}.pb-sm-1{padding-bottom:.25rem!important}.pb-sm-2{padding-bottom:.5rem!important}.pb-sm-3{padding-bottom:1rem!important}.pb-sm-4{padding-bottom:1.5rem!important}.pb-sm-5{padding-bottom:3rem!important}.ps-sm-0{padding-left:0!important}.ps-sm-1{padding-left:.25rem!important}.ps-sm-2{padding-left:.5rem!important}.ps-sm-3{padding-left:1rem!important}.ps-sm-4{padding-left:1.5rem!important}.ps-sm-5{padding-left:3rem!important}.text-sm-start{text-align:left!important}.text-sm-end{text-align:right!important}.text-sm-center{text-align:center!important}}@media (min-width:768px){.float-md-start{float:left!important}.float-md-end{float:right!important}.float-md-none{float:none!important}.d-md-inline{display:inline!important}.d-md-inline-block{display:inline-block!important}.d-md-block{display:block!important}.d-md-grid{display:grid!important}.d-md-table{display:table!important}.d-md-table-row{display:table-row!important}.d-md-table-cell{display:table-cell!important}.d-md-flex{display:flex!important}.d-md-inline-flex{display:inline-flex!important}.d-md-none{display:none!important}.flex-md-fill{flex:1 1 auto!important}.flex-md-row{flex-direction:row!important}.flex-md-column{flex-direction:column!important}.flex-md-row-reverse{flex-direction:row-reverse!important}.flex-md-column-reverse{flex-direction:column-reverse!important}.flex-md-grow-0{flex-grow:0!important}.flex-md-grow-1{flex-grow:1!important}.flex-md-shrink-0{flex-shrink:0!important}.flex-md-shrink-1{flex-shrink:1!important}.flex-md-wrap{flex-wrap:wrap!important}.flex-md-nowrap{flex-wrap:nowrap!important}.flex-md-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-md-0{gap:0!important}.gap-md-1{gap:.25rem!important}.gap-md-2{gap:.5rem!important}.gap-md-3{gap:1rem!important}.gap-md-4{gap:1.5rem!important}.gap-md-5{gap:3rem!important}.justify-content-md-start{justify-content:flex-start!important}.justify-content-md-end{justify-content:flex-end!important}.justify-content-md-center{justify-content:center!important}.justify-content-md-between{justify-content:space-between!important}.justify-content-md-around{justify-content:space-around!important}.justify-content-md-evenly{justify-content:space-evenly!important}.align-items-md-start{align-items:flex-start!important}.align-items-md-end{align-items:flex-end!important}.align-items-md-center{align-items:center!important}.align-items-md-baseline{align-items:baseline!important}.align-items-md-stretch{align-items:stretch!important}.align-content-md-start{align-content:flex-start!important}.align-content-md-end{align-content:flex-end!important}.align-content-md-center{align-content:center!important}.align-content-md-between{align-content:space-between!important}.align-content-md-around{align-content:space-around!important}.align-content-md-stretch{align-content:stretch!important}.align-self-md-auto{align-self:auto!important}.align-self-md-start{align-self:flex-start!important}.align-self-md-end{align-self:flex-end!important}.align-self-md-center{align-self:center!important}.align-self-md-baseline{align-self:baseline!important}.align-self-md-stretch{align-self:stretch!important}.order-md-first{order:-1!important}.order-md-0{order:0!important}.order-md-1{order:1!important}.order-md-2{order:2!important}.order-md-3{order:3!important}.order-md-4{order:4!important}.order-md-5{order:5!important}.order-md-last{order:6!important}.m-md-0{margin:0!important}.m-md-1{margin:.25rem!important}.m-md-2{margin:.5rem!important}.m-md-3{margin:1rem!important}.m-md-4{margin:1.5rem!important}.m-md-5{margin:3rem!important}.m-md-auto{margin:auto!important}.mx-md-0{margin-right:0!important;margin-left:0!important}.mx-md-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-md-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-md-3{margin-right:1rem!important;margin-left:1rem!important}.mx-md-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-md-5{margin-right:3rem!important;margin-left:3rem!important}.mx-md-auto{margin-right:auto!important;margin-left:auto!important}.my-md-0{margin-top:0!important;margin-bottom:0!important}.my-md-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-md-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-md-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-md-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-md-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-md-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-md-0{margin-top:0!important}.mt-md-1{margin-top:.25rem!important}.mt-md-2{margin-top:.5rem!important}.mt-md-3{margin-top:1rem!important}.mt-md-4{margin-top:1.5rem!important}.mt-md-5{margin-top:3rem!important}.mt-md-auto{margin-top:auto!important}.me-md-0{margin-right:0!important}.me-md-1{margin-right:.25rem!important}.me-md-2{margin-right:.5rem!important}.me-md-3{margin-right:1rem!important}.me-md-4{margin-right:1.5rem!important}.me-md-5{margin-right:3rem!important}.me-md-auto{margin-right:auto!important}.mb-md-0{margin-bottom:0!important}.mb-md-1{margin-bottom:.25rem!important}.mb-md-2{margin-bottom:.5rem!important}.mb-md-3{margin-bottom:1rem!important}.mb-md-4{margin-bottom:1.5rem!important}.mb-md-5{margin-bottom:3rem!important}.mb-md-auto{margin-bottom:auto!important}.ms-md-0{margin-left:0!important}.ms-md-1{margin-left:.25rem!important}.ms-md-2{margin-left:.5rem!important}.ms-md-3{margin-left:1rem!important}.ms-md-4{margin-left:1.5rem!important}.ms-md-5{margin-left:3rem!important}.ms-md-auto{margin-left:auto!important}.p-md-0{padding:0!important}.p-md-1{padding:.25rem!important}.p-md-2{padding:.5rem!important}.p-md-3{padding:1rem!important}.p-md-4{padding:1.5rem!important}.p-md-5{padding:3rem!important}.px-md-0{padding-right:0!important;padding-left:0!important}.px-md-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-md-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-md-3{padding-right:1rem!important;padding-left:1rem!important}.px-md-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-md-5{padding-right:3rem!important;padding-left:3rem!important}.py-md-0{padding-top:0!important;padding-bottom:0!important}.py-md-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-md-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-md-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-md-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-md-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-md-0{padding-top:0!important}.pt-md-1{padding-top:.25rem!important}.pt-md-2{padding-top:.5rem!important}.pt-md-3{padding-top:1rem!important}.pt-md-4{padding-top:1.5rem!important}.pt-md-5{padding-top:3rem!important}.pe-md-0{padding-right:0!important}.pe-md-1{padding-right:.25rem!important}.pe-md-2{padding-right:.5rem!important}.pe-md-3{padding-right:1rem!important}.pe-md-4{padding-right:1.5rem!important}.pe-md-5{padding-right:3rem!important}.pb-md-0{padding-bottom:0!important}.pb-md-1{padding-bottom:.25rem!important}.pb-md-2{padding-bottom:.5rem!important}.pb-md-3{padding-bottom:1rem!important}.pb-md-4{padding-bottom:1.5rem!important}.pb-md-5{padding-bottom:3rem!important}.ps-md-0{padding-left:0!important}.ps-md-1{padding-left:.25rem!important}.ps-md-2{padding-left:.5rem!important}.ps-md-3{padding-left:1rem!important}.ps-md-4{padding-left:1.5rem!important}.ps-md-5{padding-left:3rem!important}.text-md-start{text-align:left!important}.text-md-end{text-align:right!important}.text-md-center{text-align:center!important}}@media (min-width:992px){.float-lg-start{float:left!important}.float-lg-end{float:right!important}.float-lg-none{float:none!important}.d-lg-inline{display:inline!important}.d-lg-inline-block{display:inline-block!important}.d-lg-block{display:block!important}.d-lg-grid{display:grid!important}.d-lg-table{display:table!important}.d-lg-table-row{display:table-row!important}.d-lg-table-cell{display:table-cell!important}.d-lg-flex{display:flex!important}.d-lg-inline-flex{display:inline-flex!important}.d-lg-none{display:none!important}.flex-lg-fill{flex:1 1 auto!important}.flex-lg-row{flex-direction:row!important}.flex-lg-column{flex-direction:column!important}.flex-lg-row-reverse{flex-direction:row-reverse!important}.flex-lg-column-reverse{flex-direction:column-reverse!important}.flex-lg-grow-0{flex-grow:0!important}.flex-lg-grow-1{flex-grow:1!important}.flex-lg-shrink-0{flex-shrink:0!important}.flex-lg-shrink-1{flex-shrink:1!important}.flex-lg-wrap{flex-wrap:wrap!important}.flex-lg-nowrap{flex-wrap:nowrap!important}.flex-lg-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-lg-0{gap:0!important}.gap-lg-1{gap:.25rem!important}.gap-lg-2{gap:.5rem!important}.gap-lg-3{gap:1rem!important}.gap-lg-4{gap:1.5rem!important}.gap-lg-5{gap:3rem!important}.justify-content-lg-start{justify-content:flex-start!important}.justify-content-lg-end{justify-content:flex-end!important}.justify-content-lg-center{justify-content:center!important}.justify-content-lg-between{justify-content:space-between!important}.justify-content-lg-around{justify-content:space-around!important}.justify-content-lg-evenly{justify-content:space-evenly!important}.align-items-lg-start{align-items:flex-start!important}.align-items-lg-end{align-items:flex-end!important}.align-items-lg-center{align-items:center!important}.align-items-lg-baseline{align-items:baseline!important}.align-items-lg-stretch{align-items:stretch!important}.align-content-lg-start{align-content:flex-start!important}.align-content-lg-end{align-content:flex-end!important}.align-content-lg-center{align-content:center!important}.align-content-lg-between{align-content:space-between!important}.align-content-lg-around{align-content:space-around!important}.align-content-lg-stretch{align-content:stretch!important}.align-self-lg-auto{align-self:auto!important}.align-self-lg-start{align-self:flex-start!important}.align-self-lg-end{align-self:flex-end!important}.align-self-lg-center{align-self:center!important}.align-self-lg-baseline{align-self:baseline!important}.align-self-lg-stretch{align-self:stretch!important}.order-lg-first{order:-1!important}.order-lg-0{order:0!important}.order-lg-1{order:1!important}.order-lg-2{order:2!important}.order-lg-3{order:3!important}.order-lg-4{order:4!important}.order-lg-5{order:5!important}.order-lg-last{order:6!important}.m-lg-0{margin:0!important}.m-lg-1{margin:.25rem!important}.m-lg-2{margin:.5rem!important}.m-lg-3{margin:1rem!important}.m-lg-4{margin:1.5rem!important}.m-lg-5{margin:3rem!important}.m-lg-auto{margin:auto!important}.mx-lg-0{margin-right:0!important;margin-left:0!important}.mx-lg-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-lg-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-lg-3{margin-right:1rem!important;margin-left:1rem!important}.mx-lg-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-lg-5{margin-right:3rem!important;margin-left:3rem!important}.mx-lg-auto{margin-right:auto!important;margin-left:auto!important}.my-lg-0{margin-top:0!important;margin-bottom:0!important}.my-lg-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-lg-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-lg-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-lg-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-lg-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-lg-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-lg-0{margin-top:0!important}.mt-lg-1{margin-top:.25rem!important}.mt-lg-2{margin-top:.5rem!important}.mt-lg-3{margin-top:1rem!important}.mt-lg-4{margin-top:1.5rem!important}.mt-lg-5{margin-top:3rem!important}.mt-lg-auto{margin-top:auto!important}.me-lg-0{margin-right:0!important}.me-lg-1{margin-right:.25rem!important}.me-lg-2{margin-right:.5rem!important}.me-lg-3{margin-right:1rem!important}.me-lg-4{margin-right:1.5rem!important}.me-lg-5{margin-right:3rem!important}.me-lg-auto{margin-right:auto!important}.mb-lg-0{margin-bottom:0!important}.mb-lg-1{margin-bottom:.25rem!important}.mb-lg-2{margin-bottom:.5rem!important}.mb-lg-3{margin-bottom:1rem!important}.mb-lg-4{margin-bottom:1.5rem!important}.mb-lg-5{margin-bottom:3rem!important}.mb-lg-auto{margin-bottom:auto!important}.ms-lg-0{margin-left:0!important}.ms-lg-1{margin-left:.25rem!important}.ms-lg-2{margin-left:.5rem!important}.ms-lg-3{margin-left:1rem!important}.ms-lg-4{margin-left:1.5rem!important}.ms-lg-5{margin-left:3rem!important}.ms-lg-auto{margin-left:auto!important}.p-lg-0{padding:0!important}.p-lg-1{padding:.25rem!important}.p-lg-2{padding:.5rem!important}.p-lg-3{padding:1rem!important}.p-lg-4{padding:1.5rem!important}.p-lg-5{padding:3rem!important}.px-lg-0{padding-right:0!important;padding-left:0!important}.px-lg-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-lg-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-lg-3{padding-right:1rem!important;padding-left:1rem!important}.px-lg-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-lg-5{padding-right:3rem!important;padding-left:3rem!important}.py-lg-0{padding-top:0!important;padding-bottom:0!important}.py-lg-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-lg-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-lg-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-lg-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-lg-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-lg-0{padding-top:0!important}.pt-lg-1{padding-top:.25rem!important}.pt-lg-2{padding-top:.5rem!important}.pt-lg-3{padding-top:1rem!important}.pt-lg-4{padding-top:1.5rem!important}.pt-lg-5{padding-top:3rem!important}.pe-lg-0{padding-right:0!important}.pe-lg-1{padding-right:.25rem!important}.pe-lg-2{padding-right:.5rem!important}.pe-lg-3{padding-right:1rem!important}.pe-lg-4{padding-right:1.5rem!important}.pe-lg-5{padding-right:3rem!important}.pb-lg-0{padding-bottom:0!important}.pb-lg-1{padding-bottom:.25rem!important}.pb-lg-2{padding-bottom:.5rem!important}.pb-lg-3{padding-bottom:1rem!important}.pb-lg-4{padding-bottom:1.5rem!important}.pb-lg-5{padding-bottom:3rem!important}.ps-lg-0{padding-left:0!important}.ps-lg-1{padding-left:.25rem!important}.ps-lg-2{padding-left:.5rem!important}.ps-lg-3{padding-left:1rem!important}.ps-lg-4{padding-left:1.5rem!important}.ps-lg-5{padding-left:3rem!important}.text-lg-start{text-align:left!important}.text-lg-end{text-align:right!important}.text-lg-center{text-align:center!important}}@media (min-width:1200px){.float-xl-start{float:left!important}.float-xl-end{float:right!important}.float-xl-none{float:none!important}.d-xl-inline{display:inline!important}.d-xl-inline-block{display:inline-block!important}.d-xl-block{display:block!important}.d-xl-grid{display:grid!important}.d-xl-table{display:table!important}.d-xl-table-row{display:table-row!important}.d-xl-table-cell{display:table-cell!important}.d-xl-flex{display:flex!important}.d-xl-inline-flex{display:inline-flex!important}.d-xl-none{display:none!important}.flex-xl-fill{flex:1 1 auto!important}.flex-xl-row{flex-direction:row!important}.flex-xl-column{flex-direction:column!important}.flex-xl-row-reverse{flex-direction:row-reverse!important}.flex-xl-column-reverse{flex-direction:column-reverse!important}.flex-xl-grow-0{flex-grow:0!important}.flex-xl-grow-1{flex-grow:1!important}.flex-xl-shrink-0{flex-shrink:0!important}.flex-xl-shrink-1{flex-shrink:1!important}.flex-xl-wrap{flex-wrap:wrap!important}.flex-xl-nowrap{flex-wrap:nowrap!important}.flex-xl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xl-0{gap:0!important}.gap-xl-1{gap:.25rem!important}.gap-xl-2{gap:.5rem!important}.gap-xl-3{gap:1rem!important}.gap-xl-4{gap:1.5rem!important}.gap-xl-5{gap:3rem!important}.justify-content-xl-start{justify-content:flex-start!important}.justify-content-xl-end{justify-content:flex-end!important}.justify-content-xl-center{justify-content:center!important}.justify-content-xl-between{justify-content:space-between!important}.justify-content-xl-around{justify-content:space-around!important}.justify-content-xl-evenly{justify-content:space-evenly!important}.align-items-xl-start{align-items:flex-start!important}.align-items-xl-end{align-items:flex-end!important}.align-items-xl-center{align-items:center!important}.align-items-xl-baseline{align-items:baseline!important}.align-items-xl-stretch{align-items:stretch!important}.align-content-xl-start{align-content:flex-start!important}.align-content-xl-end{align-content:flex-end!important}.align-content-xl-center{align-content:center!important}.align-content-xl-between{align-content:space-between!important}.align-content-xl-around{align-content:space-around!important}.align-content-xl-stretch{align-content:stretch!important}.align-self-xl-auto{align-self:auto!important}.align-self-xl-start{align-self:flex-start!important}.align-self-xl-end{align-self:flex-end!important}.align-self-xl-center{align-self:center!important}.align-self-xl-baseline{align-self:baseline!important}.align-self-xl-stretch{align-self:stretch!important}.order-xl-first{order:-1!important}.order-xl-0{order:0!important}.order-xl-1{order:1!important}.order-xl-2{order:2!important}.order-xl-3{order:3!important}.order-xl-4{order:4!important}.order-xl-5{order:5!important}.order-xl-last{order:6!important}.m-xl-0{margin:0!important}.m-xl-1{margin:.25rem!important}.m-xl-2{margin:.5rem!important}.m-xl-3{margin:1rem!important}.m-xl-4{margin:1.5rem!important}.m-xl-5{margin:3rem!important}.m-xl-auto{margin:auto!important}.mx-xl-0{margin-right:0!important;margin-left:0!important}.mx-xl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xl-auto{margin-right:auto!important;margin-left:auto!important}.my-xl-0{margin-top:0!important;margin-bottom:0!important}.my-xl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xl-0{margin-top:0!important}.mt-xl-1{margin-top:.25rem!important}.mt-xl-2{margin-top:.5rem!important}.mt-xl-3{margin-top:1rem!important}.mt-xl-4{margin-top:1.5rem!important}.mt-xl-5{margin-top:3rem!important}.mt-xl-auto{margin-top:auto!important}.me-xl-0{margin-right:0!important}.me-xl-1{margin-right:.25rem!important}.me-xl-2{margin-right:.5rem!important}.me-xl-3{margin-right:1rem!important}.me-xl-4{margin-right:1.5rem!important}.me-xl-5{margin-right:3rem!important}.me-xl-auto{margin-right:auto!important}.mb-xl-0{margin-bottom:0!important}.mb-xl-1{margin-bottom:.25rem!important}.mb-xl-2{margin-bottom:.5rem!important}.mb-xl-3{margin-bottom:1rem!important}.mb-xl-4{margin-bottom:1.5rem!important}.mb-xl-5{margin-bottom:3rem!important}.mb-xl-auto{margin-bottom:auto!important}.ms-xl-0{margin-left:0!important}.ms-xl-1{margin-left:.25rem!important}.ms-xl-2{margin-left:.5rem!important}.ms-xl-3{margin-left:1rem!important}.ms-xl-4{margin-left:1.5rem!important}.ms-xl-5{margin-left:3rem!important}.ms-xl-auto{margin-left:auto!important}.p-xl-0{padding:0!important}.p-xl-1{padding:.25rem!important}.p-xl-2{padding:.5rem!important}.p-xl-3{padding:1rem!important}.p-xl-4{padding:1.5rem!important}.p-xl-5{padding:3rem!important}.px-xl-0{padding-right:0!important;padding-left:0!important}.px-xl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xl-0{padding-top:0!important;padding-bottom:0!important}.py-xl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xl-0{padding-top:0!important}.pt-xl-1{padding-top:.25rem!important}.pt-xl-2{padding-top:.5rem!important}.pt-xl-3{padding-top:1rem!important}.pt-xl-4{padding-top:1.5rem!important}.pt-xl-5{padding-top:3rem!important}.pe-xl-0{padding-right:0!important}.pe-xl-1{padding-right:.25rem!important}.pe-xl-2{padding-right:.5rem!important}.pe-xl-3{padding-right:1rem!important}.pe-xl-4{padding-right:1.5rem!important}.pe-xl-5{padding-right:3rem!important}.pb-xl-0{padding-bottom:0!important}.pb-xl-1{padding-bottom:.25rem!important}.pb-xl-2{padding-bottom:.5rem!important}.pb-xl-3{padding-bottom:1rem!important}.pb-xl-4{padding-bottom:1.5rem!important}.pb-xl-5{padding-bottom:3rem!important}.ps-xl-0{padding-left:0!important}.ps-xl-1{padding-left:.25rem!important}.ps-xl-2{padding-left:.5rem!important}.ps-xl-3{padding-left:1rem!important}.ps-xl-4{padding-left:1.5rem!important}.ps-xl-5{padding-left:3rem!important}.text-xl-start{text-align:left!important}.text-xl-end{text-align:right!important}.text-xl-center{text-align:center!important}}@media (min-width:1400px){.float-xxl-start{float:left!important}.float-xxl-end{float:right!important}.float-xxl-none{float:none!important}.d-xxl-inline{display:inline!important}.d-xxl-inline-block{display:inline-block!important}.d-xxl-block{display:block!important}.d-xxl-grid{display:grid!important}.d-xxl-table{display:table!important}.d-xxl-table-row{display:table-row!important}.d-xxl-table-cell{display:table-cell!important}.d-xxl-flex{display:flex!important}.d-xxl-inline-flex{display:inline-flex!important}.d-xxl-none{display:none!important}.flex-xxl-fill{flex:1 1 auto!important}.flex-xxl-row{flex-direction:row!important}.flex-xxl-column{flex-direction:column!important}.flex-xxl-row-reverse{flex-direction:row-reverse!important}.flex-xxl-column-reverse{flex-direction:column-reverse!important}.flex-xxl-grow-0{flex-grow:0!important}.flex-xxl-grow-1{flex-grow:1!important}.flex-xxl-shrink-0{flex-shrink:0!important}.flex-xxl-shrink-1{flex-shrink:1!important}.flex-xxl-wrap{flex-wrap:wrap!important}.flex-xxl-nowrap{flex-wrap:nowrap!important}.flex-xxl-wrap-reverse{flex-wrap:wrap-reverse!important}.gap-xxl-0{gap:0!important}.gap-xxl-1{gap:.25rem!important}.gap-xxl-2{gap:.5rem!important}.gap-xxl-3{gap:1rem!important}.gap-xxl-4{gap:1.5rem!important}.gap-xxl-5{gap:3rem!important}.justify-content-xxl-start{justify-content:flex-start!important}.justify-content-xxl-end{justify-content:flex-end!important}.justify-content-xxl-center{justify-content:center!important}.justify-content-xxl-between{justify-content:space-between!important}.justify-content-xxl-around{justify-content:space-around!important}.justify-content-xxl-evenly{justify-content:space-evenly!important}.align-items-xxl-start{align-items:flex-start!important}.align-items-xxl-end{align-items:flex-end!important}.align-items-xxl-center{align-items:center!important}.align-items-xxl-baseline{align-items:baseline!important}.align-items-xxl-stretch{align-items:stretch!important}.align-content-xxl-start{align-content:flex-start!important}.align-content-xxl-end{align-content:flex-end!important}.align-content-xxl-center{align-content:center!important}.align-content-xxl-between{align-content:space-between!important}.align-content-xxl-around{align-content:space-around!important}.align-content-xxl-stretch{align-content:stretch!important}.align-self-xxl-auto{align-self:auto!important}.align-self-xxl-start{align-self:flex-start!important}.align-self-xxl-end{align-self:flex-end!important}.align-self-xxl-center{align-self:center!important}.align-self-xxl-baseline{align-self:baseline!important}.align-self-xxl-stretch{align-self:stretch!important}.order-xxl-first{order:-1!important}.order-xxl-0{order:0!important}.order-xxl-1{order:1!important}.order-xxl-2{order:2!important}.order-xxl-3{order:3!important}.order-xxl-4{order:4!important}.order-xxl-5{order:5!important}.order-xxl-last{order:6!important}.m-xxl-0{margin:0!important}.m-xxl-1{margin:.25rem!important}.m-xxl-2{margin:.5rem!important}.m-xxl-3{margin:1rem!important}.m-xxl-4{margin:1.5rem!important}.m-xxl-5{margin:3rem!important}.m-xxl-auto{margin:auto!important}.mx-xxl-0{margin-right:0!important;margin-left:0!important}.mx-xxl-1{margin-right:.25rem!important;margin-left:.25rem!important}.mx-xxl-2{margin-right:.5rem!important;margin-left:.5rem!important}.mx-xxl-3{margin-right:1rem!important;margin-left:1rem!important}.mx-xxl-4{margin-right:1.5rem!important;margin-left:1.5rem!important}.mx-xxl-5{margin-right:3rem!important;margin-left:3rem!important}.mx-xxl-auto{margin-right:auto!important;margin-left:auto!important}.my-xxl-0{margin-top:0!important;margin-bottom:0!important}.my-xxl-1{margin-top:.25rem!important;margin-bottom:.25rem!important}.my-xxl-2{margin-top:.5rem!important;margin-bottom:.5rem!important}.my-xxl-3{margin-top:1rem!important;margin-bottom:1rem!important}.my-xxl-4{margin-top:1.5rem!important;margin-bottom:1.5rem!important}.my-xxl-5{margin-top:3rem!important;margin-bottom:3rem!important}.my-xxl-auto{margin-top:auto!important;margin-bottom:auto!important}.mt-xxl-0{margin-top:0!important}.mt-xxl-1{margin-top:.25rem!important}.mt-xxl-2{margin-top:.5rem!important}.mt-xxl-3{margin-top:1rem!important}.mt-xxl-4{margin-top:1.5rem!important}.mt-xxl-5{margin-top:3rem!important}.mt-xxl-auto{margin-top:auto!important}.me-xxl-0{margin-right:0!important}.me-xxl-1{margin-right:.25rem!important}.me-xxl-2{margin-right:.5rem!important}.me-xxl-3{margin-right:1rem!important}.me-xxl-4{margin-right:1.5rem!important}.me-xxl-5{margin-right:3rem!important}.me-xxl-auto{margin-right:auto!important}.mb-xxl-0{margin-bottom:0!important}.mb-xxl-1{margin-bottom:.25rem!important}.mb-xxl-2{margin-bottom:.5rem!important}.mb-xxl-3{margin-bottom:1rem!important}.mb-xxl-4{margin-bottom:1.5rem!important}.mb-xxl-5{margin-bottom:3rem!important}.mb-xxl-auto{margin-bottom:auto!important}.ms-xxl-0{margin-left:0!important}.ms-xxl-1{margin-left:.25rem!important}.ms-xxl-2{margin-left:.5rem!important}.ms-xxl-3{margin-left:1rem!important}.ms-xxl-4{margin-left:1.5rem!important}.ms-xxl-5{margin-left:3rem!important}.ms-xxl-auto{margin-left:auto!important}.p-xxl-0{padding:0!important}.p-xxl-1{padding:.25rem!important}.p-xxl-2{padding:.5rem!important}.p-xxl-3{padding:1rem!important}.p-xxl-4{padding:1.5rem!important}.p-xxl-5{padding:3rem!important}.px-xxl-0{padding-right:0!important;padding-left:0!important}.px-xxl-1{padding-right:.25rem!important;padding-left:.25rem!important}.px-xxl-2{padding-right:.5rem!important;padding-left:.5rem!important}.px-xxl-3{padding-right:1rem!important;padding-left:1rem!important}.px-xxl-4{padding-right:1.5rem!important;padding-left:1.5rem!important}.px-xxl-5{padding-right:3rem!important;padding-left:3rem!important}.py-xxl-0{padding-top:0!important;padding-bottom:0!important}.py-xxl-1{padding-top:.25rem!important;padding-bottom:.25rem!important}.py-xxl-2{padding-top:.5rem!important;padding-bottom:.5rem!important}.py-xxl-3{padding-top:1rem!important;padding-bottom:1rem!important}.py-xxl-4{padding-top:1.5rem!important;padding-bottom:1.5rem!important}.py-xxl-5{padding-top:3rem!important;padding-bottom:3rem!important}.pt-xxl-0{padding-top:0!important}.pt-xxl-1{padding-top:.25rem!important}.pt-xxl-2{padding-top:.5rem!important}.pt-xxl-3{padding-top:1rem!important}.pt-xxl-4{padding-top:1.5rem!important}.pt-xxl-5{padding-top:3rem!important}.pe-xxl-0{padding-right:0!important}.pe-xxl-1{padding-right:.25rem!important}.pe-xxl-2{padding-right:.5rem!important}.pe-xxl-3{padding-right:1rem!important}.pe-xxl-4{padding-right:1.5rem!important}.pe-xxl-5{padding-right:3rem!important}.pb-xxl-0{padding-bottom:0!important}.pb-xxl-1{padding-bottom:.25rem!important}.pb-xxl-2{padding-bottom:.5rem!important}.pb-xxl-3{padding-bottom:1rem!important}.pb-xxl-4{padding-bottom:1.5rem!important}.pb-xxl-5{padding-bottom:3rem!important}.ps-xxl-0{padding-left:0!important}.ps-xxl-1{padding-left:.25rem!important}.ps-xxl-2{padding-left:.5rem!important}.ps-xxl-3{padding-left:1rem!important}.ps-xxl-4{padding-left:1.5rem!important}.ps-xxl-5{padding-left:3rem!important}.text-xxl-start{text-align:left!important}.text-xxl-end{text-align:right!important}.text-xxl-center{text-align:center!important}}@media (min-width:1200px){.fs-1{font-size:2.5rem!important}.fs-2{font-size:2rem!important}.fs-3{font-size:1.75rem!important}.fs-4{font-size:1.5rem!important}}@media print{.d-print-inline{display:inline!important}.d-print-inline-block{display:inline-block!important}.d-print-block{display:block!important}.d-print-grid{display:grid!important}.d-print-table{display:table!important}.d-print-table-row{display:table-row!important}.d-print-table-cell{display:table-cell!important}.d-print-flex{display:flex!important}.d-print-inline-flex{display:inline-flex!important}.d-print-none{display:none!important}} +/*# sourceMappingURL=bootstrap.min.css.map */ \ No newline at end of file diff --git a/wwwroot/bootstrap/bootstrap.min.css.map b/wwwroot/bootstrap/bootstrap.min.css.map new file mode 100644 index 0000000..afcd9e3 --- /dev/null +++ b/wwwroot/bootstrap/bootstrap.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../scss/bootstrap.scss","../../scss/_root.scss","../../scss/_reboot.scss","dist/css/bootstrap.css","../../scss/vendor/_rfs.scss","../../scss/mixins/_border-radius.scss","../../scss/_type.scss","../../scss/mixins/_lists.scss","../../scss/_images.scss","../../scss/mixins/_image.scss","../../scss/_containers.scss","../../scss/mixins/_container.scss","../../scss/mixins/_breakpoints.scss","../../scss/_grid.scss","../../scss/mixins/_grid.scss","../../scss/_tables.scss","../../scss/mixins/_table-variants.scss","../../scss/forms/_labels.scss","../../scss/forms/_form-text.scss","../../scss/forms/_form-control.scss","../../scss/mixins/_transition.scss","../../scss/mixins/_gradients.scss","../../scss/forms/_form-select.scss","../../scss/forms/_form-check.scss","../../scss/forms/_form-range.scss","../../scss/forms/_floating-labels.scss","../../scss/forms/_input-group.scss","../../scss/mixins/_forms.scss","../../scss/_buttons.scss","../../scss/mixins/_buttons.scss","../../scss/_transitions.scss","../../scss/_dropdown.scss","../../scss/mixins/_caret.scss","../../scss/_button-group.scss","../../scss/_nav.scss","../../scss/_navbar.scss","../../scss/_card.scss","../../scss/_accordion.scss","../../scss/_breadcrumb.scss","../../scss/_pagination.scss","../../scss/mixins/_pagination.scss","../../scss/_badge.scss","../../scss/_alert.scss","../../scss/mixins/_alert.scss","../../scss/_progress.scss","../../scss/_list-group.scss","../../scss/mixins/_list-group.scss","../../scss/_close.scss","../../scss/_toasts.scss","../../scss/_modal.scss","../../scss/mixins/_backdrop.scss","../../scss/_tooltip.scss","../../scss/mixins/_reset-text.scss","../../scss/_popover.scss","../../scss/_carousel.scss","../../scss/mixins/_clearfix.scss","../../scss/_spinners.scss","../../scss/_offcanvas.scss","../../scss/_placeholders.scss","../../scss/helpers/_colored-links.scss","../../scss/helpers/_ratio.scss","../../scss/helpers/_position.scss","../../scss/helpers/_stacks.scss","../../scss/helpers/_visually-hidden.scss","../../scss/mixins/_visually-hidden.scss","../../scss/helpers/_stretched-link.scss","../../scss/helpers/_text-truncation.scss","../../scss/mixins/_text-truncate.scss","../../scss/helpers/_vr.scss","../../scss/mixins/_utilities.scss","../../scss/utilities/_api.scss"],"names":[],"mappings":"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","sourcesContent":["/*!\n * Bootstrap v5.1.0 (https://getbootstrap.com/)\n * Copyright 2011-2021 The Bootstrap Authors\n * Copyright 2011-2021 Twitter, Inc.\n * Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)\n */\n\n// scss-docs-start import-stack\n// Configuration\n@import \"functions\";\n@import \"variables\";\n@import \"mixins\";\n@import \"utilities\";\n\n// Layout & components\n@import \"root\";\n@import \"reboot\";\n@import \"type\";\n@import \"images\";\n@import \"containers\";\n@import \"grid\";\n@import \"tables\";\n@import \"forms\";\n@import \"buttons\";\n@import \"transitions\";\n@import \"dropdown\";\n@import \"button-group\";\n@import \"nav\";\n@import \"navbar\";\n@import \"card\";\n@import \"accordion\";\n@import \"breadcrumb\";\n@import \"pagination\";\n@import \"badge\";\n@import \"alert\";\n@import \"progress\";\n@import \"list-group\";\n@import \"close\";\n@import \"toasts\";\n@import \"modal\";\n@import \"tooltip\";\n@import \"popover\";\n@import \"carousel\";\n@import \"spinners\";\n@import \"offcanvas\";\n@import \"placeholders\";\n\n// Helpers\n@import \"helpers\";\n\n// Utilities\n@import \"utilities/api\";\n// scss-docs-end import-stack\n",":root {\n // Note: Custom variable values only support SassScript inside `#{}`.\n\n // Colors\n //\n // Generate palettes for full colors, grays, and theme colors.\n\n @each $color, $value in $colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $grays {\n --#{$variable-prefix}gray-#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors {\n --#{$variable-prefix}#{$color}: #{$value};\n }\n\n @each $color, $value in $theme-colors-rgb {\n --#{$variable-prefix}#{$color}-rgb: #{$value};\n }\n\n --#{$variable-prefix}white-rgb: #{to-rgb($white)};\n --#{$variable-prefix}black-rgb: #{to-rgb($black)};\n --#{$variable-prefix}body-rgb: #{to-rgb($body-color)};\n\n // Fonts\n\n // Note: Use `inspect` for lists so that quoted items keep the quotes.\n // See https://github.com/sass/sass/issues/2383#issuecomment-336349172\n --#{$variable-prefix}font-sans-serif: #{inspect($font-family-sans-serif)};\n --#{$variable-prefix}font-monospace: #{inspect($font-family-monospace)};\n --#{$variable-prefix}gradient: #{$gradient};\n\n // Root and body\n // stylelint-disable custom-property-empty-line-before\n // scss-docs-start root-body-variables\n @if $font-size-root != null {\n --#{$variable-prefix}root-font-size: #{$font-size-root};\n }\n --#{$variable-prefix}body-font-family: #{$font-family-base};\n --#{$variable-prefix}body-font-size: #{$font-size-base};\n --#{$variable-prefix}body-font-weight: #{$font-weight-base};\n --#{$variable-prefix}body-line-height: #{$line-height-base};\n --#{$variable-prefix}body-color: #{$body-color};\n @if $body-text-align != null {\n --#{$variable-prefix}body-text-align: #{$body-text-align};\n }\n --#{$variable-prefix}body-bg: #{$body-bg};\n // scss-docs-end root-body-variables\n // stylelint-enable custom-property-empty-line-before\n}\n","// stylelint-disable declaration-no-important, selector-no-qualifying-type, property-no-vendor-prefix\n\n\n// Reboot\n//\n// Normalization of HTML elements, manually forked from Normalize.css to remove\n// styles targeting irrelevant browsers while applying new styles.\n//\n// Normalize is licensed MIT. https://github.com/necolas/normalize.css\n\n\n// Document\n//\n// Change from `box-sizing: content-box` so that `width` is not affected by `padding` or `border`.\n\n*,\n*::before,\n*::after {\n box-sizing: border-box;\n}\n\n\n// Root\n//\n// Ability to the value of the root font sizes, affecting the value of `rem`.\n// null by default, thus nothing is generated.\n\n:root {\n @if $font-size-root != null {\n font-size: var(--#{$variable-prefix}-root-font-size);\n }\n\n @if $enable-smooth-scroll {\n @media (prefers-reduced-motion: no-preference) {\n scroll-behavior: smooth;\n }\n }\n}\n\n\n// Body\n//\n// 1. Remove the margin in all browsers.\n// 2. As a best practice, apply a default `background-color`.\n// 3. Prevent adjustments of font size after orientation changes in iOS.\n// 4. Change the default tap highlight to be completely transparent in iOS.\n\n// scss-docs-start reboot-body-rules\nbody {\n margin: 0; // 1\n font-family: var(--#{$variable-prefix}body-font-family);\n @include font-size(var(--#{$variable-prefix}body-font-size));\n font-weight: var(--#{$variable-prefix}body-font-weight);\n line-height: var(--#{$variable-prefix}body-line-height);\n color: var(--#{$variable-prefix}body-color);\n text-align: var(--#{$variable-prefix}body-text-align);\n background-color: var(--#{$variable-prefix}body-bg); // 2\n -webkit-text-size-adjust: 100%; // 3\n -webkit-tap-highlight-color: rgba($black, 0); // 4\n}\n// scss-docs-end reboot-body-rules\n\n\n// Content grouping\n//\n// 1. Reset Firefox's gray color\n// 2. Set correct height and prevent the `size` attribute to make the `hr` look like an input field\n\nhr {\n margin: $hr-margin-y 0;\n color: $hr-color; // 1\n background-color: currentColor;\n border: 0;\n opacity: $hr-opacity;\n}\n\nhr:not([size]) {\n height: $hr-height; // 2\n}\n\n\n// Typography\n//\n// 1. Remove top margins from headings\n// By default, `

`-`

` all receive top and bottom margins. We nuke the top\n// margin for easier control within type scales as it avoids margin collapsing.\n\n%heading {\n margin-top: 0; // 1\n margin-bottom: $headings-margin-bottom;\n font-family: $headings-font-family;\n font-style: $headings-font-style;\n font-weight: $headings-font-weight;\n line-height: $headings-line-height;\n color: $headings-color;\n}\n\nh1 {\n @extend %heading;\n @include font-size($h1-font-size);\n}\n\nh2 {\n @extend %heading;\n @include font-size($h2-font-size);\n}\n\nh3 {\n @extend %heading;\n @include font-size($h3-font-size);\n}\n\nh4 {\n @extend %heading;\n @include font-size($h4-font-size);\n}\n\nh5 {\n @extend %heading;\n @include font-size($h5-font-size);\n}\n\nh6 {\n @extend %heading;\n @include font-size($h6-font-size);\n}\n\n\n// Reset margins on paragraphs\n//\n// Similarly, the top margin on `

`s get reset. However, we also reset the\n// bottom margin to use `rem` units instead of `em`.\n\np {\n margin-top: 0;\n margin-bottom: $paragraph-margin-bottom;\n}\n\n\n// Abbreviations\n//\n// 1. Duplicate behavior to the data-bs-* attribute for our tooltip plugin\n// 2. Add the correct text decoration in Chrome, Edge, Opera, and Safari.\n// 3. Add explicit cursor to indicate changed behavior.\n// 4. Prevent the text-decoration to be skipped.\n\nabbr[title],\nabbr[data-bs-original-title] { // 1\n text-decoration: underline dotted; // 2\n cursor: help; // 3\n text-decoration-skip-ink: none; // 4\n}\n\n\n// Address\n\naddress {\n margin-bottom: 1rem;\n font-style: normal;\n line-height: inherit;\n}\n\n\n// Lists\n\nol,\nul {\n padding-left: 2rem;\n}\n\nol,\nul,\ndl {\n margin-top: 0;\n margin-bottom: 1rem;\n}\n\nol ol,\nul ul,\nol ul,\nul ol {\n margin-bottom: 0;\n}\n\ndt {\n font-weight: $dt-font-weight;\n}\n\n// 1. Undo browser default\n\ndd {\n margin-bottom: .5rem;\n margin-left: 0; // 1\n}\n\n\n// Blockquote\n\nblockquote {\n margin: 0 0 1rem;\n}\n\n\n// Strong\n//\n// Add the correct font weight in Chrome, Edge, and Safari\n\nb,\nstrong {\n font-weight: $font-weight-bolder;\n}\n\n\n// Small\n//\n// Add the correct font size in all browsers\n\nsmall {\n @include font-size($small-font-size);\n}\n\n\n// Mark\n\nmark {\n padding: $mark-padding;\n background-color: $mark-bg;\n}\n\n\n// Sub and Sup\n//\n// Prevent `sub` and `sup` elements from affecting the line height in\n// all browsers.\n\nsub,\nsup {\n position: relative;\n @include font-size($sub-sup-font-size);\n line-height: 0;\n vertical-align: baseline;\n}\n\nsub { bottom: -.25em; }\nsup { top: -.5em; }\n\n\n// Links\n\na {\n color: $link-color;\n text-decoration: $link-decoration;\n\n &:hover {\n color: $link-hover-color;\n text-decoration: $link-hover-decoration;\n }\n}\n\n// And undo these styles for placeholder links/named anchors (without href).\n// It would be more straightforward to just use a[href] in previous block, but that\n// causes specificity issues in many other styles that are too complex to fix.\n// See https://github.com/twbs/bootstrap/issues/19402\n\na:not([href]):not([class]) {\n &,\n &:hover {\n color: inherit;\n text-decoration: none;\n }\n}\n\n\n// Code\n\npre,\ncode,\nkbd,\nsamp {\n font-family: $font-family-code;\n @include font-size(1em); // Correct the odd `em` font sizing in all browsers.\n direction: ltr #{\"/* rtl:ignore */\"};\n unicode-bidi: bidi-override;\n}\n\n// 1. Remove browser default top margin\n// 2. Reset browser default of `1em` to use `rem`s\n// 3. Don't allow content to break outside\n\npre {\n display: block;\n margin-top: 0; // 1\n margin-bottom: 1rem; // 2\n overflow: auto; // 3\n @include font-size($code-font-size);\n color: $pre-color;\n\n // Account for some code outputs that place code tags in pre tags\n code {\n @include font-size(inherit);\n color: inherit;\n word-break: normal;\n }\n}\n\ncode {\n @include font-size($code-font-size);\n color: $code-color;\n word-wrap: break-word;\n\n // Streamline the style when inside anchors to avoid broken underline and more\n a > & {\n color: inherit;\n }\n}\n\nkbd {\n padding: $kbd-padding-y $kbd-padding-x;\n @include font-size($kbd-font-size);\n color: $kbd-color;\n background-color: $kbd-bg;\n @include border-radius($border-radius-sm);\n\n kbd {\n padding: 0;\n @include font-size(1em);\n font-weight: $nested-kbd-font-weight;\n }\n}\n\n\n// Figures\n//\n// Apply a consistent margin strategy (matches our type styles).\n\nfigure {\n margin: 0 0 1rem;\n}\n\n\n// Images and content\n\nimg,\nsvg {\n vertical-align: middle;\n}\n\n\n// Tables\n//\n// Prevent double borders\n\ntable {\n caption-side: bottom;\n border-collapse: collapse;\n}\n\ncaption {\n padding-top: $table-cell-padding-y;\n padding-bottom: $table-cell-padding-y;\n color: $table-caption-color;\n text-align: left;\n}\n\n// 1. Removes font-weight bold by inheriting\n// 2. Matches default `` alignment by inheriting `text-align`.\n// 3. Fix alignment for Safari\n\nth {\n font-weight: $table-th-font-weight; // 1\n text-align: inherit; // 2\n text-align: -webkit-match-parent; // 3\n}\n\nthead,\ntbody,\ntfoot,\ntr,\ntd,\nth {\n border-color: inherit;\n border-style: solid;\n border-width: 0;\n}\n\n\n// Forms\n//\n// 1. Allow labels to use `margin` for spacing.\n\nlabel {\n display: inline-block; // 1\n}\n\n// Remove the default `border-radius` that macOS Chrome adds.\n// See https://github.com/twbs/bootstrap/issues/24093\n\nbutton {\n // stylelint-disable-next-line property-disallowed-list\n border-radius: 0;\n}\n\n// Explicitly remove focus outline in Chromium when it shouldn't be\n// visible (e.g. as result of mouse click or touch tap). It already\n// should be doing this automatically, but seems to currently be\n// confused and applies its very visible two-tone outline anyway.\n\nbutton:focus:not(:focus-visible) {\n outline: 0;\n}\n\n// 1. Remove the margin in Firefox and Safari\n\ninput,\nbutton,\nselect,\noptgroup,\ntextarea {\n margin: 0; // 1\n font-family: inherit;\n @include font-size(inherit);\n line-height: inherit;\n}\n\n// Remove the inheritance of text transform in Firefox\nbutton,\nselect {\n text-transform: none;\n}\n// Set the cursor for non-`