using Microsoft.AspNetCore.Components.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using MudBlazor.Services; using NLog.Web; using RobotNet10.CANOpen; using RobotNet10.CustomConfiguration.Extensions; using RobotNet10.MapManager.Extensions; using RobotNet10.NavigationTune.Data; using RobotNet10.NavigationTune.Execution; using RobotNet10.NavigationTune.Extensions; using RobotNet10.NavigationTune.Hubs; using RobotNet10.RobotApp.Client; using RobotNet10.RobotApp.Components; using RobotNet10.RobotApp.Components.Account; using RobotNet10.RobotApp.Data; using RobotNet10.RobotApp.Detection; using RobotNet10.RobotApp.Devices; using RobotNet10.RobotApp.Hubs; using RobotNet10.RobotApp.Interfaces; using RobotNet10.RobotApp.MarkerDetection; using RobotNet10.RobotApp.Modules; using RobotNet10.RobotApp.Motion; using RobotNet10.RobotApp.Services; using RobotNet10.RobotApp.Services.Navigation; using RobotNet10.RobotApp.Services.Robot; using RobotNet10.RobotApp.Services.NavigationMonitor; using RobotNet10.RobotApp.Services.Simulation; using RobotNet10.RobotApp.Navigation; using RobotNet10.RobotApp.TF3; using RobotNet10.RobotApp.Xloc; using RobotNet10.ScriptEngine; using RobotNet10.ScriptEngine.Helpers; using Microsoft.AspNetCore.Http.Features; // Print Ceres library information at startup //CeresLibraryInfo.PrintLibraryInfo(); var builder = WebApplication.CreateBuilder(args); var robotConfig = builder.Configuration["RobotConfig"]; if(string.IsNullOrEmpty(robotConfig) || !File.Exists(robotConfig)) { throw new Exception("RobotConfig not found"); } builder.Configuration.AddJsonFile(robotConfig); // Add NLog //builder.Logging.ClearProviders(); //builder.Logging.SetMinimumLevel(LogLevel.Trace); builder.Host.UseNLog(); // Add services to the container. builder.Services.AddRazorComponents() .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents() .AddAuthenticationStateSerialization(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddAuthentication() .AddBearerToken(IdentityConstants.BearerScheme); var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); builder.Services.AddDbContext(options => options.UseSqlite(connectionString, b => b.MigrationsAssembly("RobotNet10.RobotApp").UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))); builder.Services.AddDatabaseDeveloperPageExceptionFilter(); builder.Services.AddIdentity(options => { options.SignIn.RequireConfirmedAccount = true; options.Lockout.AllowedForNewUsers = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; options.Password.RequireLowercase = false; options.Password.RequireDigit = false; }) .AddEntityFrameworkStores() .AddSignInManager() .AddDefaultTokenProviders(); builder.Services.AddSingleton, IdentityNoOpEmailSender>(); builder.Services.AddMudServices(); builder.Services.AddNavigationMenu(); builder.Services.Configure(o => { o.MultipartBodyLengthLimit = 200L * 1024 * 1024; }); builder.WebHost.ConfigureKestrel(o => { o.Limits.MaxRequestBodySize = 200L * 1024 * 1024; }); // Add HttpClient for Blazor Server components (server-side calls into the same Kestrel app). // BaseAddress must be loopback: using the browser's WAN/MOXA host from this machine often fails (hairpin NAT). builder.Services.AddScoped(sp => { var navigationManager = sp.GetRequiredService(); var handler = new HttpClientHandler { ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) => true }; var publicBase = new Uri(navigationManager.BaseUri); var loopbackBase = new UriBuilder(publicBase) { Host = "127.0.0.1" }.Uri; return new HttpClient(handler) { BaseAddress = loopbackBase }; }); builder.Services.AddControllers(); builder.Services.AddSignalR(); builder.Services.AddSingleton(); builder.Services.AddSingleton(); var scriptConnectionString = builder.Configuration.GetConnectionString("ScriptEngineConnection") ?? throw new InvalidOperationException("Connection string 'ScriptEngineConnection' not found."); builder.Services.AddScriptEngine(options => options.UseSqlite(scriptConnectionString, b => b.MigrationsAssembly("RobotNet10.RobotApp"))); var mapConnectionString = builder.Configuration.GetConnectionString("MapEditorConnection") ?? throw new InvalidOperationException("Connection string 'MapEditorConnection' not found."); builder.Services.AddMapManager(builder.Configuration, options => options.UseSqlite(mapConnectionString, b => b.MigrationsAssembly("RobotNet10.RobotApp").UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)), "LayoutImage"); // var navTuneConnectionString = builder.Configuration.GetConnectionString("NavigationTuneConnection") ?? throw new InvalidOperationException("Connection string 'NavigationTuneConnection' not found."); // builder.Services.AddNavigationTuningWithRobot(options => options.UseSqlite(navTuneConnectionString, b => b.MigrationsAssembly("RobotNet10.RobotApp").UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery))); // Register adapters for Navigation Tuning // builder.Services.AddScoped(sp => // { // var localization = sp.GetRequiredService(); // return new LocalizationAdapter( // () => localization.X, // () => localization.Y, // () => localization.Theta // ); // }); builder.Services.AddScoped(sp => { var velocityController = sp.GetRequiredService(); return new VelocityControllerAdapter( () => velocityController.RawVelocity, (linear, angular) => velocityController.SetVelocity(linear, angular), () => velocityController.GetModelConfidence(), acc => velocityController.SetAcceleration(acc), dec => velocityController.SetDeceleration(dec) ); }); // Add Custom Configuration builder.Services.AddCustomConfiguration(builder.Configuration, "StorageConfig"); builder.Services.AddCanOpenManager(); builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddSingleton(typeof(RobotNet10.RobotApp.Services.Logger<>)); builder.Services.AddRobotSimulation(); // Core C API services (TF3 -> XLOC -> Navigation) builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); // XLOC integration is required by RobotLocalization. builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddHostedService(); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddRobot(); // Motion services builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddHostedService(); // Rotation Module services // builder.Services.AddSingleton(); // builder.Services.AddSingleton(sp => sp.GetRequiredService()); // builder.Services.AddHostedService(sp => sp.GetRequiredService()); // Lift Module: bật khi Modules:LiftModule:Enable = true var liftModuleEnabled = builder.Configuration.GetValue("Modules:LiftModule:Enable"); if (liftModuleEnabled) { builder.Services.AddSingleton(); builder.Services.AddSingleton(sp => sp.GetRequiredService()); builder.Services.AddHostedService(sp => sp.GetRequiredService()); } else { builder.Services.AddSingleton(); } // Rotation Module (vẫn dùng NoOp khi chưa bật) builder.Services.AddSingleton(); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); builder.Services.AddSingleton(); builder.Services.AddHostedService(sp => sp.GetRequiredService()); // builder.Services.AddCartographer(builder.Configuration.GetSection("Cartographer")); // builder.Services.AddSingleton(); // Navigation Monitor (Telemetry & Safety) // builder.Services.AddSingleton(); // builder.Services.AddHostedService(sp => sp.GetRequiredService()); #if DEBUG var dllPath = builder.Configuration["ScriptEngine:RuntimeDllFolder"] ?? "dlls"; if(!Directory.Exists(dllPath)) { Directory.CreateDirectory(dllPath); } var netCoreDir = Path.GetDirectoryName(typeof(object).Assembly.Location); if (!string.IsNullOrEmpty(netCoreDir)) { var coreSrc = Path.Combine(netCoreDir, "System.Private.CoreLib.dll"); var runtimeSrc = Path.Combine(netCoreDir, "System.Runtime.dll"); var linqSrc = Path.Combine(netCoreDir, "System.Linq.Expressions.dll"); var scriptSrc = typeof(RobotNet10.Script.ILogger).Assembly.Location; var robotAppSrc = typeof(RobotNet10.RobotApp.Script.IRobot).Assembly.Location; var coreDest = Path.Combine(dllPath, "System.Private.CoreLib.dll"); var runtimeDest = Path.Combine(dllPath, "System.Runtime.dll"); var linqDest = Path.Combine(dllPath, "System.Linq.Expressions.dll"); var scriptDest = Path.Combine(dllPath, "RobotNet10.Script.dll"); var robotAppDest = Path.Combine(dllPath, "RobotNet10.RobotApp.Script.dll"); if(!File.Exists(coreDest)) File.Copy(coreSrc, coreDest); if(!File.Exists(runtimeDest)) File.Copy(runtimeSrc, runtimeDest); if(!File.Exists(linqDest)) File.Copy(linqSrc, linqDest); File.Copy(scriptSrc, scriptDest, true); File.Copy(robotAppSrc, robotAppDest, true); } #endif var app = builder.Build(); // SQLite không tự tạo thư mục chứa file database - tạo trước các thư mục trong ConnectionStrings // (đường dẫn tương đối như ./bin/dbs phụ thuộc working directory nên có thể chưa tồn tại) foreach (var connectionStringEntry in builder.Configuration.GetSection("ConnectionStrings").GetChildren()) { var dataSource = connectionStringEntry.Value? .Split(';') .Select(part => part.Split('=', 2)) .Where(kv => kv.Length == 2 && kv[0].Trim().Equals("Data Source", StringComparison.OrdinalIgnoreCase)) .Select(kv => kv[1].Trim()) .FirstOrDefault(); var dbDirectory = string.IsNullOrEmpty(dataSource) ? null : Path.GetDirectoryName(dataSource); if (!string.IsNullOrEmpty(dbDirectory)) { Directory.CreateDirectory(dbDirectory); } } await app.Services.SeedApplicationDbAsync(); await app.Services.SeedScriptEngineDbAsync(); await app.Services.SeedMapManagerAsync(); var serviceProbe = app.Services.GetService(); if (serviceProbe?.IsService(typeof(TuningDbContext)) == true) { await app.Services.SeedTuningDbAsync(); } // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseWebAssemblyDebugging(); app.UseMigrationsEndPoint(); } else { app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts(); } app.UseStatusCodePagesWithReExecute("/not-found", createScopeForStatusCodePages: true); app.UseHttpsRedirection(); app.UseAntiforgery(); app.MapControllers(); app.MapStaticAssets(); app.MapRazorComponents() .AddInteractiveServerRenderMode() .AddInteractiveWebAssemblyRenderMode() .AddAdditionalAssemblies(typeof(RobotNet10.RobotApp.Client._Imports).Assembly); // Add additional endpoints required by the Identity /Account Razor components. app.MapAdditionalIdentityEndpoints(); app.MapScriptEngineHubs(); app.MapHub("/hubs/devices"); app.MapHub("/hubs/devices/battery"); app.MapHub("/hubs/devices/inertialmeasurementunit"); app.MapHub("/hubs/devices/lidar"); app.MapHub("/hubs/devices/modbustcp"); // app.MapHub("/hubs/devices/rfhandle"); app.MapHub("/hubs/devices/cameraqr"); app.MapHub("/hubs/devices/cia402servo"); app.MapHub("/hubs/motion"); app.MapHub("/hubs/odometry"); app.MapHub("/hubs/xloc/pose"); // app.MapHub("/hubs/slam"); // app.MapHub("/tuninghub"); app.MapHub("/hubs/plc/controller"); // app.MapHub("hubs/marker-detect"); // app.MapHub("/hubs/motion/navigation-monitor"); // app.MapHub("/hubs/xloc"); app.MapMotionApiEndpoints(); app.MapPlcApiEndpoints(); app.MapXlocApiEndpoints(); app.MapNavigationApiEndpoints(); app.MapTf3ApiEndpoints(); app.MapMarkerDetectionApiEndpoints(); app.Run();