Compare commits

..

No commits in common. "e4e135e35f548a1a158c2c1421d3ec5be06440f2" and "b2eeb8cb3f3b51c62fdeeb71172a3196deac3bb9" have entirely different histories.

9 changed files with 113 additions and 185 deletions

View File

@ -77,13 +77,6 @@
</div> </div>
</div> </div>
<script>
window.ScrollToBottom = (element) => {
if (element) {
element.scrollTop = element.scrollHeight;
}
};
</script>
@code { @code {
private DateTime DateLog = DateTime.Today; private DateTime DateLog = DateTime.Today;

View File

@ -23,7 +23,7 @@
.log-level { .log-level {
display: inline-block; display: inline-block;
width: 60px; width: 46px;
} }
.log-head { .log-head {

View File

@ -8,7 +8,6 @@
@inject IJSRuntime JS @inject IJSRuntime JS
@inject IDialogService DialogService @inject IDialogService DialogService
@inject HttpClient Http @inject HttpClient Http
@inject ISnackbar Snackbar
<MudMainContent Class="pa-0 ma-0"> <MudMainContent Class="pa-0 ma-0">
<div style="height:100vh; overflow:hidden;"> <div style="height:100vh; overflow:hidden;">
@ -220,49 +219,14 @@
try try
{ {
var orderMsg = JsonSerializer.Deserialize<OrderMsg>(OrderJson);
if (orderMsg is null)
{
Snackbar.Add("Unable to convert JSON to Order", Severity.Warning);
return;
}
if (orderMsg.Nodes.Length < 1)
{
Snackbar.Add("The order must contain at least one node (number of nodes > 0)", Severity.Warning);
return;
}
if (orderMsg.Nodes.Length - 1 != orderMsg.Edges.Length)
{
Snackbar.Add("Order must have a number of edges equal to the number of nodes minus 1", Severity.Warning);
return;
}
foreach(var edge in orderMsg.Edges)
{
if (!orderMsg.Nodes.Any(n => n.NodeId == edge.StartNodeId))
{
Snackbar.Add($"The edge {edge.EdgeId} references a startNodeId {edge.StartNodeId} that does not exist in the list of nodes", Severity.Warning);
return;
}
if (!orderMsg.Nodes.Any(n => n.NodeId == edge.EndNodeId))
{
Snackbar.Add($"The edge {edge.EdgeId} references a startNodeId {edge.EndNodeId} that does not exist in the list of nodes", Severity.Warning);
return;
}
}
var response = await Http.PostAsJsonAsync( var response = await Http.PostAsJsonAsync(
"/api/order", "/api/order",
JsonSerializer.Deserialize<OrderMsg>(OrderJson) JsonSerializer.Deserialize<JsonElement>(OrderJson)
); );
sendSuccess = response.IsSuccessStatusCode; sendSuccess = response.IsSuccessStatusCode;
} }
catch(JsonException jsonEx)
{
Snackbar.Add($"Json to Order failed: {jsonEx.Message}", Severity.Warning);
}
catch catch
{ {
sendSuccess = false; sendSuccess = false;

View File

@ -560,14 +560,10 @@ public partial class RobotConfigManager
private async Task LoadConfig() private async Task LoadConfig()
{ {
IsLoading = true;
StateHasChanged();
var response = await (await Http.PostAsync($"api/RobotConfigs/load", null)).Content.ReadFromJsonAsync<MessageResult>(); 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); 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 if (!response.IsSuccess) Snackbar.Add(response.Message ?? "Failed to load config", Severity.Warning);
else Snackbar.Add("Config loaded", Severity.Success); else Snackbar.Add("Config loaded", Severity.Success);
IsLoading = false;
StateHasChanged(); StateHasChanged();
} }
} }

View File

@ -268,7 +268,7 @@ public class OrderMessage
} }
).ToArray(); ).ToArray();
} }
public OrderMsg ToSchemaObject() public object ToSchemaObject()
{ {
// ================= SORT NODES BY UI SEQUENCE ================= // ================= SORT NODES BY UI SEQUENCE =================
var orderedNodes = Nodes var orderedNodes = Nodes
@ -277,50 +277,50 @@ public class OrderMessage
// ================= BUILD NODE OBJECTS ================= // ================= BUILD NODE OBJECTS =================
var nodeObjects = orderedNodes var nodeObjects = orderedNodes
.Select((n, index) => new Node .Select((n, index) => new
{ {
NodeId = n.NodeId, nodeId = n.NodeId,
SequenceId = index * 2, // ✅ NODE = EVEN sequenceId = index * 2, // ✅ NODE = EVEN
Released = n.Released, released = n.Released,
NodePosition = new NodePosition nodePosition = new
{ {
X = n.NodePosition.X, x = n.NodePosition.X,
Y = n.NodePosition.Y, y = n.NodePosition.Y,
Theta = n.NodePosition.Theta, theta = n.NodePosition.Theta,
AllowedDeviationXY = n.NodePosition.AllowedDeviationXY, allowedDeviationXY = n.NodePosition.AllowedDeviationXY,
AllowedDeviationTheta = n.NodePosition.AllowedDeviationTheta, allowedDeviationTheta = n.NodePosition.AllowedDeviationTheta,
MapId = string.IsNullOrWhiteSpace(n.NodePosition.MapId) mapId = string.IsNullOrWhiteSpace(n.NodePosition.MapId)
? "MAP_01" ? "MAP_01"
: n.NodePosition.MapId : n.NodePosition.MapId
}, },
Actions = n.Actions? actions = n.Actions?
.Select(a => new VDA5050.InstantAction.Action .Select(a => new
{ {
ActionId = a.ActionId, actionId = a.ActionId,
ActionType = a.ActionType, actionType = a.ActionType,
BlockingType = a.BlockingType, blockingType = a.BlockingType,
ActionParameters = a.ActionParameters? actionParameters = a.ActionParameters?
.Select(p => new ActionParameter .Select(p => new
{ {
Key = p.Key, key = p.Key,
Value = p.Value value = p.Value
}) })
.ToArray() .ToArray()
?? [] ?? Array.Empty<object>()
}) })
.ToArray() .ToArray()
?? [] ?? Array.Empty<object>()
}) })
.ToArray(); .ToArray();
// ================= BUILD EDGE OBJECTS ================= // ================= BUILD EDGE OBJECTS =================
Edge[] edgeObjects = Edges var edgeObjects = Edges
.Select((e, index) => .Select<UiEdge, object>((e, index) =>
{ {
int sequenceId = index * 2 + 1; // ✅ EDGE = ODD int sequenceId = index * 2 + 1; // ✅ EDGE = ODD
@ -339,24 +339,24 @@ public class OrderMessage
// ================================================= // =================================================
if (e.HasTrajectory && e.Trajectory != null) if (e.HasTrajectory && e.Trajectory != null)
{ {
return new Edge return new
{ {
EdgeId = baseEdge.edgeId, baseEdge.edgeId,
SequenceId = baseEdge.sequenceId, baseEdge.sequenceId,
Released= baseEdge.released, baseEdge.released,
StartNodeId= baseEdge.startNodeId, baseEdge.startNodeId,
EndNodeId= baseEdge.endNodeId, baseEdge.endNodeId,
Trajectory = new Trajectory trajectory = new
{ {
Degree = e.Trajectory.Degree, degree = e.Trajectory.Degree,
KnotVector = e.Trajectory.KnotVector, knotVector = e.Trajectory.KnotVector,
ControlPoints = e.Trajectory.ControlPoints controlPoints = e.Trajectory.ControlPoints
.Select(p => new ControlPoint { X = p.X, Y = p.Y , Weight = 1}) .Select(p => new { x = p.X, y = p.Y })
.ToArray() .ToArray()
}, },
Actions = [] actions = Array.Empty<object>()
}; };
} }
@ -365,15 +365,14 @@ public class OrderMessage
// ================================================= // =================================================
if (e.Radius <= 0) if (e.Radius <= 0)
{ {
return new Edge return new
{ {
EdgeId = baseEdge.edgeId, baseEdge.edgeId,
SequenceId = baseEdge.sequenceId, baseEdge.sequenceId,
Released = baseEdge.released, baseEdge.released,
StartNodeId = baseEdge.startNodeId, baseEdge.startNodeId,
EndNodeId = baseEdge.endNodeId, baseEdge.endNodeId,
actions = Array.Empty<object>()
Actions = []
}; };
} }
@ -393,41 +392,42 @@ public class OrderMessage
e.Quadrant e.Quadrant
); );
return new Edge return new
{ {
EdgeId = baseEdge.edgeId, baseEdge.edgeId,
SequenceId = baseEdge.sequenceId, baseEdge.sequenceId,
Released = baseEdge.released, baseEdge.released,
StartNodeId = baseEdge.startNodeId, baseEdge.startNodeId,
EndNodeId = baseEdge.endNodeId, baseEdge.endNodeId,
Actions = [] trajectory = result.Trajectory,
actions = Array.Empty<object>()
}; };
}) })
.ToArray(); .ToArray();
// ================= FINAL SCHEMA OBJECT ================= // ================= FINAL SCHEMA OBJECT =================
return new OrderMsg return new
{ {
HeaderId = (uint)HeaderId++, headerId = HeaderId++,
Timestamp = string.IsNullOrWhiteSpace(Timestamp) timestamp = string.IsNullOrWhiteSpace(Timestamp)
? DateTime.UtcNow.ToString("O") ? DateTime.UtcNow.ToString("O")
: Timestamp, : Timestamp,
Version = Version, version = Version,
Manufacturer = Manufacturer, manufacturer = Manufacturer,
SerialNumber = SerialNumber, serialNumber = SerialNumber,
OrderId = OrderId= Guid.NewGuid().ToString(), orderId = OrderId= Guid.NewGuid().ToString(),
OrderUpdateId = OrderUpdateId, orderUpdateId = OrderUpdateId,
ZoneSetId = string.IsNullOrWhiteSpace(ZoneSetId) zoneSetId = string.IsNullOrWhiteSpace(ZoneSetId)
? null ? null
: ZoneSetId, : ZoneSetId,
Nodes = nodeObjects, nodes = nodeObjects,
Edges = edgeObjects, edges = edgeObjects
}; };
} }
} }

