Compare commits

..

No commits in common. "99716cc414f211fd7ef48722708b1bf6748f6eab" and "70e27da4a2272f259ed39313b7ad7b17767ad289" have entirely different histories.

19 changed files with 103 additions and 248 deletions

View File

@ -1,60 +1,40 @@
@using RobotApp.Common.Shares.Dtos @using RobotApp.Common.Shares.Dtos
@implements IDisposable <EditForm Model="@Local" OnValidSubmit="OnSubmit">
<div class="d-flex w-100 h-100 flex-column">
<EditForm EditContext="EditContext">
<DataAnnotationsValidator /> <DataAnnotationsValidator />
<ValidationSummary />
<div class="form-check mb-2"> <div class="form-check mb-2">
<InputCheckbox class="form-check-input" @bind-Value="Model.EnableSimulation" /> <InputCheckbox class="form-check-input" @bind-Value="Local.EnableSimulation" />
<label class="form-check-label">Enable Simulation</label> <label class="form-check-label">Enable Simulation</label>
<ValidationMessage For="@(() => Model.EnableSimulation)" />
</div> </div>
<div class="row g-2 mb-2"> <div class="row g-2 mb-2">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Max Velocity (m/s)</label> <label class="form-label">Max Velocity (m/s)</label>
<InputNumber class="form-control" @bind-Value="Model.SimulationMaxVelocity" /> <InputNumber class="form-control" @bind-Value="Local.SimulationMaxVelocity" />
<ValidationMessage For="@(() => Model.SimulationMaxVelocity)" />
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Max Angular Velocity (rad/s)</label> <label class="form-label">Max Angular Velocity (rad/s)</label>
<InputNumber class="form-control" @bind-Value="Model.SimulationMaxAngularVelocity" /> <InputNumber class="form-control" @bind-Value="Local.SimulationMaxAngularVelocity" />
<ValidationMessage For="@(() => Model.SimulationMaxAngularVelocity)" />
</div> </div>
</div> </div>
<div class="row g-2 mb-2"> <div class="row g-2 mb-2">
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Acceleration (m/s²)</label> <label class="form-label">Acceleration (m/s²)</label>
<InputNumber class="form-control" @bind-Value="Model.SimulationAcceleration" /> <InputNumber class="form-control" @bind-Value="Local.SimulationAcceleration" />
<ValidationMessage For="@(() => Model.SimulationAcceleration)" />
</div> </div>
<div class="col-md-6"> <div class="col-md-6">
<label class="form-label">Deceleration (m/s²)</label> <label class="form-label">Deceleration (m/s²)</label>
<InputNumber class="form-control" @bind-Value="Model.SimulationDeceleration" /> <InputNumber class="form-control" @bind-Value="Local.SimulationDeceleration" />
<ValidationMessage For="@(() => Model.SimulationDeceleration)" />
</div> </div>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Description</label> <label class="form-label">Description</label>
<InputTextArea class="form-control" @bind-Value="Model.Description" /> <InputTextArea class="form-control" @bind-Value="Local.Description" />
<ValidationMessage For="@(() => Model.Description)" />
</div> </div>
</EditForm> </EditForm>
<div class="flex-grow-1" />
<div>
@if (Model.CreatedAt != default || Model.UpdatedAt != default)
{
<div class="d-flex justify-content-end mt-2">
<small class="text-muted">Created: @Model.CreatedAt.ToString("dd/MM/yyyy HH:mm:ss")</small>
<small class="text-muted ms-3">Updated: @Model.UpdatedAt.ToString("dd/MM/yyyy HH:mm:ss")</small>
</div>
}
</div>
</div>
@code { @code {
[Parameter] [Parameter]
@ -63,25 +43,17 @@
[Parameter] [Parameter]
public EventCallback<RobotSimulationConfigDto> ModelChanged { get; set; } public EventCallback<RobotSimulationConfigDto> ModelChanged { get; set; }
private EditContext? EditContext; private RobotSimulationConfigDto Local = new();
protected override void OnParametersSet() protected override void OnParametersSet()
{ {
if (EditContext is null || !EditContext.Model!.Equals(Model)) // Use record 'with' to create a shallow copy so parent isn't mutated until submit
{ Local = Model is not null ? Model with { } : new RobotSimulationConfigDto();
if (EditContext is not null) EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
EditContext = new EditContext(Model);
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
}
} }
private void EditContext_OnFieldChanged(object? sender, FieldChangedEventArgs e) private async Task OnSubmit()
{ {
_ = ModelChanged.InvokeAsync(Model); Model = Local;
} await ModelChanged.InvokeAsync(Model);
public void Dispose()
{
if (EditContext is not null) EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
} }
} }

