From 643a34a4b4928f85bc576c3c579d507e19d11107 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=C4=83ng=20Nguy=E1=BB=85n?= Date: Tue, 28 Oct 2025 17:28:46 +0700 Subject: [PATCH] update --- RobotApp.Common.Shares/Dtos/RobotConfigDto.cs | 20 + .../Dtos/RobotPlcConfigDto.cs | 16 + .../Dtos/RobotSafetyConfig.cs | 20 + .../Dtos/RobotSimulationConfigDto.cs | 18 + .../Dtos/RobotVDA5050ConfigDto.cs | 23 + RobotApp.VDA5050/VDA5050Setting.cs | 5 +- .../Controllers/RobotConfigsController.cs | 16 + RobotApp/Data/ApplicationDbContext.cs | 8 + RobotApp/Data/ApplicationDbExtensions.cs | 113 ++++ .../20251028102815_InitConfigDb.Designer.cs | 503 ++++++++++++++++++ .../Migrations/20251028102815_InitConfigDb.cs | 118 ++++ .../ApplicationDbContextModelSnapshot.cs | 235 ++++++++ RobotApp/Data/RobotConfig.cs | 49 ++ RobotApp/Data/RobotPlcConfig.cs | 43 ++ RobotApp/Data/RobotSafetyConfig.cs | 54 ++ RobotApp/Data/RobotSimulationConfig.cs | 48 ++ RobotApp/Data/RobotVDA5050Config.cs | 70 +++ RobotApp/Program.cs | 1 - RobotApp/Services/MQTTClient.cs | 284 +++++++--- RobotApp/Services/Robot/RobotConfiguration.cs | 1 - RobotApp/Services/State/RobotStateMachine.cs | 2 +- RobotApp/robot.db | Bin 167936 -> 167936 bytes 22 files changed, 1575 insertions(+), 72 deletions(-) create mode 100644 RobotApp.Common.Shares/Dtos/RobotConfigDto.cs create mode 100644 RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs create mode 100644 RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs create mode 100644 RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs create mode 100644 RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs create mode 100644 RobotApp/Controllers/RobotConfigsController.cs create mode 100644 RobotApp/Data/Migrations/20251028102815_InitConfigDb.Designer.cs create mode 100644 RobotApp/Data/Migrations/20251028102815_InitConfigDb.cs create mode 100644 RobotApp/Data/RobotConfig.cs create mode 100644 RobotApp/Data/RobotPlcConfig.cs create mode 100644 RobotApp/Data/RobotSafetyConfig.cs create mode 100644 RobotApp/Data/RobotSimulationConfig.cs create mode 100644 RobotApp/Data/RobotVDA5050Config.cs diff --git a/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs new file mode 100644 index 0000000..eafee69 --- /dev/null +++ b/RobotApp.Common.Shares/Dtos/RobotConfigDto.cs @@ -0,0 +1,20 @@ +using RobotApp.Common.Shares.Enums; + +namespace RobotApp.Common.Shares.Dtos; + +#nullable disable + +public record RobotConfigDto +{ + public Guid Id { get; set; } + 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 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/RobotPlcConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs new file mode 100644 index 0000000..d6a2648 --- /dev/null +++ b/RobotApp.Common.Shares/Dtos/RobotPlcConfigDto.cs @@ -0,0 +1,16 @@ +namespace RobotApp.Common.Shares.Dtos; + +#nullable disable + +public record RobotPlcConfigDto +{ + public Guid Id { get; set; } + public string PLCAddress { get; set; } + public int PLCPort { get; set; } + public byte PLCUnitId { 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/RobotSafetyConfig.cs b/RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs new file mode 100644 index 0000000..9837c18 --- /dev/null +++ b/RobotApp.Common.Shares/Dtos/RobotSafetyConfig.cs @@ -0,0 +1,20 @@ +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/RobotSimulationConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs new file mode 100644 index 0000000..7630685 --- /dev/null +++ b/RobotApp.Common.Shares/Dtos/RobotSimulationConfigDto.cs @@ -0,0 +1,18 @@ +namespace RobotApp.Common.Shares.Dtos; + +#nullable disable + +public record RobotSimulationConfigDto +{ + public Guid Id { get; set; } + 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 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/RobotVDA5050ConfigDto.cs b/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs new file mode 100644 index 0000000..942981a --- /dev/null +++ b/RobotApp.Common.Shares/Dtos/RobotVDA5050ConfigDto.cs @@ -0,0 +1,23 @@ +namespace RobotApp.Common.Shares.Dtos; + +#nullable disable + +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 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 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.VDA5050/VDA5050Setting.cs b/RobotApp.VDA5050/VDA5050Setting.cs index 95cac01..9473c9b 100644 --- a/RobotApp.VDA5050/VDA5050Setting.cs +++ b/RobotApp.VDA5050/VDA5050Setting.cs @@ -1,5 +1,6 @@ namespace RobotApp.VDA5050; + public class VDA5050Setting { public string HostServer { get; set; } = string.Empty; @@ -7,6 +8,8 @@ public class VDA5050Setting public string UserName { get; set; } = "robotics"; public string Password { get; set; } = "robotics"; public string Manufacturer { get; set; } = "PhenikaaX"; - public string Version { get; set; } = "0.0.1"; + public string Version { get; set; } = "2.1.0"; public int PublishRepeat { get; set; } = 2; + public bool EnablePassword { get; set; } = false; + public bool EnableTls { get; set; } = false; } diff --git a/RobotApp/Controllers/RobotConfigsController.cs b/RobotApp/Controllers/RobotConfigsController.cs new file mode 100644 index 0000000..ad17b1b --- /dev/null +++ b/RobotApp/Controllers/RobotConfigsController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace RobotApp.Controllers; + +[Route("api/[controller]")] +[ApiController] +[Authorize] +public class RobotConfigsController(Services.Logger Logger) : ControllerBase +{ + [HttpGet] + [Route("plc")] + public void GetPLCConfig() + { + } +} diff --git a/RobotApp/Data/ApplicationDbContext.cs b/RobotApp/Data/ApplicationDbContext.cs index e1bc9ad..0530c5c 100644 --- a/RobotApp/Data/ApplicationDbContext.cs +++ b/RobotApp/Data/ApplicationDbContext.cs @@ -1,5 +1,8 @@ using Microsoft.AspNetCore.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using RobotApp.VDA5050.Order; +using RobotApp.VDA5050.State; +using static MudBlazor.CategoryTypes; namespace RobotApp.Data { @@ -8,5 +11,10 @@ namespace RobotApp.Data 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; } } } diff --git a/RobotApp/Data/ApplicationDbExtensions.cs b/RobotApp/Data/ApplicationDbExtensions.cs index 7b4383d..f89033c 100644 --- a/RobotApp/Data/ApplicationDbExtensions.cs +++ b/RobotApp/Data/ApplicationDbExtensions.cs @@ -17,6 +17,7 @@ public static class ApplicationDbExtensions await scope.ServiceProvider.SeedRolesAsync(); await scope.ServiceProvider.SeedUsersAsync(); + await scope.ServiceProvider.SeedConfigAsync(); } private static async Task SeedRolesAsync(this IServiceProvider serviceProvider) @@ -31,6 +32,15 @@ public static class ApplicationDbExtensions NormalizedName = "ADMINISTRATOR", }); } + + if (!await roleManager.RoleExistsAsync("Distributor")) + { + await roleManager.CreateAsync(new ApplicationRole() + { + Name = "Distributor", + NormalizedName = "DISTRIBUTOR", + }); + } } private static async Task SeedUsersAsync(this IServiceProvider serviceProvider) @@ -50,5 +60,108 @@ public static class ApplicationDbExtensions await userManager.CreateAsync(admin, "robotics"); await userManager.AddToRoleAsync(admin, "Administrator"); } + + if (await userManager.FindByNameAsync("distributor") is null) + { + var distributor = new ApplicationUser() + { + UserName = "distributor", + Email = "distributor@phenikaa-x.com", + NormalizedUserName = "DISTRIBUTOR", + NormalizedEmail = "DISTRIBUTOR@PHENIKAA-X.COM", + EmailConfirmed = true, + }; + + await userManager.CreateAsync(distributor, "robotics"); + await userManager.AddToRoleAsync(distributor, "Distributor"); + } + } + + private static async Task SeedConfigAsync(this IServiceProvider serviceProvider) + { + using var appDb = serviceProvider.GetRequiredService(); + + if (!await appDb.RobotConfigs.AnyAsync()) + { + var defaultConfig = new RobotConfig + { + ConfigName = "Default", + Description = "Default robot configuration", + NavigationType = Common.Shares.Enums.NavigationType.Differential, + RadiusWheel = 0.1, + Width = 0.6, + Length = 1.1, + Height = 0.5, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + }; + + appDb.RobotConfigs.Add(defaultConfig); + await appDb.SaveChangesAsync(); + } + + if (!await appDb.RobotPlcConfigs.AnyAsync()) + { + var defaultConfig = new RobotPlcConfig + { + ConfigName = "Default", + Description = "Default robot PLC configuration", + PLCAddress = "127.0.0.1", + PLCPort = 502, + PLCUnitId = 1, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + }; + + appDb.RobotPlcConfigs.Add(defaultConfig); + await appDb.SaveChangesAsync(); + } + + if (!await appDb.RobotSimulationConfigs.AnyAsync()) + { + var defaultConfig = new RobotSimulationConfig + { + ConfigName = "Default", + Description = "Default robot simulation configuration", + EnableSimulation = false, + SimulationMaxVelocity = 1.5, + SimulationMaxAngularVelocity = 0.5, + SimulationAcceleration = 2, + SimulationDeceleration = 10, + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + }; + + appDb.RobotSimulationConfigs.Add(defaultConfig); + await appDb.SaveChangesAsync(); + } + + if (!await appDb.RobotVDA5050Configs.AnyAsync()) + { + var defaultConfig = new RobotVDA5050Config + { + ConfigName = "Default", + Description = "Default robot VDA5050 configuration", + SerialNumber = "T800-002", + VDA5050HostServer = "127.0.0.1", + VDA5050Port = 1883, + VDA5050Version = "2.1.0", + VDA5050Manufacturer = "PhenikaaX", + VDA5050PublishRepeat = 2, + VDA5050EnablePassword = true, + VDA5050EnableTls = false, + VDA5050UserName = "robotics", + VDA5050Password = "robotics", + IsActive = true, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now, + }; + + appDb.RobotVDA5050Configs.Add(defaultConfig); + await appDb.SaveChangesAsync(); + } } } diff --git a/RobotApp/Data/Migrations/20251028102815_InitConfigDb.Designer.cs b/RobotApp/Data/Migrations/20251028102815_InitConfigDb.Designer.cs new file mode 100644 index 0000000..62ad2b7 --- /dev/null +++ b/RobotApp/Data/Migrations/20251028102815_InitConfigDb.Designer.cs @@ -0,0 +1,503 @@ +// +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("20251028102815_InitConfigDb")] + partial class InitConfigDb + { + /// + 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.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/20251028102815_InitConfigDb.cs b/RobotApp/Data/Migrations/20251028102815_InitConfigDb.cs new file mode 100644 index 0000000..924cfa1 --- /dev/null +++ b/RobotApp/Data/Migrations/20251028102815_InitConfigDb.cs @@ -0,0 +1,118 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RobotApp.Data.Migrations +{ + /// + public partial class InitConfigDb : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "RobotConfig", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + NavigationType = table.Column(type: "int", nullable: false), + RadiusWheel = table.Column(type: "float", nullable: false), + Width = table.Column(type: "float", nullable: false), + Length = table.Column(type: "float", nullable: false), + Height = 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_RobotConfig", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RobotPlcConfig", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + PLCAddress = table.Column(type: "nvarchar(64)", maxLength: 50, nullable: true), + PLCPort = table.Column(type: "int", nullable: false), + PLCUnitId = table.Column(type: "tinyint", 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_RobotPlcConfig", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RobotSimulationConfig", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + EnableSimulation = table.Column(type: "bit", nullable: false), + SimulationMaxVelocity = table.Column(type: "float", nullable: false), + SimulationMaxAngularVelocity = table.Column(type: "float", nullable: false), + SimulationAcceleration = table.Column(type: "float", nullable: false), + SimulationDeceleration = 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_RobotSimulationConfig", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "RobotVDA5050Config", + columns: table => new + { + Id = table.Column(type: "uniqueidentifier", nullable: false), + SerialNumber = table.Column(type: "nvarchar(64)", maxLength: 50, nullable: true), + VDA5050_HostServer = table.Column(type: "nvarchar(64)", maxLength: 100, nullable: true), + VDA5050_Port = table.Column(type: "int", nullable: false), + VDA5050_UserName = table.Column(type: "nvarchar(64)", maxLength: 50, nullable: true), + VDA5050_Password = table.Column(type: "nvarchar(64)", maxLength: 50, nullable: true), + VDA5050_Manufacturer = table.Column(type: "nvarchar(64)", maxLength: 50, nullable: true), + VDA5050_Version = table.Column(type: "nvarchar(64)", maxLength: 20, nullable: true), + VDA5050_PublishRepeat = table.Column(type: "int", nullable: false), + VDA5050_EnablePassword = table.Column(type: "bit", nullable: false), + VDA5050_EnableTls = table.Column(type: "bit", 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_RobotVDA5050Config", x => x.Id); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "RobotConfig"); + + migrationBuilder.DropTable( + name: "RobotPlcConfig"); + + migrationBuilder.DropTable( + name: "RobotSimulationConfig"); + + migrationBuilder.DropTable( + name: "RobotVDA5050Config"); + } + } +} diff --git a/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs b/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs index 3f45751..1dcd830 100644 --- a/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs +++ b/RobotApp/Data/Migrations/ApplicationDbContextModelSnapshot.cs @@ -209,6 +209,241 @@ namespace RobotApp.Migrations 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.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) diff --git a/RobotApp/Data/RobotConfig.cs b/RobotApp/Data/RobotConfig.cs new file mode 100644 index 0000000..acb9a20 --- /dev/null +++ b/RobotApp/Data/RobotConfig.cs @@ -0,0 +1,49 @@ +using RobotApp.Common.Shares.Enums; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace RobotApp.Data; + +#nullable disable + +[Table("RobotConfig")] +public class RobotConfig +{ + [Column("Id", TypeName = "uniqueidentifier")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + [Required] + public Guid Id { get; set; } + + [Column("NavigationType", TypeName = "int")] + public NavigationType NavigationType { get; set; } + + [Column("RadiusWheel", TypeName = "float")] + public double RadiusWheel { get; set; } + + [Column("Width", TypeName = "float")] + public double Width { get; set; } + + [Column("Length", TypeName = "float")] + public double Length { get; set; } + + [Column("Height", TypeName = "float")] + public double Height { get; set; } + + [Column("CreatedAt", TypeName = "datetime2")] + public DateTime CreatedAt { get; set; } + + [Column("UpdatedAt", TypeName = "datetime2")] + public DateTime UpdatedAt { get; set; } + + [Column("IsActive", TypeName = "bit")] + public bool IsActive { get; set; } = true; + + [Column("ConfigName", TypeName = "nvarchar(64)")] + [MaxLength(100)] + public string ConfigName { get; set; } + + [Column("Description", TypeName = "ntext")] + [MaxLength(500)] + public string Description { get; set; } +} diff --git a/RobotApp/Data/RobotPlcConfig.cs b/RobotApp/Data/RobotPlcConfig.cs new file mode 100644 index 0000000..5fe4b2a --- /dev/null +++ b/RobotApp/Data/RobotPlcConfig.cs @@ -0,0 +1,43 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace RobotApp.Data; + +#nullable disable + +[Table("RobotPlcConfig")] +public class RobotPlcConfig +{ + [Column("Id", TypeName = "uniqueidentifier")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + [Required] + public Guid Id { get; set; } + + [Column("PLCAddress", TypeName = "nvarchar(64)")] + [MaxLength(50)] + public string PLCAddress { get; set; } + + [Column("PLCPort", TypeName = "int")] + public int PLCPort { get; set; } + + [Column("PLCUnitId", TypeName = "tinyint")] + public byte PLCUnitId { get; set; } + + [Column("CreatedAt", TypeName = "datetime2")] + public DateTime CreatedAt { get; set; } + + [Column("UpdatedAt", TypeName = "datetime2")] + public DateTime UpdatedAt { get; set; } + + [Column("IsActive", TypeName = "bit")] + public bool IsActive { get; set; } = true; + + [Column("ConfigName", TypeName = "nvarchar(64)")] + [MaxLength(100)] + public string ConfigName { get; set; } + + [Column("Description", TypeName = "ntext")] + [MaxLength(500)] + public string Description { get; set; } +} diff --git a/RobotApp/Data/RobotSafetyConfig.cs b/RobotApp/Data/RobotSafetyConfig.cs new file mode 100644 index 0000000..53f22db --- /dev/null +++ b/RobotApp/Data/RobotSafetyConfig.cs @@ -0,0 +1,54 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace RobotApp.Data; + +#nullable disable + +[Table("RobotSafetyConfig")] +public class RobotSafetyConfig +{ + [Column("Id", TypeName = "uniqueidentifier")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + [Required] + public Guid Id { get; set; } + + [Column("SafetySpeedVerySlow", TypeName = "float")] + public double SafetySpeedVerySlow { get; set; } + + [Column("SafetySpeedSlow", TypeName = "float")] + public double SafetySpeedSlow { get; set; } + + [Column("SafetySpeedNormal", TypeName = "float")] + public double SafetySpeedNormal { get; set; } + + [Column("SafetySpeedMedium", TypeName = "float")] + public double SafetySpeedMedium { get; set; } + + [Column("SafetySpeedOptimal", TypeName = "float")] + public double SafetySpeedOptimal { get; set; } + + [Column("SafetySpeedFast", TypeName = "float")] + public double SafetySpeedFast { get; set; } + + [Column("SafetySpeedVeryFast", TypeName = "float")] + public double SafetySpeedVeryFast { get; set; } + + [Column("CreatedAt", TypeName = "datetime2")] + public DateTime CreatedAt { get; set; } + + [Column("UpdatedAt", TypeName = "datetime2")] + public DateTime UpdatedAt { get; set; } + + [Column("IsActive", TypeName = "bit")] + public bool IsActive { get; set; } = true; + + [Column("ConfigName", TypeName = "nvarchar(64)")] + [MaxLength(100)] + public string ConfigName { get; set; } + + [Column("Description", TypeName = "ntext")] + [MaxLength(500)] + public string Description { get; set; } +} diff --git a/RobotApp/Data/RobotSimulationConfig.cs b/RobotApp/Data/RobotSimulationConfig.cs new file mode 100644 index 0000000..f41476c --- /dev/null +++ b/RobotApp/Data/RobotSimulationConfig.cs @@ -0,0 +1,48 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace RobotApp.Data; + +#nullable disable + +[Table("RobotSimulationConfig")] +public class RobotSimulationConfig +{ + [Column("Id", TypeName = "uniqueidentifier")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + [Required] + public Guid Id { get; set; } + + [Column("EnableSimulation", TypeName = "bit")] + public bool EnableSimulation { get; set; } + + [Column("SimulationMaxVelocity", TypeName = "float")] + public double SimulationMaxVelocity { get; set; } + + [Column("SimulationMaxAngularVelocity", TypeName = "float")] + public double SimulationMaxAngularVelocity { get; set; } + + [Column("SimulationAcceleration", TypeName = "float")] + public double SimulationAcceleration { get; set; } + + [Column("SimulationDeceleration", TypeName = "float")] + public double SimulationDeceleration { get; set; } + + [Column("CreatedAt", TypeName = "datetime2")] + public DateTime CreatedAt { get; set; } + + [Column("UpdatedAt", TypeName = "datetime2")] + public DateTime UpdatedAt { get; set; } + + [Column("IsActive", TypeName = "bit")] + public bool IsActive { get; set; } = true; + + [Column("ConfigName", TypeName = "nvarchar(64)")] + [MaxLength(100)] + public string ConfigName { get; set; } + + [Column("Description", TypeName = "ntext")] + [MaxLength(500)] + public string Description { get; set; } +} diff --git a/RobotApp/Data/RobotVDA5050Config.cs b/RobotApp/Data/RobotVDA5050Config.cs new file mode 100644 index 0000000..b3665fb --- /dev/null +++ b/RobotApp/Data/RobotVDA5050Config.cs @@ -0,0 +1,70 @@ +using RobotApp.Common.Shares.Enums; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace RobotApp.Data; + +#nullable disable + +[Table("RobotVDA5050Config")] +public class RobotVDA5050Config +{ + [Column("Id", TypeName = "uniqueidentifier")] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + [Key] + [Required] + public Guid Id { get; set; } + + [Column("SerialNumber", TypeName = "nvarchar(64)")] + [MaxLength(50)] + public string SerialNumber { get; set; } + + [Column("VDA5050_HostServer", TypeName = "nvarchar(64)")] + [MaxLength(100)] + public string VDA5050HostServer { get; set; } + + [Column("VDA5050_Port", TypeName = "int")] + public int VDA5050Port { get; set; } = 1883; + + [Column("VDA5050_UserName", TypeName = "nvarchar(64)")] + [MaxLength(50)] + public string VDA5050UserName { get; set; } + + [Column("VDA5050_Password", TypeName = "nvarchar(64)")] + [MaxLength(50)] + public string VDA5050Password { get; set; } + + [Column("VDA5050_Manufacturer", TypeName = "nvarchar(64)")] + [MaxLength(50)] + public string VDA5050Manufacturer { get; set; } + + [Column("VDA5050_Version", TypeName = "nvarchar(64)")] + [MaxLength(20)] + public string VDA5050Version { get; set; } + + [Column("VDA5050_PublishRepeat", TypeName = "int")] + public int VDA5050PublishRepeat { get; set; } + + [Column("VDA5050_EnablePassword", TypeName = "bit")] + public bool VDA5050EnablePassword { get; set; } + + [Column("VDA5050_EnableTls", TypeName = "bit")] + public bool VDA5050EnableTls { get; set; } + + [Column("CreatedAt", TypeName = "datetime2")] + public DateTime CreatedAt { get; set; } + + [Column("UpdatedAt", TypeName = "datetime2")] + public DateTime UpdatedAt { get; set; } + + [Column("IsActive", TypeName = "bit")] + public bool IsActive { get; set; } = true; + + [Column("ConfigName", TypeName = "nvarchar(64)")] + [MaxLength(100)] + public string ConfigName { get; set; } + + [Column("Description", TypeName = "ntext")] + [MaxLength(500)] + public string Description { get; set; } +} diff --git a/RobotApp/Program.cs b/RobotApp/Program.cs index 358ace5..a7e797a 100644 --- a/RobotApp/Program.cs +++ b/RobotApp/Program.cs @@ -6,7 +6,6 @@ using RobotApp.Components; using RobotApp.Components.Account; using RobotApp.Data; using RobotApp.Services; -using RobotApp.Services.Robot; using RobotApp.Services.Robot.Simulation; var builder = WebApplication.CreateBuilder(args); diff --git a/RobotApp/Services/MQTTClient.cs b/RobotApp/Services/MQTTClient.cs index 20aba0f..e4d4d65 100644 --- a/RobotApp/Services/MQTTClient.cs +++ b/RobotApp/Services/MQTTClient.cs @@ -2,6 +2,8 @@ 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; @@ -9,28 +11,31 @@ namespace RobotApp.Services; public class MQTTClient : IAsyncDisposable { private readonly MqttClientFactory MqttClientFactory; - private readonly MqttClientOptions MqttClientOptions; + private MqttClientOptions MqttClientOptions; private readonly MqttClientSubscribeOptions MqttClientSubscribeOptions; private IMqttClient? MqttClient; private readonly Logger Logger; private readonly VDA5050Setting VDA5050Setting; - private bool IsReconnecing; + private readonly string ClientId; + private readonly SemaphoreSlim ReconnectionSemaphore = new(1, 1); + private volatile bool IsDisposed; public event Action? OrderChanged; public event Action? InstanceActionsChanged; - public bool IsConnected => !IsReconnecing && MqttClient is not null && MqttClient.IsConnected; + public bool IsConnected => !IsDisposed && MqttClient is not null && MqttClient.IsConnected; public MQTTClient(string clientId, VDA5050Setting setting, Logger logger) { VDA5050Setting = setting; + ClientId = clientId; Logger = logger; + MqttClientFactory = new MqttClientFactory(); MqttClientOptions = MqttClientFactory.CreateClientOptionsBuilder() - .WithTcpServer(setting.HostServer, setting.Port) - .WithCredentials(setting.UserName, setting.Password) - .WithClientId(clientId) + .WithTcpServer(VDA5050Setting.HostServer, VDA5050Setting.Port) + .WithClientId(ClientId) .WithCleanSession(true) .Build(); MqttClientSubscribeOptions = MqttClientFactory.CreateSubscribeOptionsBuilder() @@ -39,93 +44,231 @@ public class MQTTClient : IAsyncDisposable .Build(); } - public async Task ConnectAsync(CancellationToken cancellationToken = default) + private async Task OnDisconnected(MqttClientDisconnectedEventArgs args) { - MqttClient = MqttClientFactory.CreateMqttClient(); - MqttClient.DisconnectedAsync += async delegate (MqttClientDisconnectedEventArgs args) - { - if (args.ClientWasConnected && !IsReconnecing) - { - IsReconnecing = true; - Logger.Warning("Mất kết nối tới broker, đang cố gắng kết nối lại..."); - if (MqttClient.IsConnected) await MqttClient.DisconnectAsync(); - MqttClient.Dispose(); + if (IsDisposed || !args.ClientWasConnected) return; - await ConnectAsync(); - await SubscribeAsync(); - IsReconnecing = false; - } - }; - while (!cancellationToken.IsCancellationRequested) + if (!await ReconnectionSemaphore.WaitAsync(0)) + { + Logger.Info("Reconnection đã đang được thực hiện bởi thread khác"); + return; + } + + try + { + Logger.Warning("Mất kết nối tới broker, đang cố gắng kết nối lại..."); + + await CleanupCurrentClient(); + + await ReconnectWithRetry(); + } + catch (Exception ex) + { + Logger.Error($"Lỗi trong quá trình reconnection: {ex.Message}"); + } + finally + { + ReconnectionSemaphore.Release(); + } + } + private async Task CleanupCurrentClient() + { + if (MqttClient is not null) { try { - var connection = await MqttClient.ConnectAsync(MqttClientOptions, cancellationToken); - if (connection.ResultCode != MqttClientConnectResultCode.Success || !MqttClient.IsConnected) - Logger.Warning($"Không thể kết nối tới broker do: {connection.ReasonString}"); - else + MqttClient.DisconnectedAsync -= OnDisconnected; + MqttClient.ApplicationMessageReceivedAsync -= OnMessageReceived; + + if (MqttClient.IsConnected) { - Logger.Info("Kết nối tới broker thành công"); - break; + await MqttClient.DisconnectAsync(); } } catch (Exception ex) { - Logger.Error($"Lỗi khi tạo MQTT client: {ex.Message}"); + Logger.Warning($"Lỗi khi cleanup client: {ex.Message}"); + } + finally + { + MqttClient.Dispose(); + MqttClient = null; } - await Task.Delay(3000, cancellationToken); } } + private async Task ReconnectWithRetry() + { + const int maxRetries = 5; + const int retryDelayMs = 3000; + + for (int attempt = 1; attempt <= maxRetries && !IsDisposed; attempt++) + { + try + { + Logger.Info($"Thử reconnect lần {attempt}/{maxRetries}"); + + using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30)); + await ConnectAsync(cts.Token); + + if (IsConnected) + { + await SubscribeAsync(cts.Token); + Logger.Info("Reconnection thành công"); + return; + } + } + catch (OperationCanceledException) + { + Logger.Warning($"Reconnect attempt {attempt} bị timeout"); + } + catch (Exception ex) + { + Logger.Error($"Reconnect attempt {attempt} thất bại: {ex.Message}"); + } + + if (attempt < maxRetries && !IsDisposed) + { + await Task.Delay(retryDelayMs * attempt); + } + } + + Logger.Error("Không thể reconnect sau tất cả các attempts"); + } + + public async Task ConnectAsync(CancellationToken cancellationToken = default) + { + if (!IsDisposed) + { + BuildMqttClientOptions(VDA5050Setting.EnablePassword, VDA5050Setting.EnableTls); + await CleanupCurrentClient(); + + MqttClient = MqttClientFactory.CreateMqttClient(); + MqttClient.DisconnectedAsync += OnDisconnected; + while (!cancellationToken.IsCancellationRequested) + { + try + { + var connection = await MqttClient.ConnectAsync(MqttClientOptions, cancellationToken); + if (connection.ResultCode != MqttClientConnectResultCode.Success || !MqttClient.IsConnected) + Logger.Warning($"Không thể kết nối tới broker do: {connection.ReasonString}"); + else + { + Logger.Info("Kết nối tới broker thành công"); + break; + } + } + catch (Exception ex) + { + Logger.Error($"Lỗi khi tạo MQTT client: {ex.Message}"); + } + await Task.Delay(3000, cancellationToken); + } + } + else throw new ObjectDisposedException(nameof(MQTTClient)); + } + + private void BuildMqttClientOptions(bool enablePassword, bool enableTls) + { + var builder = MqttClientFactory.CreateClientOptionsBuilder() + .WithTcpServer(VDA5050Setting.HostServer, VDA5050Setting.Port) + .WithClientId(ClientId) + .WithCleanSession(true); + if (enablePassword) + { + builder.WithCredentials(VDA5050Setting.UserName, VDA5050Setting.Password); + } + + if (enableTls) + { + var tlsOptions = new MqttClientTlsOptionsBuilder() + .UseTls(true) + .WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12) + .WithCertificateValidationHandler(context => + { + if (context.SslPolicyErrors != SslPolicyErrors.None) + { + Logger.Warning($"[TLS] Lỗi: {context.SslPolicyErrors}"); + return false; + } + return true; + }) + .Build(); + builder.WithTlsOptions(tlsOptions); + } + MqttClientOptions = builder.Build(); + } + + private Task OnMessageReceived(MqttApplicationMessageReceivedEventArgs args) + { + try + { + if (IsDisposed) return Task.CompletedTask; + + var stringData = Encoding.UTF8.GetString(args.ApplicationMessage.Payload); + VDA5050Topic topic = EnumExtensions.ToTopic(args.ApplicationMessage.Topic); + + if (topic == VDA5050Topic.ORDER) OrderChanged?.Invoke(stringData); + else if (topic == VDA5050Topic.INSTANTACTIONS) InstanceActionsChanged?.Invoke(stringData); + } + catch (Exception ex) + { + Logger.Error($"Lỗi khi xử lý message: {ex.Message}"); + } + return Task.CompletedTask; + } + public async Task SubscribeAsync(CancellationToken cancellationToken = default) { - if (MqttClient is null) throw new Exception("Kết nối tới broker chưa được khởi tạo nhưng đã yêu cầu subscribe"); - if(!MqttClient.IsConnected) throw new Exception("Kết nối tới broker chưa thành công nhưng đã yêu cầu subscribe"); - - MqttClient.ApplicationMessageReceivedAsync += delegate (MqttApplicationMessageReceivedEventArgs args) + if (!IsDisposed) { - var stringData = Encoding.UTF8.GetString(args.ApplicationMessage.Payload); - VDA5050Topic topic = EnumExtensions.ToTopic(args.ApplicationMessage.Topic); - if (topic == VDA5050Topic.ORDER) OrderChanged?.Invoke(stringData); - else if (topic == VDA5050Topic.INSTANTACTIONS) InstanceActionsChanged?.Invoke(stringData); - return Task.CompletedTask; - }; + if (MqttClient is null) throw new Exception("Kết nối tới broker chưa được khởi tạo nhưng đã yêu cầu subscribe"); + if (!MqttClient.IsConnected) throw new Exception("Kết nối tới broker chưa thành công nhưng đã yêu cầu subscribe"); - while (!cancellationToken.IsCancellationRequested) - { - try + MqttClient.ApplicationMessageReceivedAsync -= OnMessageReceived; + MqttClient.ApplicationMessageReceivedAsync += OnMessageReceived; + + while (!cancellationToken.IsCancellationRequested) { - var response = await MqttClient.SubscribeAsync(MqttClientSubscribeOptions, cancellationToken); - bool isSuccess = true; - foreach (var item in response.Items) + try { - if (item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS0 || - item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS1 || - item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS2) + var response = await MqttClient.SubscribeAsync(MqttClientSubscribeOptions, cancellationToken); + bool isSuccess = true; + foreach (var item in response.Items) { - Logger.Info($"Subscribe thành công cho topic: {item.TopicFilter.Topic} với QoS: {item.ResultCode}"); - } - else - { - Logger.Warning($"Subscribe thất bại cho topic: {item.TopicFilter.Topic}. Lý do: {response.ReasonString}"); - isSuccess = false; - break; + if (item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS0 || + item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS1 || + item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS2) + { + Logger.Info($"Subscribe thành công cho topic: {item.TopicFilter.Topic} với QoS: {item.ResultCode}"); + } + else + { + Logger.Warning($"Subscribe thất bại cho topic: {item.TopicFilter.Topic}. Lý do: {response.ReasonString}"); + isSuccess = false; + break; + } } + if (isSuccess) break; + } + catch (Exception ex) + { + Logger.Error($"Lỗi khi subscribe: {ex.Message}"); + } + if (!cancellationToken.IsCancellationRequested && !IsDisposed) + { + await Task.Delay(3000, cancellationToken); } - if (isSuccess) break; } - catch (Exception ex) - { - Logger.Error($"Lỗi khi subscribe: {ex.Message}"); - } - await Task.Delay(3000, cancellationToken); } + else throw new ObjectDisposedException(nameof(MQTTClient)); } public async Task PublishAsync(string topic, string data) { + if (IsDisposed) return new(false, "Client đã được disposed"); var repeat = VDA5050Setting.PublishRepeat; - while (repeat-- > 0) + while (repeat-- > 0 && !IsDisposed) { try { @@ -149,12 +292,17 @@ public class MQTTClient : IAsyncDisposable public async ValueTask DisposeAsync() { - if (MqttClient is not null) + if (IsDisposed) return; + IsDisposed = true; + await ReconnectionSemaphore.WaitAsync(); + try { - if (MqttClient.IsConnected) await MqttClient.DisconnectAsync(); - MqttClient.Dispose(); - MqttClient = null; + await CleanupCurrentClient(); + } + finally + { + ReconnectionSemaphore.Dispose(); + GC.SuppressFinalize(this); } - GC.SuppressFinalize(this); } } diff --git a/RobotApp/Services/Robot/RobotConfiguration.cs b/RobotApp/Services/Robot/RobotConfiguration.cs index 6801e3b..adfd255 100644 --- a/RobotApp/Services/Robot/RobotConfiguration.cs +++ b/RobotApp/Services/Robot/RobotConfiguration.cs @@ -1,7 +1,6 @@ using RobotApp.Common.Shares.Enums; using RobotApp.Interfaces; using RobotApp.Services.Robot.Simulation; -using RobotApp.VDA5050.State; namespace RobotApp.Services.Robot; diff --git a/RobotApp/Services/State/RobotStateMachine.cs b/RobotApp/Services/State/RobotStateMachine.cs index d6085f3..f96bd8f 100644 --- a/RobotApp/Services/State/RobotStateMachine.cs +++ b/RobotApp/Services/State/RobotStateMachine.cs @@ -3,7 +3,7 @@ using System.Collections.Concurrent; namespace RobotApp.Services.State; -public record RobotStateMachine(Logger Logger) : IDisposable +public class RobotStateMachine(Logger Logger) : IDisposable { private readonly Lock StateLock = new(); private readonly ConcurrentDictionary> StateRegistry = []; diff --git a/RobotApp/robot.db b/RobotApp/robot.db index 56f2f758ce74ed382ea31e842051dc6035255710..1300b224f8aaee4a7559b58abdd83a7e12dbaa21 100644 GIT binary patch delta 2877 zcmc&$O>7%Q6yCAjwd2HICy;DHnzFLnq*hA0cI+k%MZt9fG5)P%ClVEewLNi`Zr1L4 zohGUxCrYW1h}2q^SaC|#N}OpE$sCXhN03mcI3SQJdP7_|aNqz)FkaiSH??C#g|PJG z*?Hf4J2UTl-|WU6{l*>r?fy;shLwYr_gAv~o4~ZQVnJ1_h)oyS^pnCV=q$_>*^U&Q5Q$D192}dvQ zMfL)+((?O}k;wWg&!ut6U-FSfhY5T`44Sa7FyeyIX#5#pX+NdW>VIkL)BVtKS@)6Z zhVJ$D>ncA2T*>EM(iI9U6xTgq3rPDjV66Ct4-Jdjlb}nZ!f5G{ADk>q>U7c*F9=It z`+-aPA_yk+Tj3L0a(Ecrye!=}150u1HSi-)P4-LselS=Fs(O0{2nW_ZWC5G~ET4d@ z;S>i0%<_j`@T6!AfL%~Dj(}eQFx&&5m~8kQMu2U#Zn>@f*muWfHTxPy{TO_y{uuuZ zY#TnsS^YIb2E)KMhL!*Gu7P6kRqzVHU$)_7&?;S}KyNObilxL57cbXf!KQpBw%#1k zk`@cNd0k{;9F)CfXx*^G9iDbYrm2YQqHmfq*Llp;X@j0ZKeH!crm{SHH4E7UEKxCJp{izj3(yE}a)biEtGqLMLU=Tb*a6 zM>F7fU)=#$N?cBCC_Z+BHB}$Jhdo#3rieRA-GgDCQJj2htd1nJ>~fZqi=yDl;ptA@ zKBg-r*czs_q}2*;cGjqKKs0$l2T#TZ1UhhOc%Q4BewNS;d66a+8dut9YX|sV325M!Sn{qj6CtcV0ikasdlurR@DFfx! zrrg1R^tKmtbjjsZq>xKG3k8vl6pkorC2(OTl@U?M%0B`(r$IGcG?#%wi&m^+ zLrf-fEhQl4H0|0d)X(tQMJ6t0g;wiKmWr@22Zc^h7w+MKy)0YPH^ZS|AImo5rthm?T()~}d?t3>Ij6OMapF`!UzRl~*c*tyPZ)t8%#v7aWwzVdj6EV|lH%+fdQ@%#{ z8@^UB^c8NcHT5f$uM_^BuZxocFRZ6KCzdDm<;VT`etC~3UKqj-pH;j<`ITI0?t z{2T?{`p&A6*MnpokgKgJxfU-(z>6pA;klJ%9feUk1hyOjAEOQpU(eG>A+5Xxw|WMA z{@PlY&7l#2ptaBam8OHZIZvZjmIj0Y7R7 z#nqRLsP?oQbbJ{G7xs383O+1$L8}OggdZC-kYGFs@j}s4?r>)ZLPrMs`n>Lj)g{oI zlo4V$wulmwj`J6q=H2l0szsG3o35M6d6(*Z=RC$X*|3N#k`pAG&+oLe9gqThagP23 zDO)4?Pp9xw2m*NfF4dfamvd?3-x!X8i(~dy7hI=yMFV)K;*yA38X3GuNuR0^i`uw2@2C9VvQjOTLp ZOcW()B9&tLs5pX~qu