View File

@ -147,11 +147,11 @@ public class MQTTClient : IAsyncDisposable
MqttClient = MqttClientFactory.CreateMqttClient(); MqttClient = MqttClientFactory.CreateMqttClient();
MqttClient.ApplicationMessageReceivedAsync -= OnMessageReceived; //MqttClient.ApplicationMessageReceivedAsync -= OnMessageReceived;
MqttClient.ApplicationMessageReceivedAsync += OnMessageReceived; MqttClient.ApplicationMessageReceivedAsync += OnMessageReceived;
MqttClient.DisconnectedAsync -= OnDisconnected; //MqttClient.DisconnectedAsync -= OnDisconnected;
MqttClient.DisconnectedAsync += OnDisconnected; MqttClient.DisconnectedAsync += OnDisconnected;
while (!cancellationToken.IsCancellationRequested && !IsDisposed) while (!cancellationToken.IsCancellationRequested)
{ {
try try
{ {
@ -168,11 +168,7 @@ public class MQTTClient : IAsyncDisposable
{ {
Logger.Error($"Lỗi khi tạo MQTT client: {ex.Message}"); Logger.Error($"Lỗi khi tạo MQTT client: {ex.Message}");
} }
try await Task.Delay(3000, cancellationToken);
{
await Task.Delay(3000, cancellationToken);
}
catch { }
} }
} }
else throw new ObjectDisposedException(nameof(MQTTClient)); else throw new ObjectDisposedException(nameof(MQTTClient));
@ -223,10 +219,10 @@ 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 | System.Security.Authentication.SslProtocols.Tls13)
.WithCertificateValidationHandler(ValidateCertificates) //.WithCertificateValidationHandler(ValidateCertificates)
.WithClientCertificatesProvider(new MQTTClientCertificatesProvider(VDA5050Setting.CerFile, VDA5050Setting.KeyFile)) .WithClientCertificatesProvider(new MQTTClientCertificatesProvider(VDA5050Setting.CerFile, VDA5050Setting.KeyFile))
.Build(); .Build();
builder = builder.WithTlsOptions(tlsOptions); builder = builder.WithTlsOptions(tlsOptions);
} }
MqttClientOptions = builder.Build(); MqttClientOptions = builder.Build();
} }
@ -255,42 +251,42 @@ public class MQTTClient : IAsyncDisposable
{ {
//if (!IsDisposed) //if (!IsDisposed)
//{ //{
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 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"); 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) while (!cancellationToken.IsCancellationRequested)
{
try
{ {
var response = await MqttClient.SubscribeAsync(MqttClientSubscribeOptions, cancellationToken); try
bool isSuccess = true;
foreach (var item in response.Items)
{ {
if (item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS0 || var response = await MqttClient.SubscribeAsync(MqttClientSubscribeOptions, cancellationToken);
item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS1 || bool isSuccess = true;
item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS2) foreach (var item in response.Items)
{ {
Logger.Info($"Subscribe thành công cho topic: {item.TopicFilter.Topic} với QoS: {item.ResultCode}"); if (item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS0 ||
} item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS1 ||
else item.ResultCode == MqttClientSubscribeResultCode.GrantedQoS2)
{ {
Logger.Warning($"Subscribe thất bại cho topic: {item.TopicFilter.Topic}. Lý do: {response.ReasonString}"); Logger.Info($"Subscribe thành công cho topic: {item.TopicFilter.Topic} với QoS: {item.ResultCode}");
isSuccess = false; }
break; 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}");
}
if (!cancellationToken.IsCancellationRequested && !IsDisposed)
{
await Task.Delay(3000, cancellationToken);
}
}
//} //}
//else throw new ObjectDisposedException(nameof(MQTTClient)); //else throw new ObjectDisposedException(nameof(MQTTClient));
} }

View File

@ -57,8 +57,8 @@ public class RobotConfiguration(IServiceProvider ServiceProvider, Logger<RobotCo
if (IsReady) if (IsReady)
{ {
var robotConnection = scope.ServiceProvider.GetRequiredService<RobotConnection>(); var robotConnection = scope.ServiceProvider.GetRequiredService<RobotConnection>();
robotConnection.StartConnection(); await robotConnection.StopConnection();
await Task.Delay(1000); _ = Task.Run(async () => await robotConnection.StartConnection(CancellationToken.None));
} }
} }
else throw new Exception("Chưa có cấu hình VDA5050."); else throw new Exception("Chưa có cấu hình VDA5050.");

View File

@ -3,7 +3,6 @@ using RobotApp.VDA5050;
using RobotApp.VDA5050.InstantAction; using RobotApp.VDA5050.InstantAction;
using RobotApp.VDA5050.Order; using RobotApp.VDA5050.Order;
using System.Text.Json; using System.Text.Json;
using System.Threading;
namespace RobotApp.Services.Robot; namespace RobotApp.Services.Robot;
@ -11,13 +10,12 @@ public class RobotConnection(RobotConfiguration RobotConfiguration,
Logger<RobotConnection> Logger, Logger<RobotConnection> Logger,
Logger<MQTTClient> MQTTClientLogger) Logger<MQTTClient> MQTTClientLogger)
{ {
private readonly VDA5050Setting VDA5050Setting = RobotConfiguration.VDA5050Setting;
private MQTTClient? MqttClient; private MQTTClient? MqttClient;
public bool IsConnected => MqttClient is not null && MqttClient.IsConnected; public bool IsConnected => MqttClient is not null && MqttClient.IsConnected;
public event Action<OrderMsg>? OrderUpdated; public event Action<OrderMsg>? OrderUpdated;
public event Action<InstantActionsMsg>? ActionUpdated; public event Action<InstantActionsMsg>? ActionUpdated;
private readonly SemaphoreSlim _connectionSemaphore = new(1, 1);
private CancellationTokenSource? _connectionCancel;
private void OrderChanged(string data) private void OrderChanged(string data)
@ -52,37 +50,17 @@ public class RobotConnection(RobotConfiguration RobotConfiguration,
public async Task<MessageResult> Publish(string topic, string data) public async Task<MessageResult> Publish(string topic, string data)
{ {
if (MqttClient is not null && MqttClient.IsConnected) return await MqttClient.PublishAsync($"{RobotConfiguration.VDA5050Setting.TopicPrefix}/{RobotConfiguration.VDA5050Setting.Manufacturer}/{RobotConfiguration.SerialNumber}/{topic}", data); if (MqttClient is not null && MqttClient.IsConnected) return await MqttClient.PublishAsync($"{VDA5050Setting.TopicPrefix}/{VDA5050Setting.Manufacturer}/{RobotConfiguration.SerialNumber}/{topic}", data);
return new(false, "Chưa có kết nối tới broker"); return new(false, "Chưa có kết nối tới broker");
} }
public void StartConnection()
public async Task StartConnection(CancellationToken cancellationToken)
{ {
Task.Run(async () => MqttClient = new MQTTClient(RobotConfiguration.SerialNumber, VDA5050Setting, MQTTClientLogger);
{ MqttClient.OrderChanged += OrderChanged;
await StartConnectionAsync(CancellationToken.None); MqttClient.InstanceActionsChanged += InstanceActionsChanged;
if(IsConnected)Logger.Info("Robot đã kết nối tới Fleet Manager."); await MqttClient.ConnectAsync(cancellationToken);
}); await MqttClient.SubscribeAsync(cancellationToken);
}
public async Task StartConnectionAsync(CancellationToken cancellationToken)
{
try
{
await StopConnection();
_connectionCancel?.Cancel();
if (_connectionSemaphore.Wait(1000, cancellationToken))
{
_connectionCancel = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
MqttClient = new MQTTClient(RobotConfiguration.SerialNumber, RobotConfiguration.VDA5050Setting, MQTTClientLogger);
MqttClient.OrderChanged += OrderChanged;
MqttClient.InstanceActionsChanged += InstanceActionsChanged;
await MqttClient.ConnectAsync(_connectionCancel.Token);
if(MqttClient is not null) await MqttClient.SubscribeAsync(_connectionCancel.Token);
}
}
finally
{
_connectionSemaphore.Release();
}
} }
public async Task StopConnection() public async Task StopConnection()

View File

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