View File

@ -39,10 +39,6 @@
<i class="mdi mdi-content-save" aria-hidden="true"></i> <i class="mdi mdi-content-save" aria-hidden="true"></i>
</button> </button>
<button type="button" class="btn rcm-icon-btn" data-tooltip="Load config" aria-label="Update" @onclick="LoadConfig">
<i class="mdi mdi-file-download" aria-hidden="true"></i>
</button>
<button type="button" class="btn rcm-icon-btn rcm-danger" data-tooltip="Delete config" aria-label="Delete" @onclick="DeleteConfig"> <button type="button" class="btn rcm-icon-btn rcm-danger" data-tooltip="Delete config" aria-label="Delete" @onclick="DeleteConfig">
<i class="mdi mdi-delete" aria-hidden="true"></i> <i class="mdi mdi-delete" aria-hidden="true"></i>
</button> </button>

View File

@ -306,7 +306,7 @@ public partial class RobotConfigManager
} }
} }
private async Task<bool> SaveCertificates() private async Task<bool> SaveCertificates(Guid id)
{ {
using var content = new MultipartFormDataContent(); using var content = new MultipartFormDataContent();
@ -325,17 +325,13 @@ public partial class RobotConfigManager
var fileContent = new StreamContent(RobotVDA5050ConfigRef.KeyFile.OpenReadStream(maxAllowedSize: RobotVDA5050ConfigRef.MaxFileSize)); var fileContent = new StreamContent(RobotVDA5050ConfigRef.KeyFile.OpenReadStream(maxAllowedSize: RobotVDA5050ConfigRef.MaxFileSize));
content.Add(fileContent, "KeyFile", RobotVDA5050ConfigRef.KeyFile.Name); content.Add(fileContent, "KeyFile", RobotVDA5050ConfigRef.KeyFile.Name);
} }
if (content.Any())
{ var response = await (await Http.PostAsync($"api/File/certificates/{id}", content)).Content.ReadFromJsonAsync<MessageResult>();
var response = await (await Http.PostAsync($"api/File/certificates", content)).Content.ReadFromJsonAsync<MessageResult>();
if (response is null) Snackbar.Add("Failed to update certificates", Severity.Warning); if (response is null) Snackbar.Add("Failed to update certificates", Severity.Warning);
else if (!response.IsSuccess) Snackbar.Add(response.Message ?? "Failed to update certificates", Severity.Warning); else if (!response.IsSuccess) Snackbar.Add(response.Message ?? "Failed to update certificates config", Severity.Warning);
else return true; else return true;
StateHasChanged();
return false; return false;
} }
return true;
}
private async Task SaveConfig() private async Task SaveConfig()
{ {
@ -381,7 +377,7 @@ public partial class RobotConfigManager
SelectedVda.VDA5050Key, SelectedVda.VDA5050Key,
SelectedVda.Description SelectedVda.Description
}; };
var saveCer = await SaveCertificates(); var saveCer = await SaveCertificates(SelectedVda.Id);
if (saveCer) result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/vda5050/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>(); if (saveCer) result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/vda5050/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
else return; else return;
break; break;
@ -555,13 +551,4 @@ public partial class RobotConfigManager
StateHasChanged(); StateHasChanged();
} }
} }
private async Task LoadConfig()
{
var response = await (await Http.PostAsync($"api/RobotConfigs/load", null)).Content.ReadFromJsonAsync<MessageResult>();
if (response is null) Snackbar.Add("Failed to load config", Severity.Warning);
else if (!response.IsSuccess) Snackbar.Add(response.Message ?? "Failed to load config", Severity.Warning);
else Snackbar.Add("Config loaded", Severity.Success);
StateHasChanged();
}
} }

