From aa2146e3834e7299449c4b5c56eb1cf620a11930 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=C4=83ng=20Nguy=E1=BB=85n?= Date: Thu, 30 Oct 2025 13:34:44 +0700 Subject: [PATCH] update --- RobotApp.Client/MainLayout.razor | 1 + .../Pages/Components/Config/RobotConfig.razor | 63 ++ .../Components/Config/RobotPLCConfig.razor | 50 + .../Components/Config/RobotSafetyConfig.razor | 69 ++ .../Config/RobotSimulationConfig.razor | 59 ++ .../Config/RobotVDA5050Config.razor | 81 ++ .../Components/Mapping/RobotInfomation.razor | 55 +- .../Mapping/RobotInfomation.razor.css | 116 ++- .../Pages/RobotConfigManager.razor | 208 ++++ .../Pages/RobotConfigManager.razor.css | 375 +++++++ RobotApp.Client/_Imports.razor | 2 + RobotApp.Common.Shares/Dtos/RobotConfigDto.cs | 24 +- .../Dtos/RobotPlcConfigDto.cs | 22 +- .../Dtos/RobotSafetyConfig.cs | 20 - .../Dtos/RobotSafetyConfigDto.cs | 46 + .../Dtos/RobotSimulationConfigDto.cs | 24 +- .../Dtos/RobotVDA5050ConfigDto.cs | 36 +- .../Enums/RobotConfigType.cs | 10 + .../Controllers/RobotConfigsController.cs | 944 +++++++++++++++++- RobotApp/Data/ApplicationDbContext.cs | 10 +- RobotApp/Data/ApplicationDbExtensions.cs | 22 + ...51029064620_InitConfigSafetyDb.Designer.cs | 565 +++++++++++ .../20251029064620_InitConfigSafetyDb.cs | 45 + ...1029064649_InitConfigSafety1Db.Designer.cs | 565 +++++++++++ .../20251029064649_InitConfigSafety1Db.cs | 22 + .../ApplicationDbContextModelSnapshot.cs | 62 ++ RobotApp/Data/RobotConfig.cs | 2 +- RobotApp/Data/RobotPlcConfig.cs | 2 +- RobotApp/Data/RobotSafetyConfig.cs | 2 +- RobotApp/Data/RobotSimulationConfig.cs | 2 +- RobotApp/Data/RobotVDA5050Config.cs | 4 +- RobotApp/Services/MQTTClient.cs | 1 - RobotApp/Services/Robot/RobotConfiguration.cs | 175 +++- .../Robot/RobotControllerInitialize.cs | 6 +- RobotApp/Services/Robot/RobotLocalization.cs | 4 +- RobotApp/Services/Robot/RobotNavigation.cs | 6 +- RobotApp/Services/Robot/RobotPeripheral.cs | 1 + .../Navigation/DifferentialNavigation.cs | 8 +- .../Navigation/SimulationNavigation.cs | 2 +- .../Robot/Simulation/SimulationModel.cs | 16 +- .../Simulation/SimulationVisualization.cs | 4 +- RobotApp/Services/RobotExtensions.cs | 2 +- RobotApp/robot.db | Bin 167936 -> 167936 bytes 43 files changed, 3637 insertions(+), 96 deletions(-) create mode 100644 RobotApp.Client/Pages/Components/Config/RobotConfig.razor create mode 100644 RobotApp.Client/Pages/Components/Config/RobotPLCConfig.razor create mode 100644 RobotApp.Client/Pages/Components/Config/RobotSafetyConfig.razor create mode 100644 RobotApp.Client/Pages/Components/Config/RobotSimulationConfig.razor create mode 100644 RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor create mode 100644 RobotApp.Client/Pages/RobotConfigManager.razor create mode 100644 RobotApp.Client/Pages/RobotConfigManager.razor.css delete mode 100644 RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs create mode 100644 RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs create mode 100644 RobotApp.Common.Shares/Enums/RobotConfigType.cs create mode 100644 RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.Designer.cs create mode 100644 RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.cs create mode 100644 RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.Designer.cs create mode 100644 RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.cs diff --git a/RobotApp.Client/MainLayout.razor b/RobotApp.Client/MainLayout.razor index 373fcfd..1528768 100644 --- a/RobotApp.Client/MainLayout.razor +++ b/RobotApp.Client/MainLayout.razor @@ -71,6 +71,7 @@ public NavModel[] Navs = [ new(){Icon = "mdi-view-dashboard", Path="/", Label = "Dashboard", Match = NavLinkMatch.All}, new(){Icon = "mdi-map-legend", Path="/maps-manager", Label = "Mapping", Match = NavLinkMatch.All}, + new(){Icon = "mdi-application-cog", Path="/robot-config", Label = "Config", Match = NavLinkMatch.All}, ]; private bool collapseNavMenu = true; diff --git a/RobotApp.Client/Pages/Components/Config/RobotConfig.razor b/RobotApp.Client/Pages/Components/Config/RobotConfig.razor new file mode 100644 index 0000000..fabfda2 --- /dev/null +++ b/RobotApp.Client/Pages/Components/Config/RobotConfig.razor @@ -0,0 +1,63 @@ + + + +
+ + + @foreach (var t in NavigationTypes) + { + + } + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ +@code { + [Parameter] + public RobotConfigDto Model { get; set; } = new(); + + [Parameter] + public EventCallback ModelChanged { get; set; } + + private RobotConfigDto Config = new(); + private IEnumerable NavigationTypes => Enum.GetValues(typeof(NavigationType)).Cast(); + + protected override void OnParametersSet() + { + Config = Model ?? new(); + } + + private async Task OnSubmit() + { + Model = Config; + await ModelChanged.InvokeAsync(Model); + } +} diff --git a/RobotApp.Client/Pages/Components/Config/RobotPLCConfig.razor b/RobotApp.Client/Pages/Components/Config/RobotPLCConfig.razor new file mode 100644 index 0000000..ad80fbe --- /dev/null +++ b/RobotApp.Client/Pages/Components/Config/RobotPLCConfig.razor @@ -0,0 +1,50 @@ +@using RobotApp.Common.Shares.Dtos + + + + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+ + +
+ +
+ +@code { + [Parameter] + public RobotPlcConfigDto Model { get; set; } = new(); + + [Parameter] + public EventCallback ModelChanged { get; set; } + + private RobotPlcConfigDto Local = new(); + + protected override void OnParametersSet() + { + Local = Model is not null ? Model with { } : new RobotPlcConfigDto(); + } + + private async Task OnSubmit() + { + Model = Local; + await ModelChanged.InvokeAsync(Model); + } +} \ No newline at end of file diff --git a/RobotApp.Client/Pages/Components/Config/RobotSafetyConfig.razor b/RobotApp.Client/Pages/Components/Config/RobotSafetyConfig.razor new file mode 100644 index 0000000..bf8424e --- /dev/null +++ b/RobotApp.Client/Pages/Components/Config/RobotSafetyConfig.razor @@ -0,0 +1,69 @@ + + + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ +@code { + [Parameter] + public RobotSafetyConfigDto Model { get; set; } = new(); + + [Parameter] + public EventCallback ModelChanged { get; set; } + + private RobotSafetyConfigDto Local = new(); + + protected override void OnParametersSet() + { + // Work on a shallow copy (record) so parent instance isn't mutated until submit + Local = Model is not null ? Model with { } : new RobotSafetyConfigDto(); + } + + private async Task OnSubmit() + { + Model = Local; + await ModelChanged.InvokeAsync(Model); + } +} diff --git a/RobotApp.Client/Pages/Components/Config/RobotSimulationConfig.razor b/RobotApp.Client/Pages/Components/Config/RobotSimulationConfig.razor new file mode 100644 index 0000000..a4f839d --- /dev/null +++ b/RobotApp.Client/Pages/Components/Config/RobotSimulationConfig.razor @@ -0,0 +1,59 @@ +@using RobotApp.Common.Shares.Dtos + + + + +
+ + +
+ +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+
+ +@code { + [Parameter] + public RobotSimulationConfigDto Model { get; set; } = new(); + + [Parameter] + public EventCallback ModelChanged { get; set; } + + private RobotSimulationConfigDto Local = new(); + + protected override void OnParametersSet() + { + // Use record 'with' to create a shallow copy so parent isn't mutated until submit + Local = Model is not null ? Model with { } : new RobotSimulationConfigDto(); + } + + private async Task OnSubmit() + { + Model = Local; + await ModelChanged.InvokeAsync(Model); + } +} \ No newline at end of file diff --git a/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor b/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor new file mode 100644 index 0000000..798210f --- /dev/null +++ b/RobotApp.Client/Pages/Components/Config/RobotVDA5050Config.razor @@ -0,0 +1,81 @@ + + + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +@code { + [Parameter] + public RobotVDA5050ConfigDto Model { get; set; } = new(); + + [Parameter] + public EventCallback ModelChanged { get; set; } + + private RobotVDA5050ConfigDto Local = new(); + + protected override void OnParametersSet() + { + Local = Model is not null ? Model with { } : new RobotVDA5050ConfigDto(); + } + + private async Task OnSubmit() + { + Model = Local; + await ModelChanged.InvokeAsync(Model); + } +} diff --git a/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor b/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor index 699c3e6..0b01d75 100644 --- a/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor +++ b/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor @@ -1,7 +1,60 @@ 
-

Infomations

+

Informations

+ +
+
+
+
X (m)
+
@Localization.X.ToString("F3")
+ +
Y (m)
+
@Localization.Y.ToString("F3")
+ +
Theta (rad)
+
@Localization.Theta.ToString("F4")
+ +
Theta (deg)
+
@($"{Localization.Theta * 180.0 / Math.PI:F2}°")
+ +
Ready
+
@((Localization.IsReady) ? "Yes" : "No")
+
+
+ +
+
+
SlamState
+
@Localization.SlamState
+ +
SlamDetail
+
@Localization.SlamStateDetail
+ +
Active Map
+
@Localization.CurrentActiveMap
+ +
Reliability
+
@($"{Localization.Reliability:F1}%")
+ +
MatchingScore
+
@($"{Localization.MatchingScore:F1}%")
+
+
+
@code { + private class LocalizationDto + { + public bool IsReady { get; set; } + public double X { get; set; } + public double Y { get; set; } + public double Theta { get; set; } + public string SlamState { get; set; } = "Localization"; + public string SlamStateDetail { get; set; } = "/r/n"; + public string CurrentActiveMap { get; set; } = "Localization"; + public double Reliability { get; set; } + public double MatchingScore { get; set; } + } + private LocalizationDto Localization = new(); } diff --git a/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor.css b/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor.css index 350005c..7ea543a 100644 --- a/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor.css +++ b/RobotApp.Client/Pages/Components/Mapping/RobotInfomation.razor.css @@ -5,9 +5,117 @@ display: flex; flex-direction: column; position: relative; - background-color: var(--mud-palette-surface); - border-radius: var(--mud-default-borderradius); + background-color: var(--mud-palette-surface, #ffffff); + border-radius: var(--mud-default-borderradius, 8px); transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms; - box-shadow: var(--mud-elevation-10); - padding: 15px; + box-shadow: var(--mud-elevation-10, 0 4px 12px rgba(0,0,0,0.08)); + padding: 12px; + box-sizing: border-box; + border: 1px solid rgba(0,0,0,0.04); +} + + .view::before { + content: ""; + position: absolute; + left: 0; + top: 0; + bottom: 0; + width: 6px; + border-top-left-radius: inherit; + border-bottom-left-radius: inherit; + background: linear-gradient(180deg, var(--mud-palette-primary, #1976d2), rgba(25,118,210,0.7)); + } + +.info-title { + margin: 0 0 10px 12px; + font-weight: 600; + font-size: 1rem; + color: var(--mud-palette-primary, #1976d2); +} + +.info-grid { + display: flex; + gap: 14px; + align-items: flex-start; + flex: 1 1 auto; + overflow: hidden; + padding: 1.5em; +} + +.info-col { + flex: 1 1 50%; + min-width: 0; + overflow: hidden; +} + +.info-list { + margin: 0; + padding: 0; +} + + .info-list dt, + .info-list dd { + display: flex; + align-items: center; + padding: 6px 0; + font-size: 0.95rem; + line-height: 1.2; + } + +.info-term { + width: 42%; + text-align: right; + padding-right: 12px; + color: var(--mud-palette-text-secondary, rgba(0,0,0,0.6)); + font-weight: 600; + box-sizing: border-box; + white-space: nowrap; +} + +.info-desc { + width: 58%; + text-align: left; + font-weight: 700; + color: var(--mud-palette-text-primary, rgba(0,0,0,0.85)); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-variant-numeric: tabular-nums; +} + +.text-truncate { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.info-list dt + dd { + border-bottom: 1px dashed rgba(0,0,0,0.03); +} + +.ready-yes { + color: var(--mud-palette-success, #2e7d32); +} + +.ready-no { + color: var(--mud-palette-error, #d32f2f); +} + +.percent { + color: var(--mud-palette-primary, #1976d2); +} + +@media (max-width: 640px) { + .info-grid { + flex-direction: column; + gap: 8px; + } + + .info-term { + width: 45%; + } + + .info-desc { + width: 55%; + } } diff --git a/RobotApp.Client/Pages/RobotConfigManager.razor b/RobotApp.Client/Pages/RobotConfigManager.razor new file mode 100644 index 0000000..5b09ce1 --- /dev/null +++ b/RobotApp.Client/Pages/RobotConfigManager.razor @@ -0,0 +1,208 @@ +@page "/robot-config" +@rendermode InteractiveWebAssemblyNoPrerender +@attribute [Authorize] + +@using RobotApp.Common.Shares.Dtos +@using RobotApp.Common.Shares.Enums +@inject HttpClient Http + +Robot Configuration + +
+
+
+ +
+ + +
+
+ +
+
+ + + + + +
+
+
+
+
+
+ @switch (SelectedType) + { + case RobotConfigType.VDA5050: + @RenderList(VdaConfigs, () => SelectVda) + break; + case RobotConfigType.Safety: + @RenderList(SafetyConfigs, () => SelectSafety) + break; + case RobotConfigType.Simulation: + @RenderList(SimulationConfigs, () => SelectSimulation) + break; + case RobotConfigType.PLC: + @RenderList(PlcConfigs, () => SelectPlc) + break; + case RobotConfigType.Core: + @RenderList(CoreConfigs, () => SelectCore) + break; + default: +
No configs.
+ break; + } +
+
+ +
+
+ @if (!HasSelection) + { +
Select a config from the list or click Add to create one.
+ } + else + { + @switch (SelectedType) + { + case RobotConfigType.VDA5050: + + break; + case RobotConfigType.Safety: + + break; + case RobotConfigType.Simulation: + + break; + case RobotConfigType.PLC: + + break; + case RobotConfigType.Core: + + break; + } + } +
+
+
+
+ +@code { + private List VdaConfigs { get; } = []; + private List SafetyConfigs { get; } = []; + private List SimulationConfigs { get; } = []; + private List PlcConfigs { get; } = []; + private List CoreConfigs { get; } = []; + + private RobotVDA5050ConfigDto? SelectedVda { get; set; } + private RobotSafetyConfigDto? SelectedSafety { get; set; } + private RobotSimulationConfigDto? SelectedSimulation { get; set; } + private RobotPlcConfigDto? SelectedPlc { get; set; } + private RobotConfigDto? SelectedCore { get; set; } + + private RobotConfigType SelectedType { get; set; } = RobotConfigType.VDA5050; + + private int SelectedIndex { get; set; } = -1; + + private bool HasSelection => SelectedIndex >= 0; + + RenderFragment RenderList(List list, Func> selectFactory) where T : class + { + return builder => + { + if (list is null || !list.Any()) + { + builder.OpenElement(0, "div"); + builder.AddAttribute(1, "class", "p-2 text-muted"); + builder.AddContent(2, "No configs found."); + builder.CloseElement(); + return; + } + + builder.OpenElement(3, "ul"); + builder.AddAttribute(4, "class", "list-group list-group-flush"); + + for (int i = 0; i < list.Count; i++) + { + var item = list[i]; + var idx = i; + builder.OpenElement(10 + i * 5, "li"); + builder.AddAttribute(11 + i * 5, "class", $"list-group-item {(SelectedIndex==idx ? "active" : "")}"); + builder.AddAttribute(12 + i * 5, "style", "cursor:pointer;padding:0.75rem 1rem;"); + builder.AddAttribute(13 + i * 5, "onclick", EventCallback.Factory.Create(this, () => selectFactory()(idx))); + + // Only show ConfigName + var nameProp = item.GetType().GetProperty("ConfigName"); + var name = nameProp?.GetValue(item)?.ToString() ?? "Unnamed"; + builder.AddContent(14 + i * 5, name); + + // Show active badge at end if IsActive == true + var isActiveProp = item.GetType().GetProperty("IsActive"); + var isActive = isActiveProp?.GetValue(item) is bool b && b; + if (isActive) + { + builder.OpenElement(15 + i * 5, "span"); + builder.AddAttribute(16 + i * 5, "class", "badge bg-success ms-2 float-end"); + builder.AddContent(17 + i * 5, "Active"); + builder.CloseElement(); + } + + builder.CloseElement(); + } + + builder.CloseElement(); + }; + } + + private Action SelectVda => idx => + { + SelectedIndex = idx; + SelectedVda = idx >= 0 && idx < VdaConfigs.Count ? VdaConfigs[idx] with { } : null; + StateHasChanged(); + }; + + private Action SelectSafety => idx => + { + SelectedIndex = idx; + SelectedSafety = idx >= 0 && idx < SafetyConfigs.Count ? SafetyConfigs[idx] with { } : null; + StateHasChanged(); + }; + + private Action SelectSimulation => idx => + { + SelectedIndex = idx; + SelectedSimulation = idx >= 0 && idx < SimulationConfigs.Count ? SimulationConfigs[idx] with { } : null; + StateHasChanged(); + }; + + private Action SelectPlc => idx => + { + SelectedIndex = idx; + SelectedPlc = idx >= 0 && idx < PlcConfigs.Count ? PlcConfigs[idx] with { } : null; + StateHasChanged(); + }; + + private Action SelectCore => idx => + { + SelectedIndex = idx; + SelectedCore = idx >= 0 && idx < CoreConfigs.Count ? CoreConfigs[idx] with { } : null; + StateHasChanged(); + }; + + +} diff --git a/RobotApp.Client/Pages/RobotConfigManager.razor.css b/RobotApp.Client/Pages/RobotConfigManager.razor.css new file mode 100644 index 0000000..315ca52 --- /dev/null +++ b/RobotApp.Client/Pages/RobotConfigManager.razor.css @@ -0,0 +1,375 @@ +/* Toolbar layout */ +.rcm-toolbar { + display: flex; + gap: 1rem; + align-items: center; + justify-content: space-between; + background: linear-gradient(180deg, rgba(30,30,30,0.95), rgba(20,20,20,0.95)); + color: #e6e6e6; + padding: 0.75rem 1rem; + border-radius: 0.375rem; + box-shadow: 0 2px 6px rgba(0,0,0,0.6); + border: 1px solid rgba(255,255,255,0.04); +} + +/* Content layout: take remaining height */ +.rcm-content { + height: calc(100vh - 90px); /* approximate toolbar + padding height; adjust if needed */ + min-height: 0; /* allow flex children to shrink properly */ +} + +/* Config list uses flexible basis and can shrink instead of forcing overflow */ +.rcm-config-list { + /* replace fixed width with flex basis + shrink */ + flex: 0 1 35%; /* flex-grow:0, flex-shrink:1, flex-basis:35% */ + min-width: 240px; /* allow smaller than before but keep readable */ + display: flex; + flex-direction: column; + min-height: 0; +} + +/* Config content uses remaining space and can shrink/grow */ +.rcm-config-content { + flex: 1 1 65%; /* flex-grow:1 to take remaining, flex-shrink:1 */ + min-width: 220px; + display: flex; + flex-direction: column; +} + +/* Make card body scrollable if content overflows */ +.config-list .list-body, +.rcm-config-content .card-body, +.config-list .list-body ul { + overflow: auto; +} + +/* Ensure list-body and editor body expand to fill container */ +.config-list .list-body { + height: 100%; +} + +.rcm-config-content .card-body { + height: 100%; +} + +/* Make config list body fill available height and scroll when content overflows */ +.rcm-config-list .card-body.list-body { + flex: 1 1 auto; /* expand to fill container */ + min-height: 0; /* allow flex children to shrink in many browsers */ + overflow: auto; /* enable scrolling when content overflows */ + padding: 0.25rem 0.5rem; /* keep slight padding for list */ +} + +/* Ensure the list itself doesn't add extra margins that affect scrolling */ +.rcm-config-list .list-group { + margin: 0; + padding: 0; +} + +/* Each list item keeps its padding but stays in flow */ +.rcm-config-list .list-group-item { + padding: 0.75rem 1rem; +} + +/* List items spacing */ +.list-group-item { + cursor: pointer; +} + +/* Left side controls (robot id + select) */ +.rcm-toolbar-left { + display: flex; + gap: 0.5rem; + align-items: center; + flex-wrap: wrap; +} + +/* Right side action buttons */ +.rcm-toolbar-right { + display: flex; + align-items: center; +} + +/* Group buttons with small spacing */ +.rcm-action-group { + display: flex; + gap: 0.5rem; +} + +/* Inputs sizing */ +.rcm-input { + width: 160px; + background: #1a1a1a; + border: 1px solid rgba(255,255,255,0.06); + color: #e6e6e6; +} + +.rcm-select-wrapper { + position: relative; + display: inline-flex; + align-items: center; +} + +/* Make select background brighter and match button gradients (blue tone similar to Update) */ +.rcm-select { + width: 180px; + margin-left: 6px; /* bring select closer to left controls */ + background: linear-gradient(180deg, #4aa0db, #2b87c9); + border: 1px solid rgba(0,0,0,0.12); + color: #fff; + appearance: none; + padding-right: 28px; /* space for icon */ + box-shadow: 0 1px 0 rgba(255,255,255,0.04) inset; +} + +/* Hover effect: slightly brighter */ +.rcm-select-wrapper:hover .rcm-select { + background: linear-gradient(180deg, #5bb6ee, #399ad6); + border-color: rgba(58,123,184,0.22); + transform: translateY(-1px); +} + +/* Select icon color to contrast with brighter select */ +.rcm-select-icon { + position: absolute; + right: 6px; + pointer-events: none; + color: rgba(255,255,255,0.9); + display: inline-flex; + align-items: center; +} + +/* Toolbar labels */ +.rcm-label { + font-size: 0.9rem; + margin-right: 6px; + color: #cfcfcf; +} + +/* Minor button styling to match toolbar */ +.rcm-btn { + min-width: 84px; +} + +/* Icon button styles */ +.rcm-icon-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 38px; + height: 34px; + padding: 0.25rem; + border-radius: 6px; + background: transparent; + color: #e6e6e6; + border: 1px solid rgba(255,255,255,0.04); +} + +.rcm-icon-btn svg { + display: block; +} + +/* Icon button hover and focus states */ +.rcm-icon-btn:hover, .rcm-icon-btn:focus { + background: rgba(255,255,255,0.03); + color: #fff; + border-color: rgba(255,255,255,0.08); +} + +.rcm-danger { + color: #ffb3b3; + border-color: rgba(255,100,100,0.18); +} + +/* Adjust bootstrap button colors for dark toolbar when using outline variants */ +.rcm-toolbar .btn-outline-primary { + color: #cfe2ff; + border-color: rgba(95,160,255,0.2); +} + +.rcm-toolbar .btn-outline-success { + color: #d4f5d4; + border-color: rgba(60,200,120,0.18); +} + +.rcm-toolbar .btn-outline-danger { + color: #ffcfcf; + border-color: rgba(255,100,100,0.18); +} + +/* Specific styles for action buttons (Add, Update, Delete) */ +.rcm-action-group button[aria-label="Add"] { + background: linear-gradient(180deg, #4bb24b, #2f9a2f); /* bright green */ + color: #fff; + border: 1px solid rgba(0,0,0,0.08); + box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset; +} + +.rcm-action-group button[aria-label="Add"]:hover { + background: linear-gradient(180deg, #66d166, #3fb83f); + transform: translateY(-1px); +} + +.rcm-action-group button[aria-label="Add"]:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(56,160,56,0.18); +} + +.rcm-action-group button[aria-label="Update"] { + background: linear-gradient(180deg, #4aa0db, #2b87c9); /* bright blue */ + color: #fff; + border: 1px solid rgba(0,0,0,0.08); + box-shadow: 0 1px 0 rgba(255,255,255,0.08) inset; +} + +.rcm-action-group button[aria-label="Update"]:hover { + background: linear-gradient(180deg, #5bb6ee, #399ad6); + transform: translateY(-1px); +} + +.rcm-action-group button[aria-label="Update"]:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(58,123,184,0.18); +} + +.rcm-action-group button[aria-label="Delete"] { + background: linear-gradient(180deg, #ff6b6b, #e04848); /* bright red */ + color: #fff; + border: 1px solid rgba(0,0,0,0.08); + box-shadow: 0 1px 0 rgba(255,255,255,0.06) inset; +} + +.rcm-action-group button[aria-label="Delete"]:hover { + background: linear-gradient(180deg, #ff8282, #ec5b5b); + transform: translateY(-1px); +} + +.rcm-action-group button[aria-label="Delete"]:focus { + outline: none; + box-shadow: 0 0 0 3px rgba(224,72,72,0.18); +} + +/* Ensure icons inside the action buttons remain visible */ +.rcm-action-group button[aria-label="Add"] .mdi, +.rcm-action-group button[aria-label="Update"] .mdi, +.rcm-action-group button[aria-label="Delete"] .mdi { + color: #fff; +} + +/* Ensure dropdown options use dark theme matching select */ +.rcm-select option { + background-color: #141414 !important; + color: #e6e6e6 !important; +} + +/* Hover/active state inside dropdown */ +.rcm-select option:hover, +.rcm-select option:checked { + background-color: #2b87c9 !important; /* match update button blue */ + color: #fff !important; +} + +/* Optgroup styling (if used) */ +.rcm-select optgroup { + color: #e6e6e6; + background: #141414; +} + +/* Tooltip for action buttons using data-tooltip attribute */ +.rcm-action-group button[data-tooltip] { + position: relative; +} + +.rcm-action-group button[data-tooltip]::after { + content: attr(data-tooltip); + position: absolute; + left: 50%; + transform: translateX(-50%) translateY(8px); + bottom: -9999px; /* hidden by default below flow */ + background: rgba(20,20,20,0.98); + color: #fff; + padding: 6px 10px; + border-radius: 6px; + font-size: 0.85rem; + white-space: nowrap; + box-shadow: 0 2px 6px rgba(0,0,0,0.5); + opacity: 0; + pointer-events: none; + transition: opacity 0.12s ease, transform 0.12s ease; + z-index: 50; +} + +/* Arrow below tooltip */ +.rcm-action-group button[data-tooltip]::before { + content: ""; + position: absolute; + left: 50%; + transform: translateX(-50%); + bottom: -9999px; + width: 8px; + height: 8px; + background: rgba(20,20,20,0.98); + transform-origin: center; + rotate: 45deg; + z-index: 49; +} + +/* Show tooltip on hover */ +.rcm-action-group button[data-tooltip]:hover::after, +.rcm-action-group button[data-tooltip]:hover::before { + bottom: -45px; /* reduced gap: place tooltip closer to the button */ + opacity: 1; + transform: translateX(-50%) translateY(0); +} + +/* Adjust for danger button (slightly different color) */ +.rcm-action-group button[data-tooltip].rcm-danger::after { + background: rgba(224,72,72,0.95); +} + +/* Slightly lift the button on hover to match tooltip */ +.rcm-action-group button[data-tooltip]:hover { + transform: translateY(-2px); +} + +/* Tooltip backgrounds matching their buttons */ +.rcm-action-group button[aria-label="Add"][data-tooltip]::after { + background: linear-gradient(180deg, #4bb24b, #2f9a2f); +} +.rcm-action-group button[aria-label="Add"][data-tooltip]::before { + background: #2f9a2f; +} + +.rcm-action-group button[aria-label="Update"][data-tooltip]::after { + background: linear-gradient(180deg, #4aa0db, #2b87c9); +} +.rcm-action-group button[aria-label="Update"][data-tooltip]::before { + background: #2b87c9; +} + +/* Delete already had a red variant, update the arrow to match precisely */ +.rcm-action-group button[aria-label="Delete"][data-tooltip]::after, +.rcm-action-group button[data-tooltip].rcm-danger::after { + background: linear-gradient(180deg, #ff6b6b, #e04848); +} +.rcm-action-group button[aria-label="Delete"][data-tooltip]::before, +.rcm-action-group button[data-tooltip].rcm-danger::before { + background: #e04848; +} + +/* Ensure tooltip text stays readable on gradients */ +.rcm-action-group button[data-tooltip]::after { + color: #fff; +} + +/* Active badge in config list: make slightly larger and more visible */ +.rcm-config-list .list-group-item .badge { + font-size: 1.05rem; /* slightly larger */ + padding: 0.45rem 0.8rem; + border-radius: 0.5rem; + line-height: 1; + margin-left: 0.6rem; + opacity: 0.98; + display: inline-block; + vertical-align: middle; +} diff --git a/RobotApp.Client/_Imports.razor b/RobotApp.Client/_Imports.razor index 449fd7f..4aaabf4 100644 --- a/RobotApp.Client/_Imports.razor +++ b/RobotApp.Client/_Imports.razor @@ -11,3 +11,5 @@ @using RobotApp.Client @using Microsoft.AspNetCore.Authorization @using MudBlazor +@using RobotApp.Common.Shares.Dtos +@using RobotApp.Common.Shares.Enums \ No newline at end of file diff --git a/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs index eafee69..613e441 100644 --- a/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs @@ -14,7 +14,29 @@ public record RobotConfigDto public double Height { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record UpdateRobotConfigDto +{ + public NavigationType NavigationType { get; set; } + public double RadiusWheel { get; set; } + public double Width { get; set; } + public double Length { get; set; } + public double Height { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record CreateRobotConfigDto +{ + public NavigationType NavigationType { get; set; } + public double RadiusWheel { get; set; } + public double Width { get; set; } + public double Length { get; set; } + public double Height { get; set; } public string ConfigName { get; set; } public string Description { get; set; } } diff --git a/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs index d6a2648..58d8dc1 100644 --- a/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs @@ -7,10 +7,28 @@ public record RobotPlcConfigDto public Guid Id { get; set; } public string PLCAddress { get; set; } public int PLCPort { get; set; } - public byte PLCUnitId { get; set; } + public int PLCUnitId { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } public string ConfigName { get; set; } public string Description { get; set; } } + +public record UpdateRobotPlcConfigDto +{ + public string ConfigName { get; set; } + public string Description { get; set; } + public string PLCAddress { get; set; } + public int PLCPort { get; set; } + public int PLCUnitId { get; set; } +} + +public record CreateRobotPlcConfigDto +{ + public string ConfigName { get; set; } + public string Description { get; set; } + public string PLCAddress { get; set; } + public int PLCPort { get; set; } + public int PLCUnitId { get; set; } +} diff --git a/RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs b/RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs deleted file mode 100644 index 9837c18..0000000 --- a/RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace RobotApp.Common.Shares.Dtos; - -#nullable disable - -public record RobotSafetyConfig -{ - public Guid Id { get; set; } - public double SafetySpeedVerySlow { get; set; } - public double SafetySpeedSlow { get; set; } - public double SafetySpeedNormal { get; set; } - public double SafetySpeedMedium { get; set; } - public double SafetySpeedOptimal { get; set; } - public double SafetySpeedFast { get; set; } - public double SafetySpeedVeryFast { get; set; } - public DateTime CreatedAt { get; set; } - public DateTime UpdatedAt { get; set; } - public bool IsActive { get; set; } = true; - public string ConfigName { get; set; } - public string Description { get; set; } -} diff --git a/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs new file mode 100644 index 0000000..2c7d8ef --- /dev/null +++ b/RobotApp.Common.Shares/Dtos/RobotSafetyConfigDto.cs @@ -0,0 +1,46 @@ +namespace RobotApp.Common.Shares.Dtos; + +#nullable disable + +public record RobotSafetyConfigDto +{ + public Guid Id { get; set; } + public double SafetySpeedVerySlow { get; set; } + public double SafetySpeedSlow { get; set; } + public double SafetySpeedNormal { get; set; } + public double SafetySpeedMedium { get; set; } + public double SafetySpeedOptimal { get; set; } + public double SafetySpeedFast { get; set; } + public double SafetySpeedVeryFast { get; set; } + public DateTime CreatedAt { get; set; } + public DateTime UpdatedAt { get; set; } + public bool IsActive { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record UpdateRobotSafetyConfigDto +{ + public double SafetySpeedVerySlow { get; set; } + public double SafetySpeedSlow { get; set; } + public double SafetySpeedNormal { get; set; } + public double SafetySpeedMedium { get; set; } + public double SafetySpeedOptimal { get; set; } + public double SafetySpeedFast { get; set; } + public double SafetySpeedVeryFast { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record CreateRobotSafetyConfigDto +{ + public double SafetySpeedVerySlow { get; set; } + public double SafetySpeedSlow { get; set; } + public double SafetySpeedNormal { get; set; } + public double SafetySpeedMedium { get; set; } + public double SafetySpeedOptimal { get; set; } + public double SafetySpeedFast { get; set; } + public double SafetySpeedVeryFast { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} \ No newline at end of file diff --git a/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs index 7630685..68ab802 100644 --- a/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs @@ -12,7 +12,29 @@ public record RobotSimulationConfigDto public double SimulationDeceleration { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } public string ConfigName { get; set; } public string Description { get; set; } } + +public record UpdateRobotSimulationConfigDto +{ + public bool EnableSimulation { get; set; } + public double SimulationMaxVelocity { get; set; } + public double SimulationMaxAngularVelocity { get; set; } + public double SimulationAcceleration { get; set; } + public double SimulationDeceleration { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record CreateRobotSimulationConfigDto +{ + public bool EnableSimulation { get; set; } + public double SimulationMaxVelocity { get; set; } + public double SimulationMaxAngularVelocity { get; set; } + public double SimulationAcceleration { get; set; } + public double SimulationDeceleration { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} \ No newline at end of file diff --git a/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs index 942981a..04f1b02 100644 --- a/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs +++ b/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs @@ -7,7 +7,7 @@ public record RobotVDA5050ConfigDto public Guid Id { get; set; } public string SerialNumber { get; set; } public string VDA5050HostServer { get; set; } - public int VDA5050Port { get; set; } = 1883; + public int VDA5050Port { get; set; } public string VDA5050UserName { get; set; } public string VDA5050Password { get; set; } public string VDA5050Manufacturer { get; set; } @@ -17,7 +17,39 @@ public record RobotVDA5050ConfigDto public bool VDA5050EnableTls { get; set; } public DateTime CreatedAt { get; set; } public DateTime UpdatedAt { get; set; } - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record UpdateRobotVDA5050ConfigDto +{ + public string SerialNumber { get; set; } + public string VDA5050HostServer { get; set; } + public int VDA5050Port { get; set; } + public string VDA5050UserName { get; set; } + public string VDA5050Password { get; set; } + public string VDA5050Manufacturer { get; set; } + public string VDA5050Version { get; set; } + public int VDA5050PublishRepeat { get; set; } + public bool VDA5050EnablePassword { get; set; } + public bool VDA5050EnableTls { get; set; } + public string ConfigName { get; set; } + public string Description { get; set; } +} + +public record CreateRobotVDA5050ConfigDto +{ + public string SerialNumber { get; set; } + public string VDA5050HostServer { get; set; } + public int VDA5050Port { get; set; } + public string VDA5050UserName { get; set; } + public string VDA5050Password { get; set; } + public string VDA5050Manufacturer { get; set; } + public string VDA5050Version { get; set; } + public int VDA5050PublishRepeat { get; set; } + public bool VDA5050EnablePassword { get; set; } + public bool VDA5050EnableTls { get; set; } public string ConfigName { get; set; } public string Description { get; set; } } diff --git a/RobotApp.Common.Shares/Enums/RobotConfigType.cs b/RobotApp.Common.Shares/Enums/RobotConfigType.cs new file mode 100644 index 0000000..0833384 --- /dev/null +++ b/RobotApp.Common.Shares/Enums/RobotConfigType.cs @@ -0,0 +1,10 @@ +namespace RobotApp.Common.Shares.Enums; + +public enum RobotConfigType +{ + VDA5050, + Core, + Safety , + Simulation, + PLC, +} diff --git a/RobotApp/Controllers/RobotConfigsController.cs b/RobotApp/Controllers/RobotConfigsController.cs index ad17b1b..9bd7fc9 100644 --- a/RobotApp/Controllers/RobotConfigsController.cs +++ b/RobotApp/Controllers/RobotConfigsController.cs @@ -1,16 +1,956 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.EntityFrameworkCore; +using RobotApp.Common.Shares; +using RobotApp.Common.Shares.Dtos; +using RobotApp.Data; +using RobotApp.Services.Robot; namespace RobotApp.Controllers; [Route("api/[controller]")] [ApiController] [Authorize] -public class RobotConfigsController(Services.Logger Logger) : ControllerBase +public class RobotConfigsController(Services.Logger Logger, ApplicationDbContext AppDb, RobotConfiguration RobotConfiguration) : ControllerBase { [HttpGet] [Route("plc")] - public void GetPLCConfig() + public async Task> GetAllPLCConfigs() { + try + { + var configs = await AppDb.RobotPlcConfigs + .OrderByDescending(c => c.UpdatedAt) + .ToListAsync(); + if (configs is null || configs.Count == 0) return new(false, "PLC configuration not found."); + return new(true) + { + Data = [.. configs.Select(config => new RobotPlcConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + PLCAddress = config.PLCAddress, + PLCPort = config.PLCPort, + PLCUnitId = config.PLCUnitId, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + })] + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Get PLC Configs: {ex.Message}"); + return new(false, "An error occurred while retrieving the PLC configuration."); + } + } + + [HttpPut] + [Route("plc/{id:guid}")] + public async Task> UpdatePLCConfig(Guid id, [FromBody] UpdateRobotPlcConfigDto updateDto) + { + try + { + var config = await AppDb.RobotPlcConfigs.FindAsync(id); + if (config is null) return new(false, "PLC configuration not found."); + + if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotPlcConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName)) + return new(false, "A PLC configuration with the same name already exists."); + + config.ConfigName = updateDto.ConfigName ?? config.ConfigName; + config.Description = updateDto.Description ?? config.Description; + config.PLCAddress = updateDto.PLCAddress ?? config.PLCAddress; + config.PLCPort = updateDto.PLCPort; + config.PLCUnitId = (byte)updateDto.PLCUnitId; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + if (config.IsActive) await RobotConfiguration.LoadRobotPlcConfigAsync(); + return new(true, "PLC configuration updated successfully.") + { + Data = new RobotPlcConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + PLCAddress = config.PLCAddress, + PLCPort = config.PLCPort, + PLCUnitId = config.PLCUnitId, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Update PLC Config: {ex.Message}"); + return new(false, "An error occurred while updating the PLC configuration."); + } + } + + [HttpPost] + [Route("plc")] + public async Task> CreatePLCConfig([FromBody] CreateRobotPlcConfigDto createDto) + { + try + { + if (string.IsNullOrEmpty(createDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotPlcConfigs.AnyAsync(cf => cf.ConfigName == createDto.ConfigName)) + return new(false, "A PLC configuration with the same name already exists."); + + var config = new RobotPlcConfig + { + ConfigName = createDto.ConfigName, + Description = createDto.Description, + PLCAddress = createDto.PLCAddress, + PLCPort = createDto.PLCPort, + PLCUnitId = (byte)createDto.PLCUnitId, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsActive = false + }; + + AppDb.RobotPlcConfigs.Add(config); + await AppDb.SaveChangesAsync(); + + return new(true, "PLC configuration created successfully.") + { + Data = new RobotPlcConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + PLCAddress = config.PLCAddress, + PLCPort = config.PLCPort, + PLCUnitId = config.PLCUnitId, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Create PLC Config: {ex.Message}"); + return new(false, "An error occurred while creating the PLC configuration."); + } + } + + [HttpDelete] + [Route("plc/{id:guid}")] + public async Task DeletePLCConfig(Guid id) + { + try + { + var config = await AppDb.RobotPlcConfigs.FindAsync(id); + if (config is null) return new(false, "PLC configuration not found."); + if (config.IsActive) return new(false, "Cannot delete an active PLC configuration."); + + AppDb.RobotPlcConfigs.Remove(config); + await AppDb.SaveChangesAsync(); + return new(true, "Configuration deleted successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Delete PLC Config: {ex.Message}"); + return new(false, "An error occurred while deleting the PLC configuration."); + } + } + + [HttpPut] + [Route("plc/active/{id:guid}")] + public async Task ActivePLCConfig(Guid id) + { + try + { + var config = await AppDb.RobotPlcConfigs.FindAsync(id); + if (config is null) return new(false, "PLC configuration not found."); + + await AppDb.RobotPlcConfigs.ExecuteUpdateAsync(cf => cf.SetProperty(c => c.IsActive, false)); + config.IsActive = true; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + await RobotConfiguration.LoadRobotPlcConfigAsync(); + return new(true, $"PLC configuration {config.ConfigName} activated successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Update Active PLC Config: {ex.Message}"); + return new(false, "An error occurred while updating the active PLC configuration."); + } + } + + [HttpGet] + [Route("vda5050")] + public async Task> GetAllVDA5050Configs() + { + try + { + var configs = await AppDb.RobotVDA5050Configs + .OrderByDescending(c => c.UpdatedAt) + .ToListAsync(); + + if (configs is null || configs.Count == 0) return new(false, "VDA5050 configuration not found."); + var configDtos = configs.Select(config => new RobotVDA5050ConfigDto + { + Id = config.Id, + SerialNumber = config.SerialNumber, + VDA5050HostServer = config.VDA5050HostServer, + VDA5050Port = config.VDA5050Port, + VDA5050UserName = config.VDA5050UserName, + VDA5050Password = config.VDA5050Password, + VDA5050Manufacturer = config.VDA5050Manufacturer, + VDA5050Version = config.VDA5050Version, + VDA5050PublishRepeat = config.VDA5050PublishRepeat, + VDA5050EnablePassword = config.VDA5050EnablePassword, + VDA5050EnableTls = config.VDA5050EnableTls, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + }).ToArray(); + + return new(true) { Data = configDtos }; + } + catch (Exception ex) + { + Logger.Error($"Error in Get All VDA5050 Configs: {ex.Message}"); + return new(false, "An error occurred while retrieving VDA5050 configurations."); + } + } + + [HttpPut] + [Route("vda5050/{id:guid}")] + public async Task> UpdateVDA5050Config(Guid id, [FromBody] UpdateRobotVDA5050ConfigDto updateDto) + { + try + { + var config = await AppDb.RobotVDA5050Configs.FindAsync(id); + if (config is null) return new(false, "VDA5050 configuration not found."); + + if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotVDA5050Configs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName)) + return new(false, "A VDA5050 configuration with the same name already exists."); + + config.SerialNumber = updateDto.SerialNumber ?? config.SerialNumber; + config.VDA5050HostServer = updateDto.VDA5050HostServer ?? config.VDA5050HostServer; + config.VDA5050Port = updateDto.VDA5050Port; + config.VDA5050UserName = updateDto.VDA5050UserName ?? config.VDA5050UserName; + config.VDA5050Password = updateDto.VDA5050Password ?? config.VDA5050Password; + config.VDA5050Manufacturer = updateDto.VDA5050Manufacturer ?? config.VDA5050Manufacturer; + config.VDA5050Version = updateDto.VDA5050Version ?? config.VDA5050Version; + config.VDA5050PublishRepeat = updateDto.VDA5050PublishRepeat; + config.VDA5050EnablePassword = updateDto.VDA5050EnablePassword; + config.VDA5050EnableTls = updateDto.VDA5050EnableTls; + config.ConfigName = updateDto.ConfigName ?? config.ConfigName; + config.Description = updateDto.Description ?? config.Description; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + if (config.IsActive) await RobotConfiguration.LoadVDA5050ConfigAsync(); + return new(true, "VDA5050 configuration updated successfully.") + { + Data = new RobotVDA5050ConfigDto + { + Id = config.Id, + SerialNumber = config.SerialNumber, + VDA5050HostServer = config.VDA5050HostServer, + VDA5050Port = config.VDA5050Port, + VDA5050UserName = config.VDA5050UserName, + VDA5050Password = config.VDA5050Password, + VDA5050Manufacturer = config.VDA5050Manufacturer, + VDA5050Version = config.VDA5050Version, + VDA5050PublishRepeat = config.VDA5050PublishRepeat, + VDA5050EnablePassword = config.VDA5050EnablePassword, + VDA5050EnableTls = config.VDA5050EnableTls, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Update VDA5050 Config: {ex.Message}"); + return new(false, "An error occurred while updating the VDA5050 configuration."); + } + } + + [HttpPost] + [Route("vda5050")] + public async Task> CreateVDA5050Config([FromBody] CreateRobotVDA5050ConfigDto createDto) + { + try + { + if (string.IsNullOrEmpty(createDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotVDA5050Configs.AnyAsync(cf => cf.ConfigName == createDto.ConfigName)) + return new(false, "A VDA5050 configuration with the same name already exists."); + + var config = new RobotVDA5050Config + { + ConfigName = createDto.ConfigName, + Description = createDto.Description, + SerialNumber = createDto.SerialNumber, + VDA5050HostServer = createDto.VDA5050HostServer, + VDA5050Port = createDto.VDA5050Port, + VDA5050UserName = createDto.VDA5050UserName, + VDA5050Password = createDto.VDA5050Password, + VDA5050Manufacturer = createDto.VDA5050Manufacturer, + VDA5050Version = createDto.VDA5050Version, + VDA5050PublishRepeat = createDto.VDA5050PublishRepeat, + VDA5050EnablePassword = createDto.VDA5050EnablePassword, + VDA5050EnableTls = createDto.VDA5050EnableTls, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsActive = false + }; + + AppDb.RobotVDA5050Configs.Add(config); + await AppDb.SaveChangesAsync(); + + return new(true, "VDA5050 configuration created successfully.") + { + Data = new RobotVDA5050ConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + SerialNumber = config.SerialNumber, + VDA5050HostServer = config.VDA5050HostServer, + VDA5050Port = config.VDA5050Port, + VDA5050UserName = config.VDA5050UserName, + VDA5050Password = config.VDA5050Password, + VDA5050Manufacturer = config.VDA5050Manufacturer, + VDA5050Version = config.VDA5050Version, + VDA5050PublishRepeat = config.VDA5050PublishRepeat, + VDA5050EnablePassword = config.VDA5050EnablePassword, + VDA5050EnableTls = config.VDA5050EnableTls, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Create VDA5050 Config: {ex.Message}"); + return new(false, "An error occurred while creating the VDA5050 configuration."); + } + } + + [HttpDelete] + [Route("vda5050/{id:guid}")] + public async Task DeleteVDA5050Config(Guid id) + { + try + { + var config = await AppDb.RobotVDA5050Configs.FindAsync(id); + if (config is null) return new(false, "VDA5050 configuration not found."); + if (config.IsActive) return new(false, "Cannot delete an active VDA5050 configuration."); + + AppDb.RobotVDA5050Configs.Remove(config); + await AppDb.SaveChangesAsync(); + return new(true, "Configuration deleted successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Delete VDA5050 Config: {ex.Message}"); + return new(false, "An error occurred while deleting the VDA5050 configuration."); + } + } + + [HttpPut] + [Route("vda5050/active/{id:guid}")] + public async Task ActiveVDA5050Config(Guid id) + { + try + { + var config = await AppDb.RobotVDA5050Configs.FindAsync(id); + if (config is null) return new(false, "VDA5050 configuration not found."); + + await AppDb.RobotVDA5050Configs.ExecuteUpdateAsync(cf => cf.SetProperty(c => c.IsActive, false)); + config.IsActive = true; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + await RobotConfiguration.LoadVDA5050ConfigAsync(); + return new(true, $"VDA5050 configuration {config.ConfigName} activated successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Update Active VDA5050 Config: {ex.Message}"); + return new(false, "An error occurred while updating the active VDA5050 configuration."); + } + } + + [HttpGet] + [Route("robot")] + public async Task> GetAllRobotConfigs() + { + try + { + var configs = await AppDb.RobotConfigs + .OrderByDescending(c => c.UpdatedAt) + .ToListAsync(); + if (configs is null || configs.Count == 0) return new(false, "Robot configuration not found."); + + var configDtos = configs.Select(config => new RobotConfigDto + { + Id = config.Id, + NavigationType = config.NavigationType, + RadiusWheel = config.RadiusWheel, + Width = config.Width, + Length = config.Length, + Height = config.Height, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + }).ToArray(); + + return new(true) { Data = configDtos }; + } + catch (Exception ex) + { + Logger.Error($"Error in Get All Robot Configs: {ex.Message}"); + return new(false, "An error occurred while retrieving Robot configurations."); + } + } + + [HttpPut] + [Route("robot/{id:guid}")] + public async Task> UpdateRobotConfig(Guid id, [FromBody] UpdateRobotConfigDto updateDto) + { + try + { + var config = await AppDb.RobotConfigs.FindAsync(id); + if (config is null) return new(false, "Robot configuration not found."); + + if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName)) + return new(false, "A Robot configuration with the same name already exists."); + + config.NavigationType = updateDto.NavigationType; + config.RadiusWheel = updateDto.RadiusWheel; + config.Width = updateDto.Width; + config.Length = updateDto.Length; + config.Height = updateDto.Height; + config.ConfigName = updateDto.ConfigName ?? config.ConfigName; + config.Description = updateDto.Description ?? config.Description; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + if (config.IsActive) await RobotConfiguration.LoadRobotConfigAsync(); + return new(true, "Robot configuration updated successfully.") + { + Data = new RobotConfigDto + { + Id = config.Id, + NavigationType = config.NavigationType, + RadiusWheel = config.RadiusWheel, + Width = config.Width, + Length = config.Length, + Height = config.Height, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Update Robot Config: {ex.Message}"); + return new(false, "An error occurred while updating the Robot configuration."); + } + } + + [HttpPost] + [Route("robot")] + public async Task> CreateRobotConfig([FromBody] CreateRobotConfigDto createDto) + { + try + { + if (string.IsNullOrEmpty(createDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotConfigs.AnyAsync(cf => cf.ConfigName == createDto.ConfigName)) + return new(false, "A Robot configuration with the same name already exists."); + + var config = new RobotConfig + { + ConfigName = createDto.ConfigName, + Description = createDto.Description, + NavigationType = createDto.NavigationType, + RadiusWheel = createDto.RadiusWheel, + Width = createDto.Width, + Length = createDto.Length, + Height = createDto.Height, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsActive = false + }; + + AppDb.RobotConfigs.Add(config); + await AppDb.SaveChangesAsync(); + + return new(true, "Robot configuration created successfully.") + { + Data = new RobotConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + NavigationType = config.NavigationType, + RadiusWheel = config.RadiusWheel, + Width = config.Width, + Length = config.Length, + Height = config.Height, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Create Robot Config: {ex.Message}"); + return new(false, "An error occurred while creating the Robot configuration."); + } + } + + [HttpDelete] + [Route("robot/{id:guid}")] + public async Task DeleteRobotConfig(Guid id) + { + try + { + var config = await AppDb.RobotConfigs.FindAsync(id); + if (config is null) return new(false, "Robot configuration not found."); + if (config.IsActive) return new(false, "Cannot delete an active Robot configuration."); + + AppDb.RobotConfigs.Remove(config); + await AppDb.SaveChangesAsync(); + return new(true, "Configuration deleted successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Delete Robot Config: {ex.Message}"); + return new(false, "An error occurred while deleting the Robot configuration."); + } + } + + [HttpPut] + [Route("robot/active/{id:guid}")] + public async Task ActiveRobotConfig(Guid id) + { + try + { + var config = await AppDb.RobotConfigs.FindAsync(id); + if (config is null) return new(false, "Robot configuration not found."); + + await AppDb.RobotConfigs.ExecuteUpdateAsync(cf => cf.SetProperty(c => c.IsActive, false)); + config.IsActive = true; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + await RobotConfiguration.LoadRobotConfigAsync(); + return new(true, $"Robot configuration {config.ConfigName} activated successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Update Active Robot Config: {ex.Message}"); + return new(false, "An error occurred while updating the active Robot configuration."); + } + } + + [HttpGet] + [Route("simulation")] + public async Task> GetAllSimulationConfigs() + { + try + { + var configs = await AppDb.RobotSimulationConfigs + .OrderByDescending(c => c.UpdatedAt) + .ToListAsync(); + + if (configs is null || configs.Count == 0) return new(false, "Simulation configuration not found."); + var configDtos = configs.Select(config => new RobotSimulationConfigDto + { + Id = config.Id, + EnableSimulation = config.EnableSimulation, + SimulationMaxVelocity = config.SimulationMaxVelocity, + SimulationMaxAngularVelocity = config.SimulationMaxAngularVelocity, + SimulationAcceleration = config.SimulationAcceleration, + SimulationDeceleration = config.SimulationDeceleration, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + }).ToArray(); + + return new(true) { Data = configDtos }; + } + catch (Exception ex) + { + Logger.Error($"Error in Get All Simulation Configs: {ex.Message}"); + return new(false, "An error occurred while retrieving Simulation configurations."); + } + } + + [HttpPut] + [Route("simulation/{id:guid}")] + public async Task> UpdateSimulationConfig(Guid id, [FromBody] UpdateRobotSimulationConfigDto updateDto) + { + try + { + var config = await AppDb.RobotSimulationConfigs.FindAsync(id); + if (config is null) return new(false, "Simulation configuration not found."); + + if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotSimulationConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName)) + return new(false, "A Simulation configuration with the same name already exists."); + + config.EnableSimulation = updateDto.EnableSimulation; + config.SimulationMaxVelocity = updateDto.SimulationMaxVelocity; + config.SimulationMaxAngularVelocity = updateDto.SimulationMaxAngularVelocity; + config.SimulationAcceleration = updateDto.SimulationAcceleration; + config.SimulationDeceleration = updateDto.SimulationDeceleration; + config.ConfigName = updateDto.ConfigName ?? config.ConfigName; + config.Description = updateDto.Description ?? config.Description; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + if (config.IsActive) await RobotConfiguration.LoadRobotSimulationConfigAsync(); + return new(true, "Simulation configuration updated successfully.") + { + Data = new RobotSimulationConfigDto + { + Id = config.Id, + EnableSimulation = config.EnableSimulation, + SimulationMaxVelocity = config.SimulationMaxVelocity, + SimulationMaxAngularVelocity = config.SimulationMaxAngularVelocity, + SimulationAcceleration = config.SimulationAcceleration, + SimulationDeceleration = config.SimulationDeceleration, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Update Simulation Config: {ex.Message}"); + return new(false, "An error occurred while updating the Simulation configuration."); + } + } + + [HttpPost] + [Route("simulation")] + public async Task> CreateRobotSimulationConfig([FromBody] CreateRobotSimulationConfigDto createDto) + { + try + { + if (string.IsNullOrEmpty(createDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotSimulationConfigs.AnyAsync(cf => cf.ConfigName == createDto.ConfigName)) + return new(false, "A Simulation configuration with the same name already exists."); + + var config = new RobotSimulationConfig + { + ConfigName = createDto.ConfigName, + Description = createDto.Description, + EnableSimulation = createDto.EnableSimulation, + SimulationMaxVelocity = createDto.SimulationMaxVelocity, + SimulationMaxAngularVelocity = createDto.SimulationMaxAngularVelocity, + SimulationAcceleration = createDto.SimulationAcceleration, + SimulationDeceleration = createDto.SimulationDeceleration, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsActive = false + }; + + AppDb.RobotSimulationConfigs.Add(config); + await AppDb.SaveChangesAsync(); + + return new(true, "Simulation configuration created successfully.") + { + Data = new RobotSimulationConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + EnableSimulation = config.EnableSimulation, + SimulationMaxVelocity = config.SimulationMaxVelocity, + SimulationMaxAngularVelocity = config.SimulationMaxAngularVelocity, + SimulationAcceleration = config.SimulationAcceleration, + SimulationDeceleration = config.SimulationDeceleration, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Create Simulation Config: {ex.Message}"); + return new(false, "An error occurred while creating the Simulation configuration."); + } + } + + [HttpDelete] + [Route("simulation/{id:guid}")] + public async Task DeleteRobotSimulationConfig(Guid id) + { + try + { + var config = await AppDb.RobotSimulationConfigs.FindAsync(id); + if (config is null) return new(false, "Simulation configuration not found."); + if (config.IsActive) return new(false, "Cannot delete an active Simulation configuration."); + + AppDb.RobotSimulationConfigs.Remove(config); + await AppDb.SaveChangesAsync(); + return new(true, "Configuration deleted successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Delete Simulation Config: {ex.Message}"); + return new(false, "An error occurred while deleting the Simulation configuration."); + } + } + + [HttpPut] + [Route("simulation/active/{id:guid}")] + public async Task ActiveSimulationConfig(Guid id) + { + try + { + var config = await AppDb.RobotSimulationConfigs.FindAsync(id); + if (config is null) return new(false, "Simulation configuration not found."); + + await AppDb.RobotSimulationConfigs.ExecuteUpdateAsync(cf => cf.SetProperty(c => c.IsActive, false)); + config.IsActive = true; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + await RobotConfiguration.LoadRobotSimulationConfigAsync(); + return new(true, $"Simulation configuration {config.ConfigName} activated successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Update Active Simulation Config: {ex.Message}"); + return new(false, "An error occurred while updating the active Simulation configuration."); + } + } + + [HttpGet] + [Route("safety")] + public async Task> GetAllSafetyConfigs() + { + try + { + var configs = await AppDb.RobotSafetyConfigs + .OrderByDescending(c => c.UpdatedAt) + .ToListAsync(); + if (configs is null || configs.Count == 0) return new(false, "Safety configuration not found."); + var configDtos = configs.Select(config => new RobotSafetyConfigDto + { + Id = config.Id, + SafetySpeedVerySlow = config.SafetySpeedVerySlow, + SafetySpeedSlow = config.SafetySpeedSlow, + SafetySpeedNormal = config.SafetySpeedNormal, + SafetySpeedMedium = config.SafetySpeedMedium, + SafetySpeedOptimal = config.SafetySpeedOptimal, + SafetySpeedFast = config.SafetySpeedFast, + SafetySpeedVeryFast = config.SafetySpeedVeryFast, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + }).ToArray(); + + return new(true) { Data = configDtos }; + } + catch (Exception ex) + { + Logger.Error($"Error in Get All Safety Configs: {ex.Message}"); + return new(false, "An error occurred while retrieving Safety configurations."); + } + } + + [HttpPut] + [Route("safety/{id:guid}")] + public async Task> UpdateSafetyConfig(Guid id, [FromBody] UpdateRobotSafetyConfigDto updateDto) + { + try + { + var config = await AppDb.RobotSafetyConfigs.FindAsync(id); + if (config is null) return new(false, "Safety configuration not found."); + + if (string.IsNullOrEmpty(updateDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotSafetyConfigs.AnyAsync(cf => cf.Id != id && cf.ConfigName == updateDto.ConfigName)) + return new(false, "A Safety configuration with the same name already exists."); + + config.SafetySpeedVerySlow = updateDto.SafetySpeedVerySlow; + config.SafetySpeedSlow = updateDto.SafetySpeedSlow; + config.SafetySpeedNormal = updateDto.SafetySpeedNormal; + config.SafetySpeedMedium = updateDto.SafetySpeedMedium; + config.SafetySpeedOptimal = updateDto.SafetySpeedOptimal; + config.SafetySpeedFast = updateDto.SafetySpeedFast; + config.SafetySpeedVeryFast = updateDto.SafetySpeedVeryFast; + config.ConfigName = updateDto.ConfigName ?? config.ConfigName; + config.Description = updateDto.Description ?? config.Description; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + if (config.IsActive) await RobotConfiguration.LoadRobotSafetyConfigAsync(); + return new(true, "Safety configuration updated successfully.") + { + Data = new RobotSafetyConfigDto + { + Id = config.Id, + SafetySpeedVerySlow = config.SafetySpeedVerySlow, + SafetySpeedSlow = config.SafetySpeedSlow, + SafetySpeedNormal = config.SafetySpeedNormal, + SafetySpeedMedium = config.SafetySpeedMedium, + SafetySpeedOptimal = config.SafetySpeedOptimal, + SafetySpeedFast = config.SafetySpeedFast, + SafetySpeedVeryFast = config.SafetySpeedVeryFast, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + ConfigName = config.ConfigName, + Description = config.Description + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Update Safety Config: {ex.Message}"); + return new(false, "An error occurred while updating the Safety configuration."); + } + } + + [HttpPost] + [Route("safety")] + public async Task> CreateRobotSafetyConfig([FromBody] CreateRobotSafetyConfigDto createDto) + { + try + { + if (string.IsNullOrEmpty(createDto.ConfigName)) return new(false, "ConfigName cannot be null or empty."); + if (await AppDb.RobotSafetyConfigs.AnyAsync(cf => cf.ConfigName == createDto.ConfigName)) + return new(false, "A Safety configuration with the same name already exists."); + + var config = new RobotSafetyConfig + { + ConfigName = createDto.ConfigName, + Description = createDto.Description, + SafetySpeedVerySlow = createDto.SafetySpeedVerySlow, + SafetySpeedSlow = createDto.SafetySpeedSlow, + SafetySpeedNormal = createDto.SafetySpeedNormal, + SafetySpeedMedium = createDto.SafetySpeedMedium, + SafetySpeedOptimal = createDto.SafetySpeedOptimal, + SafetySpeedFast = createDto.SafetySpeedFast, + SafetySpeedVeryFast = createDto.SafetySpeedVeryFast, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsActive = false + }; + + AppDb.RobotSafetyConfigs.Add(config); + await AppDb.SaveChangesAsync(); + + return new(true, "Safety configuration created successfully.") + { + Data = new RobotSafetyConfigDto + { + Id = config.Id, + ConfigName = config.ConfigName, + Description = config.Description, + SafetySpeedVerySlow = config.SafetySpeedVerySlow, + SafetySpeedSlow = config.SafetySpeedSlow, + SafetySpeedNormal = config.SafetySpeedNormal, + SafetySpeedMedium = config.SafetySpeedMedium, + SafetySpeedOptimal = config.SafetySpeedOptimal, + SafetySpeedFast = config.SafetySpeedFast, + SafetySpeedVeryFast = config.SafetySpeedVeryFast, + CreatedAt = config.CreatedAt, + UpdatedAt = config.UpdatedAt, + IsActive = config.IsActive, + } + }; + } + catch (Exception ex) + { + Logger.Error($"Error in Create Safety Config: {ex.Message}"); + return new(false, "An error occurred while creating the Safety configuration."); + } + } + + [HttpDelete] + [Route("safety/{id:guid}")] + public async Task DeleteRobotSafetyConfig(Guid id) + { + try + { + var config = await AppDb.RobotSafetyConfigs.FindAsync(id); + if (config is null) return new(false, "Safety configuration not found."); + if (config.IsActive) return new(false, "Cannot delete an active Safety configuration."); + + AppDb.RobotSafetyConfigs.Remove(config); + await AppDb.SaveChangesAsync(); + return new(true, "Configuration deleted successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Delete Safety Config: {ex.Message}"); + return new(false, "An error occurred while deleting the Safety configuration."); + } + } + + [HttpPut] + [Route("safety/active/{id:guid}")] + public async Task ActiveSafetyConfig(Guid id) + { + try + { + var config = await AppDb.RobotSafetyConfigs.FindAsync(id); + if (config is null) return new(false, "Safety configuration not found."); + + await AppDb.RobotSafetyConfigs.ExecuteUpdateAsync(cf => cf.SetProperty(c => c.IsActive, false)); + config.IsActive = true; + config.UpdatedAt = DateTime.Now; + + await AppDb.SaveChangesAsync(); + + await RobotConfiguration.LoadRobotSafetyConfigAsync(); + return new(true, $"Safety configuration {config.ConfigName} activated successfully."); + } + catch (Exception ex) + { + Logger.Error($"Error in Update Active Safety Config: {ex.Message}"); + return new(false, "An error occurred while updating the active Safety configuration."); + } + } + } diff --git a/RobotApp/Data/ApplicationDbContext.cs b/RobotApp/Data/ApplicationDbContext.cs index 0530c5c..a222055 100644 --- a/RobotApp/Data/ApplicationDbContext.cs +++ b/RobotApp/Data/ApplicationDbContext.cs @@ -1,20 +1,14 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; -using RobotApp.VDA5050.Order; -using RobotApp.VDA5050.State; -using static MudBlazor.CategoryTypes; namespace RobotApp.Data { - public class ApplicationDbContext : IdentityDbContext + public class ApplicationDbContext(DbContextOptions options) : IdentityDbContext(options) { - public ApplicationDbContext(DbContextOptions options) : base(options) - { - } - public DbSet RobotConfigs { get; private set; } public DbSet RobotSimulationConfigs { get; private set; } public DbSet RobotPlcConfigs { get; private set; } public DbSet RobotVDA5050Configs { get; private set; } + public DbSet RobotSafetyConfigs { get; private set; } } } diff --git a/RobotApp/Data/ApplicationDbExtensions.cs b/RobotApp/Data/ApplicationDbExtensions.cs index f89033c..b5e2cb3 100644 --- a/RobotApp/Data/ApplicationDbExtensions.cs +++ b/RobotApp/Data/ApplicationDbExtensions.cs @@ -163,5 +163,27 @@ public static class ApplicationDbExtensions appDb.RobotVDA5050Configs.Add(defaultConfig); await appDb.SaveChangesAsync(); } + + if (!await appDb.RobotSafetyConfigs.AnyAsync()) + { + var defaultConfig = new RobotSafetyConfig + { + ConfigName = "Default", + Description = "Default robot Safety configuration", + SafetySpeedVerySlow = 0.15, + SafetySpeedSlow = 0.3, + SafetySpeedNormal = 0.6, + SafetySpeedMedium = 0.9, + SafetySpeedOptimal = 1.2, + SafetySpeedFast = 1.5, + SafetySpeedVeryFast = 1.9, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + }; + + appDb.RobotSafetyConfigs.Add(defaultConfig); + await appDb.SaveChangesAsync(); + } } } diff --git a/RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.Designer.cs b/RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.Designer.cs new file mode 100644 index 0000000..8c1f2c1 --- /dev/null +++ b/RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.Designer.cs @@ -0,0 +1,565 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RobotApp.Data; + +#nullable disable + +namespace RobotApp.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251029064620_InitConfigSafetyDb")] + partial class InitConfigSafetyDb + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.ApplicationRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.RobotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("Height") + .HasColumnType("float") + .HasColumnName("Height"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("Length") + .HasColumnType("float") + .HasColumnName("Length"); + + b.Property("NavigationType") + .HasColumnType("int") + .HasColumnName("NavigationType"); + + b.Property("RadiusWheel") + .HasColumnType("float") + .HasColumnName("RadiusWheel"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.Property("Width") + .HasColumnType("float") + .HasColumnName("Width"); + + b.HasKey("Id"); + + b.ToTable("RobotConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotPlcConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("PLCAddress") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("PLCAddress"); + + b.Property("PLCPort") + .HasColumnType("int") + .HasColumnName("PLCPort"); + + b.Property("PLCUnitId") + .HasColumnType("tinyint") + .HasColumnName("PLCUnitId"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotPlcConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotSafetyConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SafetySpeedFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedFast"); + + b.Property("SafetySpeedMedium") + .HasColumnType("float") + .HasColumnName("SafetySpeedMedium"); + + b.Property("SafetySpeedNormal") + .HasColumnType("float") + .HasColumnName("SafetySpeedNormal"); + + b.Property("SafetySpeedOptimal") + .HasColumnType("float") + .HasColumnName("SafetySpeedOptimal"); + + b.Property("SafetySpeedSlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedSlow"); + + b.Property("SafetySpeedVeryFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedVeryFast"); + + b.Property("SafetySpeedVerySlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedVerySlow"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSafetyConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotSimulationConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("EnableSimulation") + .HasColumnType("bit") + .HasColumnName("EnableSimulation"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SimulationAcceleration") + .HasColumnType("float") + .HasColumnName("SimulationAcceleration"); + + b.Property("SimulationDeceleration") + .HasColumnType("float") + .HasColumnName("SimulationDeceleration"); + + b.Property("SimulationMaxAngularVelocity") + .HasColumnType("float") + .HasColumnName("SimulationMaxAngularVelocity"); + + b.Property("SimulationMaxVelocity") + .HasColumnType("float") + .HasColumnName("SimulationMaxVelocity"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSimulationConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotVDA5050Config", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SerialNumber") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("SerialNumber"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.Property("VDA5050EnablePassword") + .HasColumnType("bit") + .HasColumnName("VDA5050_EnablePassword"); + + b.Property("VDA5050EnableTls") + .HasColumnType("bit") + .HasColumnName("VDA5050_EnableTls"); + + b.Property("VDA5050HostServer") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_HostServer"); + + b.Property("VDA5050Manufacturer") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Manufacturer"); + + b.Property("VDA5050Password") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Password"); + + b.Property("VDA5050Port") + .HasColumnType("int") + .HasColumnName("VDA5050_Port"); + + b.Property("VDA5050PublishRepeat") + .HasColumnType("int") + .HasColumnName("VDA5050_PublishRepeat"); + + b.Property("VDA5050UserName") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_UserName"); + + b.Property("VDA5050Version") + .HasMaxLength(20) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Version"); + + b.HasKey("Id"); + + b.ToTable("RobotVDA5050Config"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("RobotApp.Data.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("RobotApp.Data.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.cs b/RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.cs new file mode 100644 index 0000000..4b36c6c --- /dev/null +++ b/RobotApp/Data/Migrations/20251029064620_InitConfigSafetyDb.cs @@ -0,0 +1,45 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RobotApp.Data.Migrations +{ + /// + public partial class InitConfigSafetyDb : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RobotSafetyConfig", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + SafetySpeedVerySlow = table.Column(type: "float", nullable: false), + SafetySpeedSlow = table.Column(type: "float", nullable: false), + SafetySpeedNormal = table.Column(type: "float", nullable: false), + SafetySpeedMedium = table.Column(type: "float", nullable: false), + SafetySpeedOptimal = table.Column(type: "float", nullable: false), + SafetySpeedFast = table.Column(type: "float", nullable: false), + SafetySpeedVeryFast = table.Column(type: "float", nullable: false), + CreatedAt = table.Column(type: "datetime2", nullable: false), + UpdatedAt = table.Column(type: "datetime2", nullable: false), + IsActive = table.Column(type: "bit", nullable: false), + ConfigName = table.Column(type: "nvarchar(64)", maxLength: 100, nullable: true), + Description = table.Column(type: "ntext", maxLength: 500, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_RobotSafetyConfig", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RobotSafetyConfig"); + } + } +} diff --git a/RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.Designer.cs b/RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.Designer.cs new file mode 100644 index 0000000..30666a8 --- /dev/null +++ b/RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.Designer.cs @@ -0,0 +1,565 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RobotApp.Data; + +#nullable disable + +namespace RobotApp.Data.Migrations +{ + [DbContext(typeof(ApplicationDbContext))] + [Migration("20251029064649_InitConfigSafety1Db")] + partial class InitConfigSafety1Db + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "9.0.9"); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetRoleClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ClaimType") + .HasColumnType("TEXT"); + + b.Property("ClaimValue") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserClaims", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("ProviderKey") + .HasColumnType("TEXT"); + + b.Property("ProviderDisplayName") + .HasColumnType("TEXT"); + + b.Property("UserId") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("LoginProvider", "ProviderKey"); + + b.HasIndex("UserId"); + + b.ToTable("AspNetUserLogins", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("RoleId") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "RoleId"); + + b.HasIndex("RoleId"); + + b.ToTable("AspNetUserRoles", (string)null); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.Property("UserId") + .HasColumnType("TEXT"); + + b.Property("LoginProvider") + .HasColumnType("TEXT"); + + b.Property("Name") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("UserId", "LoginProvider", "Name"); + + b.ToTable("AspNetUserTokens", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.ApplicationRole", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Name") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedName") + .IsUnique() + .HasDatabaseName("RoleNameIndex"); + + b.ToTable("AspNetRoles", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.ApplicationUser", b => + { + b.Property("Id") + .HasColumnType("TEXT"); + + b.Property("AccessFailedCount") + .HasColumnType("INTEGER"); + + b.Property("ConcurrencyStamp") + .IsConcurrencyToken() + .HasColumnType("TEXT"); + + b.Property("Email") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("EmailConfirmed") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnabled") + .HasColumnType("INTEGER"); + + b.Property("LockoutEnd") + .HasColumnType("TEXT"); + + b.Property("NormalizedEmail") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("NormalizedUserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("PhoneNumber") + .HasColumnType("TEXT"); + + b.Property("PhoneNumberConfirmed") + .HasColumnType("INTEGER"); + + b.Property("SecurityStamp") + .HasColumnType("TEXT"); + + b.Property("TwoFactorEnabled") + .HasColumnType("INTEGER"); + + b.Property("UserName") + .HasMaxLength(256) + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("NormalizedEmail") + .HasDatabaseName("EmailIndex"); + + b.HasIndex("NormalizedUserName") + .IsUnique() + .HasDatabaseName("UserNameIndex"); + + b.ToTable("AspNetUsers", (string)null); + }); + + modelBuilder.Entity("RobotApp.Data.RobotConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("Height") + .HasColumnType("float") + .HasColumnName("Height"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("Length") + .HasColumnType("float") + .HasColumnName("Length"); + + b.Property("NavigationType") + .HasColumnType("int") + .HasColumnName("NavigationType"); + + b.Property("RadiusWheel") + .HasColumnType("float") + .HasColumnName("RadiusWheel"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.Property("Width") + .HasColumnType("float") + .HasColumnName("Width"); + + b.HasKey("Id"); + + b.ToTable("RobotConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotPlcConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("PLCAddress") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("PLCAddress"); + + b.Property("PLCPort") + .HasColumnType("int") + .HasColumnName("PLCPort"); + + b.Property("PLCUnitId") + .HasColumnType("tinyint") + .HasColumnName("PLCUnitId"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotPlcConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotSafetyConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SafetySpeedFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedFast"); + + b.Property("SafetySpeedMedium") + .HasColumnType("float") + .HasColumnName("SafetySpeedMedium"); + + b.Property("SafetySpeedNormal") + .HasColumnType("float") + .HasColumnName("SafetySpeedNormal"); + + b.Property("SafetySpeedOptimal") + .HasColumnType("float") + .HasColumnName("SafetySpeedOptimal"); + + b.Property("SafetySpeedSlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedSlow"); + + b.Property("SafetySpeedVeryFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedVeryFast"); + + b.Property("SafetySpeedVerySlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedVerySlow"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSafetyConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotSimulationConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("EnableSimulation") + .HasColumnType("bit") + .HasColumnName("EnableSimulation"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SimulationAcceleration") + .HasColumnType("float") + .HasColumnName("SimulationAcceleration"); + + b.Property("SimulationDeceleration") + .HasColumnType("float") + .HasColumnName("SimulationDeceleration"); + + b.Property("SimulationMaxAngularVelocity") + .HasColumnType("float") + .HasColumnName("SimulationMaxAngularVelocity"); + + b.Property("SimulationMaxVelocity") + .HasColumnType("float") + .HasColumnName("SimulationMaxVelocity"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSimulationConfig"); + }); + + modelBuilder.Entity("RobotApp.Data.RobotVDA5050Config", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SerialNumber") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("SerialNumber"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.Property("VDA5050EnablePassword") + .HasColumnType("bit") + .HasColumnName("VDA5050_EnablePassword"); + + b.Property("VDA5050EnableTls") + .HasColumnType("bit") + .HasColumnName("VDA5050_EnableTls"); + + b.Property("VDA5050HostServer") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_HostServer"); + + b.Property("VDA5050Manufacturer") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Manufacturer"); + + b.Property("VDA5050Password") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Password"); + + b.Property("VDA5050Port") + .HasColumnType("int") + .HasColumnName("VDA5050_Port"); + + b.Property("VDA5050PublishRepeat") + .HasColumnType("int") + .HasColumnName("VDA5050_PublishRepeat"); + + b.Property("VDA5050UserName") + .HasMaxLength(50) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_UserName"); + + b.Property("VDA5050Version") + .HasMaxLength(20) + .HasColumnType("nvarchar(64)") + .HasColumnName("VDA5050_Version"); + + b.HasKey("Id"); + + b.ToTable("RobotVDA5050Config"); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim", b => + { + b.HasOne("RobotApp.Data.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole", b => + { + b.HasOne("RobotApp.Data.ApplicationRole", null) + .WithMany() + .HasForeignKey("RoleId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken", b => + { + b.HasOne("RobotApp.Data.ApplicationUser", null) + .WithMany() + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.cs b/RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.cs new file mode 100644 index 0000000..b42f975 --- /dev/null +++ b/RobotApp/Data/Migrations/20251029064649_InitConfigSafety1Db.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RobotApp.Data.Migrations +{ + /// + public partial class InitConfigSafety1Db : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 1dcd830..2a02fed 100644 --- a/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -310,6 +310,68 @@ namespace RobotApp.Migrations b.ToTable("RobotPlcConfig"); }); + modelBuilder.Entity("RobotApp.Data.RobotSafetyConfig", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("uniqueidentifier") + .HasColumnName("Id"); + + b.Property("ConfigName") + .HasMaxLength(100) + .HasColumnType("nvarchar(64)") + .HasColumnName("ConfigName"); + + b.Property("CreatedAt") + .HasColumnType("datetime2") + .HasColumnName("CreatedAt"); + + b.Property("Description") + .HasMaxLength(500) + .HasColumnType("ntext") + .HasColumnName("Description"); + + b.Property("IsActive") + .HasColumnType("bit") + .HasColumnName("IsActive"); + + b.Property("SafetySpeedFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedFast"); + + b.Property("SafetySpeedMedium") + .HasColumnType("float") + .HasColumnName("SafetySpeedMedium"); + + b.Property("SafetySpeedNormal") + .HasColumnType("float") + .HasColumnName("SafetySpeedNormal"); + + b.Property("SafetySpeedOptimal") + .HasColumnType("float") + .HasColumnName("SafetySpeedOptimal"); + + b.Property("SafetySpeedSlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedSlow"); + + b.Property("SafetySpeedVeryFast") + .HasColumnType("float") + .HasColumnName("SafetySpeedVeryFast"); + + b.Property("SafetySpeedVerySlow") + .HasColumnType("float") + .HasColumnName("SafetySpeedVerySlow"); + + b.Property("UpdatedAt") + .HasColumnType("datetime2") + .HasColumnName("UpdatedAt"); + + b.HasKey("Id"); + + b.ToTable("RobotSafetyConfig"); + }); + modelBuilder.Entity("RobotApp.Data.RobotSimulationConfig", b => { b.Property("Id") diff --git a/RobotApp/Data/RobotConfig.cs b/RobotApp/Data/RobotConfig.cs index acb9a20..e2e8e55 100644 --- a/RobotApp/Data/RobotConfig.cs +++ b/RobotApp/Data/RobotConfig.cs @@ -37,7 +37,7 @@ public class RobotConfig public DateTime UpdatedAt { get; set; } [Column("IsActive", TypeName = "bit")] - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } [Column("ConfigName", TypeName = "nvarchar(64)")] [MaxLength(100)] diff --git a/RobotApp/Data/RobotPlcConfig.cs b/RobotApp/Data/RobotPlcConfig.cs index 5fe4b2a..67e6bbd 100644 --- a/RobotApp/Data/RobotPlcConfig.cs +++ b/RobotApp/Data/RobotPlcConfig.cs @@ -31,7 +31,7 @@ public class RobotPlcConfig public DateTime UpdatedAt { get; set; } [Column("IsActive", TypeName = "bit")] - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } [Column("ConfigName", TypeName = "nvarchar(64)")] [MaxLength(100)] diff --git a/RobotApp/Data/RobotSafetyConfig.cs b/RobotApp/Data/RobotSafetyConfig.cs index 53f22db..3db1093 100644 --- a/RobotApp/Data/RobotSafetyConfig.cs +++ b/RobotApp/Data/RobotSafetyConfig.cs @@ -42,7 +42,7 @@ public class RobotSafetyConfig public DateTime UpdatedAt { get; set; } [Column("IsActive", TypeName = "bit")] - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } [Column("ConfigName", TypeName = "nvarchar(64)")] [MaxLength(100)] diff --git a/RobotApp/Data/RobotSimulationConfig.cs b/RobotApp/Data/RobotSimulationConfig.cs index f41476c..dc55fcd 100644 --- a/RobotApp/Data/RobotSimulationConfig.cs +++ b/RobotApp/Data/RobotSimulationConfig.cs @@ -36,7 +36,7 @@ public class RobotSimulationConfig public DateTime UpdatedAt { get; set; } [Column("IsActive", TypeName = "bit")] - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } [Column("ConfigName", TypeName = "nvarchar(64)")] [MaxLength(100)] diff --git a/RobotApp/Data/RobotVDA5050Config.cs b/RobotApp/Data/RobotVDA5050Config.cs index b3665fb..ded7cdd 100644 --- a/RobotApp/Data/RobotVDA5050Config.cs +++ b/RobotApp/Data/RobotVDA5050Config.cs @@ -24,7 +24,7 @@ public class RobotVDA5050Config public string VDA5050HostServer { get; set; } [Column("VDA5050_Port", TypeName = "int")] - public int VDA5050Port { get; set; } = 1883; + public int VDA5050Port { get; set; } [Column("VDA5050_UserName", TypeName = "nvarchar(64)")] [MaxLength(50)] @@ -58,7 +58,7 @@ public class RobotVDA5050Config public DateTime UpdatedAt { get; set; } [Column("IsActive", TypeName = "bit")] - public bool IsActive { get; set; } = true; + public bool IsActive { get; set; } [Column("ConfigName", TypeName = "nvarchar(64)")] [MaxLength(100)] diff --git a/RobotApp/Services/MQTTClient.cs b/RobotApp/Services/MQTTClient.cs index e4d4d65..d38af3e 100644 --- a/RobotApp/Services/MQTTClient.cs +++ b/RobotApp/Services/MQTTClient.cs @@ -3,7 +3,6 @@ using MQTTnet.Protocol; using RobotApp.Common.Shares; using RobotApp.VDA5050; using System.Net.Security; -using System.Security.Cryptography.X509Certificates; using System.Text; namespace RobotApp.Services; diff --git a/RobotApp/Services/Robot/RobotConfiguration.cs b/RobotApp/Services/Robot/RobotConfiguration.cs index adfd255..2342977 100644 --- a/RobotApp/Services/Robot/RobotConfiguration.cs +++ b/RobotApp/Services/Robot/RobotConfiguration.cs @@ -1,30 +1,26 @@ -using RobotApp.Common.Shares.Enums; +using Microsoft.EntityFrameworkCore; +using RobotApp.Common.Shares.Enums; using RobotApp.Interfaces; using RobotApp.Services.Robot.Simulation; namespace RobotApp.Services.Robot; -public class RobotConfiguration +public class RobotConfiguration(IServiceProvider ServiceProvider, Logger Logger) : BackgroundService { - public string SerialNumber { get; set; } = "Robot-Demo"; - public NavigationType NavigationType { get; set; } = NavigationType.Differential; - public VDA5050.VDA5050Setting VDA5050Setting { get; set; } = new() - { - HostServer = "172.20.235.176", - Port = 1883, - UserName = "robotics", - Password = "robotics", - Manufacturer = "PhenikaaX", - Version = "0.0.1", - PublishRepeat = 2, - }; - public string PLCAddress { get; set; } = "127.0.0.1"; - public int PLCPort { get; set; } = 502; - public byte PLCUnitId { get; set; } = 1; - public bool IsSimulation { get; set; } = true; - public SimulationModel SimulationModel { get; set; } = new(); - public string XlocAddress { get; set; } = "http://192.168.195.56:50050"; - public Dictionary SafetySpeedMap = new () + public bool IsReady { get; private set; } = false; + public string SerialNumber { get; private set; } = ""; + public double RadiusWheel { get; private set; } = 0.1; + public double Width { get; private set; } = 0.606; + public double Length { get; private set; } = 1.106; + public double Height { get; private set; } = 0.5; + public NavigationType NavigationType { get; private set; } = NavigationType.Differential; + public VDA5050.VDA5050Setting VDA5050Setting { get; private set; } = new(); + public string PLCAddress { get; private set; } = "127.0.0.1"; + public int PLCPort { get; private set; } = 502; + public byte PLCUnitId { get; private set; } = 1; + public bool IsSimulation { get; private set; } = true; + public SimulationModel SimulationModel { get; private set; } = new(); + public Dictionary SafetySpeedMap { get; private set; } = new() { { SafetySpeed.Very_Slow, 0.15}, { SafetySpeed.Slow, 0.3}, @@ -34,4 +30,141 @@ public class RobotConfiguration { SafetySpeed.Fast, 1.5}, { SafetySpeed.Very_Fast, 1.9}, }; + + public async Task LoadVDA5050ConfigAsync() + { + try + { + using var scope = ServiceProvider.CreateAsyncScope(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var config = await appDb.RobotVDA5050Configs.FirstOrDefaultAsync(c => c.IsActive); + if (config is not null) + { + VDA5050Setting.HostServer = config.VDA5050HostServer; + VDA5050Setting.Port = config.VDA5050Port; + VDA5050Setting.UserName = config.VDA5050UserName; + VDA5050Setting.Password = config.VDA5050Password; + VDA5050Setting.Manufacturer = config.VDA5050Manufacturer; + VDA5050Setting.Version = config.VDA5050Version; + VDA5050Setting.PublishRepeat = config.VDA5050PublishRepeat; + VDA5050Setting.EnablePassword = config.VDA5050EnablePassword; + VDA5050Setting.EnableTls = config.VDA5050EnableTls; + SerialNumber = config.SerialNumber; + } + else throw new Exception("Chưa có cấu hình VDA5050."); + } + catch (Exception ex) + { + Logger.Warning($"Có lỗi xảy ra khi tải cấu hình VDA5050: {ex.Message}"); + } + } + + public async Task LoadRobotConfigAsync() + { + try + { + using var scope = ServiceProvider.CreateAsyncScope(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var config = await appDb.RobotConfigs.FirstOrDefaultAsync(c => c.IsActive); + if (config is not null) + { + Width = config.Width; + Length = config.Length; + Height = config.Height; + RadiusWheel = config.RadiusWheel; + NavigationType = config.NavigationType; + } + else throw new Exception("Chưa có cấu hình robot."); + } + catch (Exception ex) + { + Logger.Warning($"Có lỗi xảy ra khi tải cấu hình robot: {ex.Message}"); + } + } + + public async Task LoadRobotSafetyConfigAsync() + { + try + { + using var scope = ServiceProvider.CreateAsyncScope(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var config = await appDb.RobotSafetyConfigs.FirstOrDefaultAsync(c => c.IsActive); + if (config is not null) + { + SafetySpeedMap[SafetySpeed.Very_Slow] = config.SafetySpeedVerySlow; + SafetySpeedMap[SafetySpeed.Slow] = config.SafetySpeedSlow; + SafetySpeedMap[SafetySpeed.Normal] = config.SafetySpeedNormal; + SafetySpeedMap[SafetySpeed.Medium] = config.SafetySpeedMedium; + SafetySpeedMap[SafetySpeed.Optimal] = config.SafetySpeedOptimal; + SafetySpeedMap[SafetySpeed.Fast] = config.SafetySpeedFast; + SafetySpeedMap[SafetySpeed.Very_Fast] = config.SafetySpeedVeryFast; + } + else throw new Exception("Chưa có cấu hình safety robot."); + } + catch (Exception ex) + { + Logger.Warning($"Có lỗi xảy ra khi tải cấu hình safety robot: {ex.Message}"); + } + } + + public async Task LoadRobotSimulationConfigAsync() + { + try + { + using var scope = ServiceProvider.CreateAsyncScope(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var config = await appDb.RobotSimulationConfigs.FirstOrDefaultAsync(c => c.IsActive); + if (config is not null) + { + IsSimulation = config.EnableSimulation; + SimulationModel.MaxVelocity = config.SimulationMaxVelocity; + SimulationModel.MaxAngularVelocity = config.SimulationMaxAngularVelocity; + SimulationModel.Acceleration = config.SimulationAcceleration; + SimulationModel.Deceleration = config.SimulationDeceleration; + } + else throw new Exception("Chưa có cấu hình simulation robot."); + } + catch (Exception ex) + { + Logger.Warning($"Có lỗi xảy ra khi tải cấu hình simulation robot: {ex.Message}"); + } + } + + public async Task LoadRobotPlcConfigAsync() + { + try + { + using var scope = ServiceProvider.CreateAsyncScope(); + var appDb = scope.ServiceProvider.GetRequiredService(); + var config = await appDb.RobotPlcConfigs.FirstOrDefaultAsync(c => c.IsActive); + if (config is not null) + { + PLCAddress = config.PLCAddress; + PLCPort = config.PLCPort; + PLCUnitId = config.PLCUnitId; + } + else throw new Exception("Chưa có cấu hình plc robot."); + } + catch (Exception ex) + { + Logger.Warning($"Có lỗi xảy ra khi tải cấu hình plc robot: {ex.Message}"); + } + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + await LoadVDA5050ConfigAsync(); + await LoadRobotConfigAsync(); + await LoadRobotSimulationConfigAsync(); + await LoadRobotSafetyConfigAsync(); + await LoadRobotPlcConfigAsync(); + IsReady = true; + } + catch (Exception ex) + { + Logger.Error($"Lỗi khởi tạo RobotConfiguration: {ex.Message}"); + } + } } diff --git a/RobotApp/Services/Robot/RobotControllerInitialize.cs b/RobotApp/Services/Robot/RobotControllerInitialize.cs index 566f478..b3de793 100644 --- a/RobotApp/Services/Robot/RobotControllerInitialize.cs +++ b/RobotApp/Services/Robot/RobotControllerInitialize.cs @@ -29,7 +29,9 @@ public partial class RobotController PeripheralManager.RightMotorReady && BatteryManager.IsReady && Localization.IsReady && - NavigationManager.IsReady) break; + NavigationManager.IsReady && + RobotConfiguration.IsReady + ) break; } await Task.Delay(1000, cancellationToken); } @@ -37,7 +39,7 @@ public partial class RobotController ConnectionManager.OrderUpdated += NewOrderUpdated; ConnectionManager.ActionUpdated += NewInstantActionUpdated; - await ConnectionManager.StartConnection(cancellationToken); + //await ConnectionManager.StartConnection(cancellationToken); Logger.Info("Robot đã kết nối tới Fleet Manager."); StateManager.TransitionTo(SystemStateType.Standby); diff --git a/RobotApp/Services/Robot/RobotLocalization.cs b/RobotApp/Services/Robot/RobotLocalization.cs index 02c139b..5b4e5e2 100644 --- a/RobotApp/Services/Robot/RobotLocalization.cs +++ b/RobotApp/Services/Robot/RobotLocalization.cs @@ -1,10 +1,8 @@ using Google.Protobuf.WellKnownTypes; using Grpc.Core; -using Grpc.Net.Client; using RobotApp.Common.Shares; using RobotApp.Interfaces; using RobotApp.Services.Robot.Simulation; -using System; using Xloc; using static Xloc.XlocDiagnostics.Types; @@ -44,7 +42,7 @@ public class RobotLocalization(RobotConfiguration RobotConfiguration, Simulation private readonly XlocData XlocData = new(); private readonly bool IsSimulation = RobotConfiguration.IsSimulation; - private readonly XlocServices.XlocServicesClient XlocClient = new(GrpcChannel.ForAddress(RobotConfiguration.XlocAddress)); + private readonly XlocServices.XlocServicesClient XlocClient; private WatchTimerAsync? ReaderTimer; private const int ReaderInterval = 50; private int TimerCounter = 0; diff --git a/RobotApp/Services/Robot/RobotNavigation.cs b/RobotApp/Services/Robot/RobotNavigation.cs index 4eb33cd..43647cc 100644 --- a/RobotApp/Services/Robot/RobotNavigation.cs +++ b/RobotApp/Services/Robot/RobotNavigation.cs @@ -30,7 +30,7 @@ public class RobotNavigation(RobotConfiguration RobotConfiguration, IServiceProv { if (IsSimulation) { - SimNavigation = SimulationNavigationManager.GetNavigation(RobotConfiguration.SimulationModel.NavigationType, ServiceProvider); + SimNavigation = SimulationNavigationManager.GetNavigation(RobotConfiguration.NavigationType, ServiceProvider); SimNavigation.OnNavigationFinished += NavigationFinished; SimNavigation.Move(nodes, edges); } @@ -40,7 +40,7 @@ public class RobotNavigation(RobotConfiguration RobotConfiguration, IServiceProv { if (IsSimulation) { - SimNavigation = SimulationNavigationManager.GetNavigation(RobotConfiguration.SimulationModel.NavigationType, ServiceProvider); + SimNavigation = SimulationNavigationManager.GetNavigation(RobotConfiguration.NavigationType, ServiceProvider); SimNavigation.MoveStraight(x, y); } } @@ -59,7 +59,7 @@ public class RobotNavigation(RobotConfiguration RobotConfiguration, IServiceProv { if (IsSimulation) { - SimNavigation = SimulationNavigationManager.GetNavigation(RobotConfiguration.SimulationModel.NavigationType, ServiceProvider); + SimNavigation = SimulationNavigationManager.GetNavigation(RobotConfiguration.NavigationType, ServiceProvider); SimNavigation.Rotate(angle * 180 / Math.PI); } } diff --git a/RobotApp/Services/Robot/RobotPeripheral.cs b/RobotApp/Services/Robot/RobotPeripheral.cs index 9f89a48..05ff9e3 100644 --- a/RobotApp/Services/Robot/RobotPeripheral.cs +++ b/RobotApp/Services/Robot/RobotPeripheral.cs @@ -242,6 +242,7 @@ public partial class RobotPeripheral(RobotConfiguration RobotConfiguration, IErr protected override async Task ExecuteAsync(CancellationToken stoppingToken) { + if(RobotConfiguration.IsSimulation) return; await Task.Yield(); while (!stoppingToken.IsCancellationRequested && !RobotConfiguration.IsSimulation) { diff --git a/RobotApp/Services/Robot/Simulation/Navigation/DifferentialNavigation.cs b/RobotApp/Services/Robot/Simulation/Navigation/DifferentialNavigation.cs index 1540878..634850f 100644 --- a/RobotApp/Services/Robot/Simulation/Navigation/DifferentialNavigation.cs +++ b/RobotApp/Services/Robot/Simulation/Navigation/DifferentialNavigation.cs @@ -54,13 +54,13 @@ public class DifferentialNavigation : SimulationNavigation if (NavigationPath[MovePurePursuit.OnNodeIndex].Direction == RobotDirection.FORWARD) { - AngularVelocityLeft /= RobotConfiguration.SimulationModel.RadiusWheel; - AngularVelocityRight = AngularVelocityRight / RobotConfiguration.SimulationModel.RadiusWheel * -1; + AngularVelocityLeft /= RobotConfiguration.RadiusWheel; + AngularVelocityRight = AngularVelocityRight / RobotConfiguration.RadiusWheel * -1; } else { - AngularVelocityLeft = AngularVelocityLeft / RobotConfiguration.SimulationModel.RadiusWheel * -1; - AngularVelocityRight /= RobotConfiguration.SimulationModel.RadiusWheel; + AngularVelocityLeft = AngularVelocityLeft / RobotConfiguration.RadiusWheel * -1; + AngularVelocityRight /= RobotConfiguration.RadiusWheel; } VelocityController.SetSpeed(AngularVelocityLeft, AngularVelocityRight, CycleHandlerMilliseconds / 1000.0); } diff --git a/RobotApp/Services/Robot/Simulation/Navigation/SimulationNavigation.cs b/RobotApp/Services/Robot/Simulation/Navigation/SimulationNavigation.cs index 7b433a5..03fde06 100644 --- a/RobotApp/Services/Robot/Simulation/Navigation/SimulationNavigation.cs +++ b/RobotApp/Services/Robot/Simulation/Navigation/SimulationNavigation.cs @@ -57,7 +57,7 @@ public class SimulationNavigation : INavigation, IDisposable RobotConfiguration = scope.ServiceProvider.GetRequiredService(); PathPlanner = scope.ServiceProvider.GetRequiredService(); VelocityController = new(Visualization, RobotConfiguration.SimulationModel); - AngularVelocity = RobotConfiguration.SimulationModel.MaxAngularVelocity * RobotConfiguration.SimulationModel.Width / 2 / 2 / RobotConfiguration.SimulationModel.RadiusWheel; + AngularVelocity = RobotConfiguration.SimulationModel.MaxAngularVelocity * RobotConfiguration.Width / 2 / 2 / RobotConfiguration.RadiusWheel; } protected void HandleNavigationStart() diff --git a/RobotApp/Services/Robot/Simulation/SimulationModel.cs b/RobotApp/Services/Robot/Simulation/SimulationModel.cs index 9421c9c..2b7db16 100644 --- a/RobotApp/Services/Robot/Simulation/SimulationModel.cs +++ b/RobotApp/Services/Robot/Simulation/SimulationModel.cs @@ -1,15 +1,9 @@ -using RobotApp.Common.Shares.Enums; - -namespace RobotApp.Services.Robot.Simulation; +namespace RobotApp.Services.Robot.Simulation; public class SimulationModel { - public readonly double RadiusWheel = 0.1; - public readonly double Width = 0.606; - public readonly double Length = 1.106; - public readonly double MaxVelocity = 1.5; - public readonly double MaxAngularVelocity = 0.5; - public readonly double Acceleration = 2; - public readonly double Deceleration = 10; - public readonly NavigationType NavigationType = NavigationType.Differential; + public double MaxVelocity { get; set; } = 1.5; + public double MaxAngularVelocity { get; set; } = 0.5; + public double Acceleration { get; set; } = 2; + public double Deceleration { get; set; } = 10; } diff --git a/RobotApp/Services/Robot/Simulation/SimulationVisualization.cs b/RobotApp/Services/Robot/Simulation/SimulationVisualization.cs index ff4cbc5..44626b7 100644 --- a/RobotApp/Services/Robot/Simulation/SimulationVisualization.cs +++ b/RobotApp/Services/Robot/Simulation/SimulationVisualization.cs @@ -9,8 +9,8 @@ public class SimulationVisualization(RobotConfiguration RobotConfiguration) public double Vy { get; private set; } public double Omega { get; private set; } - private readonly double RadiusWheel = RobotConfiguration.SimulationModel.RadiusWheel; - private readonly double RadiusRobot = RobotConfiguration.SimulationModel.Width / 2; + private readonly double RadiusWheel = RobotConfiguration.RadiusWheel; + private readonly double RadiusRobot = RobotConfiguration.Width / 2; public (double x, double y, double angle) UpdatePosition(double wL, double wR, double time) { diff --git a/RobotApp/Services/RobotExtensions.cs b/RobotApp/Services/RobotExtensions.cs index efa448a..8c9372d 100644 --- a/RobotApp/Services/RobotExtensions.cs +++ b/RobotApp/Services/RobotExtensions.cs @@ -10,7 +10,6 @@ public static class RobotExtensions public static IServiceCollection AddRobot(this IServiceCollection services) { services.AddSingleton(); - services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); @@ -30,6 +29,7 @@ public static class RobotExtensions services.AddHostedServiceSingleton(); services.AddHostedServiceSingleton(); services.AddHostedServiceSingleton(); + services.AddHostedServiceSingleton(); return services; } diff --git a/RobotApp/robot.db b/RobotApp/robot.db index 1300b224f8aaee4a7559b58abdd83a7e12dbaa21..f1b47f63e62a31128625bf9ff8552eb3107afef8 100644 GIT binary patch delta 902 zcmZozz}2vTYl5_3J_7@TCJ+k&F)I+8PSi1G%-@(WpPx}+U1XZYD9*5z!Fi&i_;hYh z#-i!D9*jvW%?xVn(;Gb*zfFJ8$<#UBSBcSldbKv=gXt@}nS!^sDls-OZa>@0WX!Z( zQj?K~X*;_LV?Gm;z4Y|+y-aG8{h1^fWw)29F)n0e^JHcdk2RlOsK6*OnVnyEG9TaV z?Kb+1M+HD`nKo41{N6?ZYvfgS7)Y+spwGH+qD2TC(=nM~JmV7$cU zXwD`cst-1(J}5sazXTFHkeG@$#1N8bDKcXh*Vkw46fQ|j%1OmgfFU669OUX4;;Inh z=;Y(7poAf>q@cmer2que<@^{;Siy;7I-fVAj1ZU^T#%ZY5|&z28Jv?}K7FGfqa+_f z94;n+5cA6~%1z7xt3(p@O-;!x%>|1JBGmd9lw`sbBN^+KSX=^Disl5cxT8Pg{^`ZO zjIP_uy%_&6v3VN9qhfly8>8;_9lnenOw)TD8SS?3bz@x1IDMHjqxtrej*NF1x8HSU VtYliWz+h7Yi@|@8Uy3<5ovtk#IFm3Lo3K7^Mv$Wujkzh)RS?0X z7(|+i&`URI1iJ}>P2;TyqKjP=gsm>3FX$#{H(d^jK^^Jh2M^El`}2oqwPswc8B6x| zBtpogu}?#<5hxG%A-QqBO2Dyc`AHmOiq9Jy9t-+M27UgKBg4Lc+v^Q_y=G13ND+|_ zq(O@Hc1w;_ibE?#j#Lr(L|&4rwkwl*HFW8nRnTOc#sQ61n_WX@2QBb}xI`IQ&>KX? zB}v*5E}Qy!z44c!Q~caEE8fF5#i;2Tp3;PcayeXa;^SRMEqdAGfwlf@VlJtMGwEqH zdnThUpH3~POHA&D=fJo=xB+a-1u4u{2H*kkri;i+w+pzh5)wta(F-&5Z2^brt0E4u zXHICtIAo{I0(Mm5*tWlm1d!F)3pxAz(gpSEOe(2PCl={;4}53R9b5;R7=$nN9mx!A zcL+=tmv9yl$aCVN!KC}BJHX6-TSewlXfz~?_iunck-01&s zM-)7w*hFIMz_w!W7`R)9vh;}pbG+d?8mISrz^~ILolGMNEc2T8huXVgm;H{w9;S^5 WoM01CIEgpYyz-DwD~vC}-rs+z>ytkK