Compare commits
2 Commits
70e27da4a2
...
99716cc414
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
99716cc414 | ||
|
|
73038de662 |
|
|
@ -1,40 +1,60 @@
|
|||
@using RobotApp.Common.Shares.Dtos
|
||||
|
||||
<EditForm Model="@Local" OnValidSubmit="OnSubmit">
|
||||
<DataAnnotationsValidator />
|
||||
<ValidationSummary />
|
||||
<div class="form-check mb-2">
|
||||
<InputCheckbox class="form-check-input" @bind-Value="Local.EnableSimulation" />
|
||||
<label class="form-check-label">Enable Simulation</label>
|
||||
</div>
|
||||
@implements IDisposable
|
||||
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Max Velocity (m/s)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Local.SimulationMaxVelocity" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Max Angular Velocity (rad/s)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Local.SimulationMaxAngularVelocity" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex w-100 h-100 flex-column">
|
||||
<EditForm EditContext="EditContext">
|
||||
<DataAnnotationsValidator />
|
||||
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Acceleration (m/s²)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Local.SimulationAcceleration" />
|
||||
<div class="form-check mb-2">
|
||||
<InputCheckbox class="form-check-input" @bind-Value="Model.EnableSimulation" />
|
||||
<label class="form-check-label">Enable Simulation</label>
|
||||
<ValidationMessage For="@(() => Model.EnableSimulation)" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Deceleration (m/s²)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Local.SimulationDeceleration" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Description</label>
|
||||
<InputTextArea class="form-control" @bind-Value="Local.Description" />
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Max Velocity (m/s)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Model.SimulationMaxVelocity" />
|
||||
<ValidationMessage For="@(() => Model.SimulationMaxVelocity)" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Max Angular Velocity (rad/s)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Model.SimulationMaxAngularVelocity" />
|
||||
<ValidationMessage For="@(() => Model.SimulationMaxAngularVelocity)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-2 mb-2">
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Acceleration (m/s²)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Model.SimulationAcceleration" />
|
||||
<ValidationMessage For="@(() => Model.SimulationAcceleration)" />
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label">Deceleration (m/s²)</label>
|
||||
<InputNumber class="form-control" @bind-Value="Model.SimulationDeceleration" />
|
||||
<ValidationMessage For="@(() => Model.SimulationDeceleration)" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mb-2">
|
||||
<label class="form-label">Description</label>
|
||||
<InputTextArea class="form-control" @bind-Value="Model.Description" />
|
||||
<ValidationMessage For="@(() => Model.Description)" />
|
||||
</div>
|
||||
</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>
|
||||
</EditForm>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
[Parameter]
|
||||
|
|
@ -43,17 +63,25 @@
|
|||
[Parameter]
|
||||
public EventCallback<RobotSimulationConfigDto> ModelChanged { get; set; }
|
||||
|
||||
private RobotSimulationConfigDto Local = new();
|
||||
private EditContext? EditContext;
|
||||
|
||||
protected override void OnParametersSet()
|
||||
{
|
||||
// Use record 'with' to create a shallow copy so parent isn't mutated until submit
|
||||
Local = Model is not null ? Model with { } : new RobotSimulationConfigDto();
|
||||
if (EditContext is null || !EditContext.Model!.Equals(Model))
|
||||
{
|
||||
if (EditContext is not null) EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
|
||||
EditContext = new EditContext(Model);
|
||||
EditContext.OnFieldChanged += EditContext_OnFieldChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task OnSubmit()
|
||||
private void EditContext_OnFieldChanged(object? sender, FieldChangedEventArgs e)
|
||||
{
|
||||
Model = Local;
|
||||
await ModelChanged.InvokeAsync(Model);
|
||||
_ = ModelChanged.InvokeAsync(Model);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (EditContext is not null) EditContext.OnFieldChanged -= EditContext_OnFieldChanged;
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,10 @@
|
|||
<i class="mdi mdi-content-save" aria-hidden="true"></i>
|
||||
</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">
|
||||
<i class="mdi mdi-delete" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -306,7 +306,7 @@ public partial class RobotConfigManager
|
|||
}
|
||||
}
|
||||
|
||||
private async Task<bool> SaveCertificates(Guid id)
|
||||
private async Task<bool> SaveCertificates()
|
||||
{
|
||||
using var content = new MultipartFormDataContent();
|
||||
|
||||
|
|
@ -325,12 +325,16 @@ public partial class RobotConfigManager
|
|||
var fileContent = new StreamContent(RobotVDA5050ConfigRef.KeyFile.OpenReadStream(maxAllowedSize: RobotVDA5050ConfigRef.MaxFileSize));
|
||||
content.Add(fileContent, "KeyFile", RobotVDA5050ConfigRef.KeyFile.Name);
|
||||
}
|
||||
|
||||
var response = await (await Http.PostAsync($"api/File/certificates/{id}", content)).Content.ReadFromJsonAsync<MessageResult>();
|
||||
if (response is null) Snackbar.Add("Failed to update certificates", Severity.Warning);
|
||||
else if (!response.IsSuccess) Snackbar.Add(response.Message ?? "Failed to update certificates config", Severity.Warning);
|
||||
else return true;
|
||||
return false;
|
||||
if (content.Any())
|
||||
{
|
||||
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);
|
||||
else if (!response.IsSuccess) Snackbar.Add(response.Message ?? "Failed to update certificates", Severity.Warning);
|
||||
else return true;
|
||||
StateHasChanged();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task SaveConfig()
|
||||
|
|
@ -377,7 +381,7 @@ public partial class RobotConfigManager
|
|||
SelectedVda.VDA5050Key,
|
||||
SelectedVda.Description
|
||||
};
|
||||
var saveCer = await SaveCertificates(SelectedVda.Id);
|
||||
var saveCer = await SaveCertificates();
|
||||
if (saveCer) result = await (await Http.PutAsJsonAsync($"api/RobotConfigs/vda5050/{id}", updateDto)).Content.ReadFromJsonAsync<MessageResult>();
|
||||
else return;
|
||||
break;
|
||||
|
|
@ -551,4 +555,13 @@ public partial class RobotConfigManager
|
|||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace RobotApp.Common.Shares.Dtos;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace RobotApp.Common.Shares.Dtos;
|
||||
|
||||
#nullable disable
|
||||
|
||||
|
|
@ -6,13 +8,18 @@ public record RobotSimulationConfigDto
|
|||
{
|
||||
public Guid Id { 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; }
|
||||
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
|
||||
public double SimulationMaxAngularVelocity { get; set; }
|
||||
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
|
||||
public double SimulationAcceleration { get; set; }
|
||||
[Range(0.1, 10, ErrorMessage = "Value must be from 0.1 to 10")]
|
||||
public double SimulationDeceleration { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime UpdatedAt { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
[Required]
|
||||
public string ConfigName { get; set; }
|
||||
public string Description { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ public record RobotVDA5050ConfigDto
|
|||
[Required]
|
||||
public string VDA5050HostServer { get; set; }
|
||||
[Required]
|
||||
[Range(1, 65535, ErrorMessage = "Value must be from 1 to 65535")]
|
||||
public int VDA5050Port { get; set; }
|
||||
[Required]
|
||||
public string VDA5050UserName { get; set; }
|
||||
public string VDA5050Password { get; set; }
|
||||
public string VDA5050Manufacturer { get; set; }
|
||||
public string VDA5050Version { get; set; }
|
||||
[Range(1, 65535, ErrorMessage = "Value must be from 1 to 65535")]
|
||||
public int VDA5050PublishRepeat { get; set; }
|
||||
public bool VDA5050EnablePassword { get; set; }
|
||||
public bool VDA5050EnableTls { get; set; }
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
namespace RobotApp.VDA5050;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace RobotApp.VDA5050;
|
||||
|
||||
|
||||
public class VDA5050Setting
|
||||
|
|
@ -12,4 +14,7 @@ public class VDA5050Setting
|
|||
public int PublishRepeat { get; set; } = 2;
|
||||
public bool EnablePassword { get; set; } = false;
|
||||
public bool EnableTls { get; set; } = false;
|
||||
public string? CAFile { get; set; }
|
||||
public string? CerFile { get; set; }
|
||||
public string? KeyFile { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,4 +4,4 @@
|
|||
@rendermode InteractiveServer
|
||||
|
||||
@attribute [Authorize]
|
||||
<h1>Welcome to RobotApp!</h1>
|
||||
@* <h1>Welcome to RobotApp!</h1> *@
|
||||
|
|
|
|||
|
|
@ -15,8 +15,8 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
|
|||
private readonly string keyFilePath = "key";
|
||||
|
||||
[HttpPost]
|
||||
[Route("certificates/{id:guid}")]
|
||||
public async Task<MessageResult> UpdateMqttCertificates(Guid id, [FromForm(Name = "CaFile")] IFormFile? caFile,
|
||||
[Route("certificates")]
|
||||
public async Task<MessageResult> UpdateMqttCertificates([FromForm(Name = "CaFile")] IFormFile? caFile,
|
||||
[FromForm(Name = "CertFile")] IFormFile? certFile,
|
||||
[FromForm(Name = "KeyFile")] IFormFile? keyFile)
|
||||
{
|
||||
|
|
@ -29,7 +29,7 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
|
|||
var caFolder = Path.Combine(certificatesPath, caFilePath);
|
||||
if (!Directory.Exists(caFolder)) Directory.CreateDirectory(caFolder);
|
||||
string caExtension = Path.GetExtension(caFile.FileName);
|
||||
var caLocal = Path.Combine(caFolder, $"{id}{caExtension}");
|
||||
var caLocal = Path.Combine(caFolder, caFile.FileName);
|
||||
|
||||
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");
|
||||
|
|
@ -43,7 +43,7 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
|
|||
var certFolder = Path.Combine(certificatesPath, cerFilePath);
|
||||
if (!Directory.Exists(certFolder)) Directory.CreateDirectory(certFolder);
|
||||
string certExtension = Path.GetExtension(certFile.FileName);
|
||||
var certLocal = Path.Combine(certFolder, $"{id}{certExtension}");
|
||||
var certLocal = Path.Combine(certFolder, certFile.FileName);
|
||||
|
||||
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");
|
||||
|
|
@ -57,7 +57,7 @@ public class FileController(Services.Logger<FileController> Logger) : Controller
|
|||
var keyFolder = Path.Combine(certificatesPath, keyFilePath);
|
||||
if (!Directory.Exists(keyFolder)) Directory.CreateDirectory(keyFolder);
|
||||
string keyExtension = Path.GetExtension(keyFile.FileName);
|
||||
var keyLocal = Path.Combine(keyFolder, $"{id}{keyExtension}");
|
||||
var keyLocal = Path.Combine(keyFolder, keyFile.FileName);
|
||||
|
||||
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");
|
||||
|
|
|
|||
|
|
@ -63,8 +63,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
if (config.IsActive) await RobotConfiguration.LoadRobotPlcConfigAsync();
|
||||
return new(true, "PLC configuration updated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -158,8 +156,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
await RobotConfiguration.LoadRobotPlcConfigAsync();
|
||||
return new(true, $"PLC configuration {config.ConfigName} activated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -238,8 +234,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
if (config.IsActive) await RobotConfiguration.LoadVDA5050ConfigAsync();
|
||||
return new(true, "VDA5050 configuration updated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -353,8 +347,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
await RobotConfiguration.LoadVDA5050ConfigAsync();
|
||||
return new(true, $"VDA5050 configuration {config.ConfigName} activated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -417,8 +409,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
if (config.IsActive) await RobotConfiguration.LoadRobotConfigAsync();
|
||||
return new(true, "Robot configuration updated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -516,8 +506,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
await RobotConfiguration.LoadRobotConfigAsync();
|
||||
return new(true, $"Robot configuration {config.ConfigName} activated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -580,8 +568,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
if (config.IsActive) await RobotConfiguration.LoadRobotSimulationConfigAsync();
|
||||
return new(true, "Simulation configuration updated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -679,8 +665,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
await RobotConfiguration.LoadRobotSimulationConfigAsync();
|
||||
return new(true, $"Simulation configuration {config.ConfigName} activated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -746,8 +730,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
if (config.IsActive) await RobotConfiguration.LoadRobotSafetyConfigAsync();
|
||||
return new(true, "Safety configuration updated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -849,8 +831,6 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
config.UpdatedAt = DateTime.Now;
|
||||
|
||||
await AppDb.SaveChangesAsync();
|
||||
|
||||
await RobotConfiguration.LoadRobotSafetyConfigAsync();
|
||||
return new(true, $"Safety configuration {config.ConfigName} activated successfully.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
|
@ -859,4 +839,20 @@ public class RobotConfigsController(Services.Logger<RobotConfigsController> Logg
|
|||
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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"launchBrowser": true,
|
||||
"workingDirectory": "$(TargetDir)",
|
||||
"inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
|
||||
"applicationUrl": "https://localhost:7150;http://localhost:5229",
|
||||
"applicationUrl": "https://0.0.0.0:7150;http://localhost:5229",
|
||||
"environmentVariables": {
|
||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
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;
|
||||
|
|
@ -167,6 +167,46 @@ public class MQTTClient : IAsyncDisposable
|
|||
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)
|
||||
{
|
||||
var builder = MqttClientFactory.CreateClientOptionsBuilder()
|
||||
|
|
@ -182,16 +222,9 @@ public class MQTTClient : IAsyncDisposable
|
|||
{
|
||||
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;
|
||||
})
|
||||
.WithSslProtocols(System.Security.Authentication.SslProtocols.Tls12 | System.Security.Authentication.SslProtocols.Tls13)
|
||||
.WithCertificateValidationHandler(ValidateCertificates)
|
||||
.WithClientCertificatesProvider(new MQTTClientCertificatesProvider(VDA5050Setting.CerFile, VDA5050Setting.KeyFile))
|
||||
.Build();
|
||||
builder.WithTlsOptions(tlsOptions);
|
||||
}
|
||||
|
|
@ -271,11 +304,11 @@ public class MQTTClient : IAsyncDisposable
|
|||
{
|
||||
try
|
||||
{
|
||||
var applicationMessage = new MqttApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(data)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.Build();
|
||||
var applicationMessage = MqttClientFactory.CreateApplicationMessageBuilder()
|
||||
.WithTopic(topic)
|
||||
.WithPayload(data)
|
||||
.WithQualityOfServiceLevel(MqttQualityOfServiceLevel.AtLeastOnce)
|
||||
.Build();
|
||||
if (MqttClient is null || !IsConnected) return new(false, "Chưa có kết nối tới broker");
|
||||
var publish = await MqttClient.PublishAsync(applicationMessage);
|
||||
if (!publish.IsSuccess) continue;
|
||||
|
|
|
|||
38
RobotApp/Services/MQTTClientCertificatesProvider.cs
Normal file
38
RobotApp/Services/MQTTClientCertificatesProvider.cs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -59,6 +59,7 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
|
|||
{
|
||||
HistoryStatus = Status;
|
||||
Status = ActionStatus.PAUSED;
|
||||
IsPaused = true;
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
|
|
@ -68,7 +69,8 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
|
|||
|
||||
public void Cancel()
|
||||
{
|
||||
if(!IsCompleted) IsCancelAction = true;
|
||||
if (!IsCompleted) IsCancelAction = true;
|
||||
if (Status == ActionStatus.WAITING) StopAction();
|
||||
}
|
||||
|
||||
public async Task WaitAsync(CancellationToken cancellationToken)
|
||||
|
|
@ -94,6 +96,7 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposabl
|
|||
|
||||
protected virtual Task StopAction()
|
||||
{
|
||||
Console.WriteLine($"StopAction {Type}");
|
||||
Status = ActionStatus.FAILED;
|
||||
ResultDescription = "Action bị hủy bỏ.";
|
||||
return Task.CompletedTask;
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
|
|||
ActionStatus = a.Status.ToString(),
|
||||
ResultDescription = a.ResultDescription,
|
||||
})];
|
||||
public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => !a.IsCompleted);
|
||||
public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => a.Type != ActionType.cancelOrder && !a.IsCompleted);
|
||||
|
||||
private readonly Dictionary<string, RobotAction> Actions = [];
|
||||
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)
|
||||
{
|
||||
if (!action.IsCompleted) action.Cancel();
|
||||
if (!action.IsCompleted && action.Type != ActionType.cancelOrder) action.Cancel();
|
||||
}
|
||||
}
|
||||
else if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null) robotAction.Cancel();
|
||||
|
|
|
|||
|
|
@ -49,6 +49,9 @@ public class RobotConfiguration(IServiceProvider ServiceProvider, Logger<RobotCo
|
|||
VDA5050Setting.PublishRepeat = config.VDA5050PublishRepeat;
|
||||
VDA5050Setting.EnablePassword = config.VDA5050EnablePassword;
|
||||
VDA5050Setting.EnableTls = config.VDA5050EnableTls;
|
||||
VDA5050Setting.CAFile = config.VDA5050CA;
|
||||
VDA5050Setting.CerFile = config.VDA5050Cer;
|
||||
VDA5050Setting.KeyFile = config.VDA5050Key;
|
||||
SerialNumber = config.SerialNumber;
|
||||
}
|
||||
else throw new Exception("Chưa có cấu hình VDA5050.");
|
||||
|
|
@ -151,6 +154,19 @@ 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)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ public partial class RobotController
|
|||
|
||||
ConnectionManager.OrderUpdated += NewOrderUpdated;
|
||||
ConnectionManager.ActionUpdated += NewInstantActionUpdated;
|
||||
//await ConnectionManager.StartConnection(cancellationToken);
|
||||
await ConnectionManager.StartConnection(cancellationToken);
|
||||
Logger.Info("Robot đã kết nối tới Fleet Manager.");
|
||||
StateManager.TransitionTo(SystemStateType.Standby);
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using RobotApp.VDA5050.InstantAction;
|
|||
using RobotApp.VDA5050.Order;
|
||||
using RobotApp.VDA5050.State;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Data;
|
||||
using Action = RobotApp.VDA5050.InstantAction.Action;
|
||||
|
||||
namespace RobotApp.Services.Robot;
|
||||
|
|
@ -111,6 +112,7 @@ public class RobotOrderController(INavigation NavigationManager,
|
|||
CurrentBaseNode = null;
|
||||
Nodes = [];
|
||||
Edges = [];
|
||||
UpdateState();
|
||||
StateManager.TransitionTo(AutoStateType.Idle);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,6 @@ public class RobotStates(RobotConfiguration RobotConfiguration,
|
|||
IDriver DriverManager) : BackgroundService
|
||||
{
|
||||
private uint HeaderId = 0;
|
||||
private readonly string SerialNumber = RobotConfiguration.SerialNumber;
|
||||
|
||||
private WatchTimerAsync<RobotStates>? UpdateStateTimer;
|
||||
private const int UpdateStateInterval = 1000;
|
||||
|
|
@ -42,7 +41,7 @@ public class RobotStates(RobotConfiguration RobotConfiguration,
|
|||
return new StateMsg
|
||||
{
|
||||
HeaderId = HeaderId++,
|
||||
SerialNumber = SerialNumber,
|
||||
SerialNumber = RobotConfiguration.SerialNumber,
|
||||
Maps = [],
|
||||
OrderId = OrderManager.OrderId,
|
||||
OrderUpdateId = OrderManager.OrderUpdateId,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ namespace RobotApp.Services.Robot;
|
|||
|
||||
public class RobotVisualization(ILocalization Localization, INavigation Navigation, RobotConfiguration RobotConfiguration, RobotConnection RobotConnection, Logger<RobotVisualization> Logger) : BackgroundService
|
||||
{
|
||||
public string SerialNumber = RobotConfiguration.SerialNumber;
|
||||
private uint HeaderId;
|
||||
|
||||
private WatchTimerAsync<RobotVisualization>? UpdateTimer;
|
||||
|
|
@ -17,7 +16,7 @@ public class RobotVisualization(ILocalization Localization, INavigation Navigati
|
|||
return new VisualizationMsg()
|
||||
{
|
||||
HeaderId = HeaderId++,
|
||||
SerialNumber = SerialNumber,
|
||||
SerialNumber = RobotConfiguration.SerialNumber,
|
||||
MapId = Localization.CurrentActiveMap,
|
||||
MapDescription = string.Empty,
|
||||
AgvPosition = new AgvPosition()
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user