View File

@ -1,6 +1,4 @@
using System.ComponentModel.DataAnnotations; namespace RobotApp.Common.Shares.Dtos;
namespace RobotApp.Common.Shares.Dtos;
#nullable disable #nullable disable
@ -8,18 +6,13 @@ public record RobotSimulationConfigDto
{ {
public Guid Id { get; set; } public Guid Id { get; set; }
public bool EnableSimulation { get; set; } public bool EnableSimulation { get; set; }
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
public double SimulationMaxVelocity { get; set; } public double SimulationMaxVelocity { get; set; }
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
public double SimulationMaxAngularVelocity { get; set; } public double SimulationMaxAngularVelocity { get; set; }
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
public double SimulationAcceleration { get; set; } public double SimulationAcceleration { get; set; }
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
public double SimulationDeceleration { get; set; } public double SimulationDeceleration { get; set; }
public DateTime CreatedAt { get; set; } public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; } public DateTime UpdatedAt { get; set; }
public bool IsActive { get; set; } public bool IsActive { get; set; }
[Required]
public string ConfigName { get; set; } public string ConfigName { get; set; }
public string Description { get; set; } public string Description { get; set; }
} }

View File

@ -12,14 +12,12 @@ public record RobotVDA5050ConfigDto
[Required] [Required]
public string VDA5050HostServer { get; set; } public string VDA5050HostServer { get; set; }
[Required] [Required]
[Range(1, 65535, ErrorMessage = "Value must be from 1 to 65535")]
public int VDA5050Port { get; set; } public int VDA5050Port { get; set; }
[Required] [Required]
public string VDA5050UserName { get; set; } public string VDA5050UserName { get; set; }
public string VDA5050Password { get; set; } public string VDA5050Password { get; set; }
public string VDA5050Manufacturer { get; set; } public string VDA5050Manufacturer { get; set; }
public string VDA5050Version { get; set; } public string VDA5050Version { get; set; }
[Range(1, 65535, ErrorMessage = "Value must be from 1 to 65535")]
public int VDA5050PublishRepeat { get; set; } public int VDA5050PublishRepeat { get; set; }
public bool VDA5050EnablePassword { get; set; } public bool VDA5050EnablePassword { get; set; }
public bool VDA5050EnableTls { get; set; } public bool VDA5050EnableTls { get; set; }

View File

@ -1,6 +1,4 @@
using System.ComponentModel.DataAnnotations.Schema; namespace RobotApp.VDA5050;
namespace RobotApp.VDA5050;
public class VDA5050Setting public class VDA5050Setting
@ -14,7 +12,4 @@ public class VDA5050Setting
public int PublishRepeat { get; set; } = 2; public int PublishRepeat { get; set; } = 2;
public bool EnablePassword { get; set; } = false; public bool EnablePassword { get; set; } = false;
public bool EnableTls { get; set; } = false; public bool EnableTls { get; set; } = false;
public string? CAFile { get; set; }
public string? CerFile { get; set; }
public string? KeyFile { get; set; }
} }

View File

@ -4,4 +4,4 @@
@rendermode InteractiveServer @rendermode InteractiveServer
@attribute [Authorize] @attribute [Authorize]
@* <h1>Welcome to RobotApp!</h1> *@ <h1>Welcome to RobotApp!</h1>

View File

@ -15,8 +15,8 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
private readonly string keyFilePath = "key"; private readonly string keyFilePath = "key";
[HttpPost] [HttpPost]
[Route("certificates")] [Route("certificates/{id:guid}")]
public async Task<MessageResult> UpdateMqttCertificates([FromForm(Name = "CaFile")] IFormFile? caFile, public async Task<MessageResult> UpdateMqttCertificates(Guid id, [FromForm(Name = "CaFile")] IFormFile? caFile,
[FromForm(Name = "CertFile")] IFormFile? certFile, [FromForm(Name = "CertFile")] IFormFile? certFile,
[FromForm(Name = "KeyFile")] IFormFile? keyFile) [FromForm(Name = "KeyFile")] IFormFile? keyFile)
{ {
@ -29,7 +29,7 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
var caFolder = Path.Combine(certificatesPath, caFilePath); var caFolder = Path.Combine(certificatesPath, caFilePath);
if (!Directory.Exists(caFolder)) Directory.CreateDirectory(caFolder); if (!Directory.Exists(caFolder)) Directory.CreateDirectory(caFolder);
string caExtension = Path.GetExtension(caFile.FileName); string caExtension = Path.GetExtension(caFile.FileName);
var caLocal = Path.Combine(caFolder, caFile.FileName); var caLocal = Path.Combine(caFolder, $"{id}{caExtension}");
if (System.IO.File.Exists($"{caLocal}.bk")) System.IO.File.Delete($"{caLocal}.bk"); if (System.IO.File.Exists($"{caLocal}.bk")) System.IO.File.Delete($"{caLocal}.bk");
if (System.IO.File.Exists(caLocal)) System.IO.File.Move(caLocal, $"{caLocal}.bk"); if (System.IO.File.Exists(caLocal)) System.IO.File.Move(caLocal, $"{caLocal}.bk");
@ -43,7 +43,7 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
var certFolder = Path.Combine(certificatesPath, cerFilePath); var certFolder = Path.Combine(certificatesPath, cerFilePath);
if (!Directory.Exists(certFolder)) Directory.CreateDirectory(certFolder); if (!Directory.Exists(certFolder)) Directory.CreateDirectory(certFolder);
string certExtension = Path.GetExtension(certFile.FileName); string certExtension = Path.GetExtension(certFile.FileName);
var certLocal = Path.Combine(certFolder, certFile.FileName); var certLocal = Path.Combine(certFolder, $"{id}{certExtension}");
if (System.IO.File.Exists($"{certLocal}.bk")) System.IO.File.Delete($"{certLocal}.bk"); if (System.IO.File.Exists($"{certLocal}.bk")) System.IO.File.Delete($"{certLocal}.bk");
if (System.IO.File.Exists(certLocal)) System.IO.File.Move(certLocal, $"{certLocal}.bk"); if (System.IO.File.Exists(certLocal)) System.IO.File.Move(certLocal, $"{certLocal}.bk");
@ -57,7 +57,7 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
var keyFolder = Path.Combine(certificatesPath, keyFilePath); var keyFolder = Path.Combine(certificatesPath, keyFilePath);
if (!Directory.Exists(keyFolder)) Directory.CreateDirectory(keyFolder); if (!Directory.Exists(keyFolder)) Directory.CreateDirectory(keyFolder);
string keyExtension = Path.GetExtension(keyFile.FileName); string keyExtension = Path.GetExtension(keyFile.FileName);
var keyLocal = Path.Combine(keyFolder, keyFile.FileName); var keyLocal = Path.Combine(keyFolder, $"{id}{keyExtension}");
if (System.IO.File.Exists($"{keyLocal}.bk")) System.IO.File.Delete($"{keyLocal}.bk"); if (System.IO.File.Exists($"{keyLocal}.bk")) System.IO.File.Delete($"{keyLocal}.bk");
if (System.IO.File.Exists(keyLocal)) System.IO.File.Move(keyLocal, $"{keyLocal}.bk"); if (System.IO.File.Exists(keyLocal)) System.IO.File.Move(keyLocal, $"{keyLocal}.bk");

View File

@ -63,6 +63,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotPlcConfigAsync();
return new(true, "PLC configuration updated successfully."); return new(true, "PLC configuration updated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -156,6 +158,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
await RobotConfiguration.LoadRobotPlcConfigAsync();
return new(true, $"PLC configuration {config.ConfigName} activated successfully."); return new(true, $"PLC configuration {config.ConfigName} activated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -234,6 +238,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadVDA5050ConfigAsync();
return new(true, "VDA5050 configuration updated successfully."); return new(true, "VDA5050 configuration updated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -347,6 +353,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
await RobotConfiguration.LoadVDA5050ConfigAsync();
return new(true, $"VDA5050 configuration {config.ConfigName} activated successfully."); return new(true, $"VDA5050 configuration {config.ConfigName} activated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -409,6 +417,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotConfigAsync();
return new(true, "Robot configuration updated successfully."); return new(true, "Robot configuration updated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -506,6 +516,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
await RobotConfiguration.LoadRobotConfigAsync();
return new(true, $"Robot configuration {config.ConfigName} activated successfully."); return new(true, $"Robot configuration {config.ConfigName} activated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -568,6 +580,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotSimulationConfigAsync();
return new(true, "Simulation configuration updated successfully."); return new(true, "Simulation configuration updated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -665,6 +679,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
await RobotConfiguration.LoadRobotSimulationConfigAsync();
return new(true, $"Simulation configuration {config.ConfigName} activated successfully."); return new(true, $"Simulation configuration {config.ConfigName} activated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -730,6 +746,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
if (config.IsActive) await RobotConfiguration.LoadRobotSafetyConfigAsync();
return new(true, "Safety configuration updated successfully."); return new(true, "Safety configuration updated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -831,6 +849,8 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
config.UpdatedAt = DateTime.Now; config.UpdatedAt = DateTime.Now;
await AppDb.SaveChangesAsync(); await AppDb.SaveChangesAsync();
await RobotConfiguration.LoadRobotSafetyConfigAsync();
return new(true, $"Safety configuration {config.ConfigName} activated successfully."); return new(true, $"Safety configuration {config.ConfigName} activated successfully.");
} }
catch (Exception ex) catch (Exception ex)
@ -839,20 +859,4 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
return new(false, "An error occurred while updating the active Safety configuration."); return new(false, "An error occurred while updating the active Safety configuration.");
} }
} }
[HttpPost]
[Route("load")]
public async Task<MessageResult> LoadConfig()
{
try
{
//await RobotConfiguration.LoadVDA5050ConfigAsync();
return new(true, "Robot configuration loaded successfully.");
}
catch (Exception ex)
{
Logger.Error($"Error in Load Robot Config: {ex.Message}");
return new(false, "An error occurred while loading the Robot configuration.");
}
}
} }

View File

@ -18,7 +18,7 @@
"launchBrowser": true, "launchBrowser": true,
"workingDirectory": "$(TargetDir)", "workingDirectory": "$(TargetDir)",
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
"applicationUrl": "https://0.0.0.0:7150;http://localhost:5229", "applicationUrl": "https://localhost:7150;http://localhost:5229",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }

View File

@ -2,7 +2,7 @@
using MQTTnet.Protocol; using MQTTnet.Protocol;
using RobotApp.Common.Shares; using RobotApp.Common.Shares;
using RobotApp.VDA5050; using RobotApp.VDA5050;
using System.Security.Cryptography.X509Certificates; using System.Net.Security;
using System.Text; using System.Text;
namespace RobotApp.Services; namespace RobotApp.Services;
@ -167,46 +167,6 @@ public class MQTTClient : IAsyncDisposable
else throw new ObjectDisposedException(nameof(MQTTClient)); else throw new ObjectDisposedException(nameof(MQTTClient));
} }
private bool ValidateCertificates(MqttClientCertificateValidationEventArgs arg)
{
string certificatesPath = "MqttCertificates";
string caFilePath = "ca";
if (!string.IsNullOrEmpty(VDA5050Setting.CAFile))
{
if (Directory.Exists(certificatesPath))
{
var caFolder = Path.Combine(certificatesPath, caFilePath);
if (Directory.Exists(caFolder))
{
var caLocal = Path.Combine(caFolder, VDA5050Setting.CAFile);
if (File.Exists(caLocal))
{
var caCert = X509CertificateLoader.LoadCertificateFromFile(caLocal);
arg.Chain.ChainPolicy.ExtraStore.Add(caCert);
arg.Chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
arg.Chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = arg.Chain.Build((X509Certificate2)arg.Certificate);
if (isValid)
{
Console.WriteLine("Broker CERTIFICATE VALID");
}
else
{
Console.WriteLine("Broker CERTIFICATE INVALID");
foreach (var status in arg.Chain.ChainStatus)
Console.WriteLine($" -> Chain error: {status.Status} - {status.StatusInformation}");
}
return isValid;
}
}
}
}
return true;
}
private void BuildMqttClientOptions(bool enablePassword, bool enableTls) private void BuildMqttClientOptions(bool enablePassword, bool enableTls)
{ {
var builder = MqttClientFactory.CreateClientOptionsBuilder() var builder = MqttClientFactory.CreateClientOptionsBuilder()
@ -222,9 +182,16 @@ public class MQTTClient : IAsyncDisposable
{ {
var tlsOptions = new MqttClientTlsOptionsBuilder() var tlsOptions = new MqttClientTlsOptionsBuilder()
.UseTls(true) .UseTls(true)
.WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13) .WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12)
.WithCertificateValidationHandler(ValidateCertificates) .WithCertificateValidationHandler(context =>
.WithClientCertificatesProvider(new MQTTClientCertificatesProvider(VDA5050Setting.CerFile, VDA5050Setting.KeyFile)) {
if (context.SslPolicyErrors != SslPolicyErrors.None)
{
Logger.Warning($"[TLS] Lỗi: {context.SslPolicyErrors}");
return false;
}
return true;
})
.Build(); .Build();
builder.WithTlsOptions(tlsOptions); builder.WithTlsOptions(tlsOptions);
} }
@ -304,7 +271,7 @@ public class MQTTClient : IAsyncDisposable
{ {
try try
{ {
var applicationMessage = MqttClientFactory.CreateApplicationMessageBuilder() var applicationMessage = new MqttApplicationMessageBuilder()
.WithTopic(topic) .WithTopic(topic)
.WithPayload(data) .WithPayload(data)
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce) .WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)

View File

@ -1,38 +0,0 @@
using MQTTnet;
using MQTTnet.Certificates;
using System.Security.Cryptography.X509Certificates;
namespace RobotApp.Services;
public class MQTTClientCertificatesProvider(string? CerFile, string? KeyFile) : IMqttClientCertificatesProvider
{
private readonly string certificatesPath = "MqttCertificates";
private readonly string cerFilePath = "cer";
private readonly string keyFilePath = "key";
public X509CertificateCollection? GetCertificates()
{
if (!string.IsNullOrEmpty(CerFile) && !string.IsNullOrEmpty(KeyFile))
{
if (Directory.Exists(certificatesPath))
{
var certFolder = Path.Combine(certificatesPath, cerFilePath);
var keyFolder = Path.Combine(certificatesPath, keyFilePath);
if (Directory.Exists(certFolder) && Directory.Exists(keyFolder))
{
var certLocal = Path.Combine(certFolder, CerFile);
var keyLocal = Path.Combine(keyFolder, KeyFile);
if (File.Exists(certLocal) && File.Exists(keyLocal))
{
var cert = X509Certificate2.CreateFromPem(File.ReadAllText(certLocal), File.ReadAllText(keyLocal));
var pfxBytes = cert.Export(X509ContentType.Pfx);
var pfxCert = X509CertificateLoader.LoadPkcs12(pfxBytes, "", X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet);
Console.WriteLine($"Client cert loaded: {pfxCert.Subject}, HasPrivateKey: {pfxCert.HasPrivateKey}, PrivateKey Type: {pfxCert.GetRSAPrivateKey()?.GetType()}");
return [pfxCert];
}
}
}
}
return null;
}
}

View File

@ -59,7 +59,6 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
{ {
HistoryStatus = Status; HistoryStatus = Status;
Status = ActionStatus.PAUSED; Status = ActionStatus.PAUSED;
IsPaused = true;
} }
public void Resume() public void Resume()
@ -69,8 +68,7 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
public void Cancel() public void Cancel()
{ {
if (!IsCompleted) IsCancelAction = true; if(!IsCompleted) IsCancelAction = true;
if (Status == ActionStatus.WAITING) StopAction();
} }
public async Task WaitAsync(CancellationToken cancellationToken) public async Task WaitAsync(CancellationToken cancellationToken)
@ -96,7 +94,6 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
protected virtual Task StopAction() protected virtual Task StopAction()
{ {
Console.WriteLine($"StopAction {Type}");
Status = ActionStatus.FAILED; Status = ActionStatus.FAILED;
ResultDescription = "Action bị hủy bỏ."; ResultDescription = "Action bị hủy bỏ.";
return Task.CompletedTask; return Task.CompletedTask;

View File

@ -18,7 +18,7 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
ActionStatus = a.Status.ToString(), ActionStatus = a.Status.ToString(),
ResultDescription = a.ResultDescription, ResultDescription = a.ResultDescription,
})]; })];
public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => a.Type != ActionType.cancelOrder && !a.IsCompleted); public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => !a.IsCompleted);
private readonly Dictionary<string, RobotAction> Actions = []; private readonly Dictionary<string, RobotAction> Actions = [];
private readonly ConcurrentQueue<(ActionScopes scope, VDA5050.InstantAction.Action action)> ActionQueue = []; private readonly ConcurrentQueue<(ActionScopes scope, VDA5050.InstantAction.Action action)> ActionQueue = [];
@ -62,7 +62,7 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
{ {
foreach (var action in Actions.Values) foreach (var action in Actions.Values)
{ {
if (!action.IsCompleted && action.Type != ActionType.cancelOrder) action.Cancel(); if (!action.IsCompleted) action.Cancel();
} }
} }
else if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null) robotAction.Cancel(); else if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null) robotAction.Cancel();

View File

@ -49,9 +49,6 @@ public class RobotConfiguration(IServiceProvider ServiceProvider, Logger<RobotCo
VDA5050Setting.PublishRepeat = config.VDA5050PublishRepeat; VDA5050Setting.PublishRepeat = config.VDA5050PublishRepeat;
VDA5050Setting.EnablePassword = config.VDA5050EnablePassword; VDA5050Setting.EnablePassword = config.VDA5050EnablePassword;
VDA5050Setting.EnableTls = config.VDA5050EnableTls; VDA5050Setting.EnableTls = config.VDA5050EnableTls;
VDA5050Setting.CAFile = config.VDA5050CA;
VDA5050Setting.CerFile = config.VDA5050Cer;
VDA5050Setting.KeyFile = config.VDA5050Key;
SerialNumber = config.SerialNumber; SerialNumber = config.SerialNumber;
} }
else throw new Exception("Chưa có cấu hình VDA5050."); else throw new Exception("Chưa có cấu hình VDA5050.");
@ -154,19 +151,6 @@ public class RobotConfiguration(IServiceProvider ServiceProvider, Logger<RobotCo
} }
} }
public async Task LoadConfig()
{
IsReady = false;
try
{
}
catch (Exception ex)
{
Logger.Error($"Lỗi tải lại cấu hình Robot: {ex.Message}");
}
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
try try

View File

@ -39,7 +39,7 @@ public partial class RobotController
ConnectionManager.OrderUpdated += NewOrderUpdated; ConnectionManager.OrderUpdated += NewOrderUpdated;
ConnectionManager.ActionUpdated += NewInstantActionUpdated; ConnectionManager.ActionUpdated += NewInstantActionUpdated;
await ConnectionManager.StartConnection(cancellationToken); //await ConnectionManager.StartConnection(cancellationToken);
Logger.Info("Robot đã kết nối tới Fleet Manager."); Logger.Info("Robot đã kết nối tới Fleet Manager.");
StateManager.TransitionTo(SystemStateType.Standby); StateManager.TransitionTo(SystemStateType.Standby);

View File

@ -6,7 +6,6 @@ using RobotApp.VDA5050.InstantAction;
using RobotApp.VDA5050.Order; using RobotApp.VDA5050.Order;
using RobotApp.VDA5050.State; using RobotApp.VDA5050.State;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Data;
using Action = RobotApp.VDA5050.InstantAction.Action; using Action = RobotApp.VDA5050.InstantAction.Action;
namespace RobotApp.Services.Robot; namespace RobotApp.Services.Robot;
@ -112,7 +111,6 @@ public class RobotOrderController(INavigation NavigationManager,
CurrentBaseNode = null; CurrentBaseNode = null;
Nodes = []; Nodes = [];
Edges = []; Edges = [];
UpdateState();
StateManager.TransitionTo(AutoStateType.Idle); StateManager.TransitionTo(AutoStateType.Idle);
} }

View File

@ -23,6 +23,7 @@ public class RobotStates(RobotConfiguration RobotConfiguration,
IDriver DriverManager) : BackgroundService IDriver DriverManager) : BackgroundService
{ {
private uint HeaderId = 0; private uint HeaderId = 0;
private readonly string SerialNumber = RobotConfiguration.SerialNumber;
private WatchTimerAsync<RobotStates>? UpdateStateTimer; private WatchTimerAsync<RobotStates>? UpdateStateTimer;
private const int UpdateStateInterval = 1000; private const int UpdateStateInterval = 1000;
@ -41,7 +42,7 @@ public class RobotStates(RobotConfiguration RobotConfiguration,
return new StateMsg return new StateMsg
{ {
HeaderId = HeaderId++, HeaderId = HeaderId++,
SerialNumber = RobotConfiguration.SerialNumber, SerialNumber = SerialNumber,
Maps = [], Maps = [],
OrderId = OrderManager.OrderId, OrderId = OrderManager.OrderId,
OrderUpdateId = OrderManager.OrderUpdateId, OrderUpdateId = OrderManager.OrderUpdateId,

View File

@ -7,6 +7,7 @@ namespace RobotApp.Services.Robot;
public class RobotVisualization(ILocalization Localization, INavigation Navigation, RobotConfiguration RobotConfiguration, RobotConnection RobotConnection, Logger<RobotVisualization> Logger) : BackgroundService public class RobotVisualization(ILocalization Localization, INavigation Navigation, RobotConfiguration RobotConfiguration, RobotConnection RobotConnection, Logger<RobotVisualization> Logger) : BackgroundService
{ {
public string SerialNumber = RobotConfiguration.SerialNumber;
private uint HeaderId; private uint HeaderId;
private WatchTimerAsync<RobotVisualization>? UpdateTimer; private WatchTimerAsync<RobotVisualization>? UpdateTimer;
@ -16,7 +17,7 @@ public class RobotVisualization(ILocalization Localization, INavigation Navigati
return new VisualizationMsg() return new VisualizationMsg()
{ {
HeaderId = HeaderId++, HeaderId = HeaderId++,
SerialNumber = RobotConfiguration.SerialNumber, SerialNumber = SerialNumber,
MapId = Localization.CurrentActiveMap, MapId = Localization.CurrentActiveMap,
MapDescription = string.Empty, MapDescription = string.Empty,
AgvPosition = new AgvPosition() AgvPosition = new AgvPosition()