This commit is contained in:
Đăng Nguyễn 2025-10-24 17:09:00 +07:00
parent a01f140f2e
commit 6eeed8c7b4
19 changed files with 288 additions and 96 deletions

View File

@ -1,4 +1,5 @@
using RobotApp.VDA5050.State; using RobotApp.Services.Robot.Actions;
using RobotApp.VDA5050.State;
using Action = RobotApp.VDA5050.InstantAction.Action; using Action = RobotApp.VDA5050.InstantAction.Action;
namespace RobotApp.Interfaces; namespace RobotApp.Interfaces;
@ -7,9 +8,11 @@ public interface IInstantActions
{ {
ActionState[] ActionStates { get; } ActionState[] ActionStates { get; }
bool HasActionRunning { get; } bool HasActionRunning { get; }
RobotAction? this[string actionId] { get; }
void AddOrderActions(Action[] actions); void AddOrderActions(Action[] actions);
void AddInstantAction(Action[] action); void AddInstantAction(Action[] action);
void StartOrderAction(string actionId, bool wait); void StartOrderAction(string actionId, bool wait = false);
void StopOrderAction(string actionId = "");
void ClearInstantActions(); void ClearInstantActions();
void PauseActions(); void PauseActions();
void ResumeActions(); void ResumeActions();

View File

@ -26,7 +26,6 @@ public enum NavigationProccess
public interface INavigation public interface INavigation
{ {
event Action? OnNavigationStateChanged;
event Action? OnNavigationFinished; event Action? OnNavigationFinished;
bool IsReady { get; } bool IsReady { get; }
bool Driving { get; } bool Driving { get; }
@ -43,4 +42,5 @@ public interface INavigation
void RefreshOrder(Node[] nodes, Edge[] edges); void RefreshOrder(Node[] nodes, Edge[] edges);
void Refresh(); void Refresh();
void CancelMovement(); void CancelMovement();
void SetSpeed(double speed);
} }

View File

@ -1,4 +1,5 @@
using Grpc.Core; using Grpc.Core;
using MudBlazor;
using RobotApp.Services.Exceptions; using RobotApp.Services.Exceptions;
using RobotApp.VDA5050.Factsheet; using RobotApp.VDA5050.Factsheet;
using RobotApp.VDA5050.InstantAction; using RobotApp.VDA5050.InstantAction;
@ -7,7 +8,7 @@ using RobotApp.VDA5050.Type;
namespace RobotApp.Services.Robot.Actions; namespace RobotApp.Services.Robot.Actions;
public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisposable public abstract class RobotAction(IServiceProvider serviceProvider) : IDisposable
{ {
public ActionType Type { get; private set; } public ActionType Type { get; private set; }
public string Id { get; private set; } = ""; public string Id { get; private set; } = "";
@ -30,6 +31,8 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisp
protected bool IsPaused = false; protected bool IsPaused = false;
protected ActionStatus HistoryStatus; protected ActionStatus HistoryStatus;
private bool IsCancelAction = false;
public bool Initialize(ActionScopes actionScope, VDA5050.InstantAction.Action action) public bool Initialize(ActionScopes actionScope, VDA5050.InstantAction.Action action)
{ {
Status = ActionStatus.WAITING; Status = ActionStatus.WAITING;
@ -65,7 +68,7 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisp
public void Cancel() public void Cancel()
{ {
Dispose(); if(!IsCompleted) IsCancelAction = true;
} }
public async Task WaitAsync(CancellationToken cancellationToken) public async Task WaitAsync(CancellationToken cancellationToken)
@ -89,6 +92,13 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisp
return Task.CompletedTask; return Task.CompletedTask;
} }
protected virtual Task StopAction()
{
Status = ActionStatus.FAILED;
ResultDescription = "Action bị hủy bỏ.";
return Task.CompletedTask;
}
protected virtual Task ExecuteAction() protected virtual Task ExecuteAction()
{ {
return Task.CompletedTask; return Task.CompletedTask;
@ -139,28 +149,35 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisp
{ {
try try
{ {
if (Status == ActionStatus.INITIALIZING) if (IsCancelAction)
{ {
Logger?.Info($"Thực hiện action {Type}"); await StopAction();
Status = ActionStatus.RUNNING;
await StartAction();
} }
else if (Status == ActionStatus.RUNNING) else
{ {
await ExecuteAction(); if (Status == ActionStatus.INITIALIZING)
} {
else if (Status == ActionStatus.PAUSED) Logger?.Info($"Thực hiện action {Type}");
{ Status = ActionStatus.RUNNING;
await PauseAction(); await StartAction();
} }
else if (Status == ActionStatus.WAITING && IsPaused) else if (Status == ActionStatus.RUNNING)
{ {
await ResumeAction(); await ExecuteAction();
}
else if (Status == ActionStatus.PAUSED)
{
await PauseAction();
}
else if (Status == ActionStatus.WAITING && IsPaused)
{
await ResumeAction();
}
} }
if (IsCompleted) if (IsCompleted)
{ {
await Dispose(); Dispose();
} }
} }
catch (Exception ex) catch (Exception ex)
@ -171,16 +188,11 @@ public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisp
} }
} }
private Task Dispose() public void Dispose()
{ {
if (!IsCompleted) StopAction();
ActionTimer?.Dispose(); ActionTimer?.Dispose();
ActionTimer = null; ActionTimer = null;
return Task.CompletedTask;
}
public async ValueTask DisposeAsync()
{
await Dispose();
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View File

@ -1,20 +1,37 @@
using RobotApp.Interfaces; using RobotApp.Interfaces;
using RobotApp.VDA5050.State;
namespace RobotApp.Services.Robot.Actions; namespace RobotApp.Services.Robot.Actions;
public class RobotCancelOrderAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) public class RobotCancelOrderAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
{ {
private IOrder? RobotOrder;
private IInstantActions? RobotAction;
protected override Task StartAction() protected override Task StartAction()
{ {
Scope ??= ServiceProvider.CreateAsyncScope(); Scope ??= ServiceProvider.CreateAsyncScope();
var RobotOrder = Scope.ServiceProvider.GetRequiredService<IOrder>(); RobotOrder = Scope.ServiceProvider.GetRequiredService<IOrder>();
RobotAction = Scope.ServiceProvider.GetRequiredService<IInstantActions>();
RobotOrder.StopOrder(); RobotOrder.StopOrder();
Status = VDA5050.State.ActionStatus.FINISHED; RobotAction.StopOrderAction();
return base.StartAction(); return base.StartAction();
} }
protected override Task ExecuteAction() protected override Task ExecuteAction()
{ {
if (RobotOrder is null || RobotAction is null)
{
Status = ActionStatus.FAILED;
ResultDescription = $"Không thể tìm thấy module quản lý {(RobotOrder is null ? "Order" : RobotAction is null ? "Action" : "")}";
}
else
{
if (string.IsNullOrEmpty(RobotOrder.OrderId) && !RobotAction.HasActionRunning)
{
Status = ActionStatus.FINISHED;
ResultDescription = AgvAction is null ? ResultDescription : AgvAction.ResultDescription;
}
}
return base.ExecuteAction(); return base.ExecuteAction();
} }
} }

View File

@ -2,11 +2,11 @@
public class RobotLiftDownAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) public class RobotLiftDownAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
{ {
protected override Task StartAction() protected override async Task StartAction()
{ {
await Task.Delay(2000);
Status = VDA5050.State.ActionStatus.FINISHED; Status = VDA5050.State.ActionStatus.FINISHED;
ResultDescription = AgvAction is null ? ResultDescription : AgvAction.ResultDescription; ResultDescription = AgvAction is null ? ResultDescription : AgvAction.ResultDescription;
return base.StartAction();
} }
protected override Task ExecuteAction() protected override Task ExecuteAction()

View File

@ -2,11 +2,11 @@
public class RobotLiftUpAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) public class RobotLiftUpAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
{ {
protected override Task StartAction() protected override async Task StartAction()
{ {
await Task.Delay(2000);
Status = VDA5050.State.ActionStatus.FINISHED; Status = VDA5050.State.ActionStatus.FINISHED;
ResultDescription = AgvAction is null ? ResultDescription : AgvAction.ResultDescription; ResultDescription = AgvAction is null ? ResultDescription : AgvAction.ResultDescription;
return base.StartAction();
} }
protected override Task ExecuteAction() protected override Task ExecuteAction()

View File

@ -9,6 +9,7 @@ public class RobotMutedBaseOffAction(IServiceProvider ServiceProvider) : RobotAc
Scope ??= ServiceProvider.CreateAsyncScope(); Scope ??= ServiceProvider.CreateAsyncScope();
var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>(); var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>();
RobotSafety.SetMutedBase(false); RobotSafety.SetMutedBase(false);
Status = VDA5050.State.ActionStatus.FINISHED;
return base.StartAction(); return base.StartAction();
} }

View File

@ -9,6 +9,7 @@ public class RobotMutedBaseOnAction(IServiceProvider ServiceProvider) : RobotAct
Scope ??= ServiceProvider.CreateAsyncScope(); Scope ??= ServiceProvider.CreateAsyncScope();
var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>(); var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>();
RobotSafety.SetMutedBase(true); RobotSafety.SetMutedBase(true);
Status = VDA5050.State.ActionStatus.FINISHED;
return base.StartAction(); return base.StartAction();
} }

View File

@ -9,6 +9,7 @@ public class RobotMutedLoadOffAction(IServiceProvider ServiceProvider) : RobotAc
Scope ??= ServiceProvider.CreateAsyncScope(); Scope ??= ServiceProvider.CreateAsyncScope();
var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>(); var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>();
RobotSafety.SetMutedLoad(false); RobotSafety.SetMutedLoad(false);
Status = VDA5050.State.ActionStatus.FINISHED;
return base.StartAction(); return base.StartAction();
} }

View File

@ -9,6 +9,7 @@ public class RobotMutedLoadOnAction(IServiceProvider ServiceProvider) : RobotAct
Scope ??= ServiceProvider.CreateAsyncScope(); Scope ??= ServiceProvider.CreateAsyncScope();
var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>(); var RobotSafety = Scope.ServiceProvider.GetRequiredService<ISafety>();
RobotSafety.SetMutedLoad(true); RobotSafety.SetMutedLoad(true);
Status = VDA5050.State.ActionStatus.FINISHED;
return base.StartAction(); return base.StartAction();
} }

View File

@ -20,12 +20,14 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
})]; })];
public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => !a.IsCompleted); public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => !a.IsCompleted);
private readonly ConcurrentDictionary<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 = [];
private WatchTimer<RobotActionController>? HandlerTimer; private WatchTimer<RobotActionController>? HandlerTimer;
private const int HandlerInterval = 200; private const int HandlerInterval = 200;
public RobotAction? this[string actionId] => Actions.TryGetValue(actionId, out RobotAction? action) && action is not null ? action : null;
public void AddInstantAction(VDA5050.InstantAction.Action[] actions) public void AddInstantAction(VDA5050.InstantAction.Action[] actions)
{ {
foreach (var action in actions) foreach (var action in actions)
@ -42,7 +44,7 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
} }
} }
public void StartOrderAction(string actionId, bool wait) public void StartOrderAction(string actionId, bool wait = false)
{ {
if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null) if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null)
{ {
@ -54,6 +56,18 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
} }
} }
public void StopOrderAction(string actionId = "")
{
if(string.IsNullOrEmpty(actionId))
{
foreach (var action in Actions.Values)
{
if (!action.IsCompleted) action.Cancel();
}
}
else if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null) robotAction.Cancel();
}
public void PauseActions() public void PauseActions()
{ {
foreach(var action in Actions.Values) foreach(var action in Actions.Values)
@ -78,7 +92,7 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
{ {
if (ActionQueue.TryDequeue(out var result)) if (ActionQueue.TryDequeue(out var result))
{ {
if (Actions.ContainsKey(result.action.ActionId)) return; if (Actions.ContainsKey(result.action.ActionId)) continue;
if (Enum.TryParse(result.action.ActionType, out ActionType actionType)) if (Enum.TryParse(result.action.ActionType, out ActionType actionType))
{ {
var robotAction = ActionStorage[actionType]; var robotAction = ActionStorage[actionType];
@ -87,7 +101,7 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
var init = robotAction.Initialize(result.scope, result.action); var init = robotAction.Initialize(result.scope, result.action);
if (init) if (init)
{ {
Actions.TryAdd(result.action.ActionId, robotAction); Actions.Add(result.action.ActionId, robotAction);
if(result.scope == ActionScopes.INSTANT) robotAction.Start(); if(result.scope == ActionScopes.INSTANT) robotAction.Start();
} }
} }
@ -113,6 +127,10 @@ public class RobotActionController(Logger<RobotActionController> Logger, RobotAc
public void ClearInstantActions() public void ClearInstantActions()
{ {
ActionQueue.Clear(); ActionQueue.Clear();
foreach (var action in Actions.Values)
{
action.Dispose();
}
Actions.Clear(); Actions.Clear();
} }

View File

@ -1,5 +1,7 @@
using RobotApp.Common.Shares.Enums; using RobotApp.Common.Shares.Enums;
using RobotApp.Interfaces;
using RobotApp.Services.Robot.Simulation; using RobotApp.Services.Robot.Simulation;
using RobotApp.VDA5050.State;
namespace RobotApp.Services.Robot; namespace RobotApp.Services.Robot;
@ -22,6 +24,15 @@ public class RobotConfiguration
public byte PLCUnitId { get; set; } = 1; public byte PLCUnitId { get; set; } = 1;
public bool IsSimulation { get; set; } = true; public bool IsSimulation { get; set; } = true;
public SimulationModel SimulationModel { get; set; } = new(); public SimulationModel SimulationModel { get; set; } = new();
public string XlocAddress { get; set; } = "http://192.168.195.56:50050"; public string XlocAddress { get; set; } = "http://192.168.195.56:50050";
public Dictionary<SafetySpeed, double> SafetySpeedMap = new ()
{
{ SafetySpeed.Very_Slow, 0.15},
{ SafetySpeed.Slow, 0.3},
{ SafetySpeed.Normal, 0.6},
{ SafetySpeed.Medium, 0.9},
{ SafetySpeed.Optimal, 1.2},
{ SafetySpeed.Fast, 1.5},
{ SafetySpeed.Very_Fast, 1.9},
};
} }

View File

@ -101,13 +101,32 @@ public partial class RobotController(IOrder OrderManager,
{ {
if(StateManager.HasState(AutoStateType.Executing.ToString())) if(StateManager.HasState(AutoStateType.Executing.ToString()))
{ {
if(StateManager.HasState(ExecutingStateType.Moving.ToString())) OrderManager.PauseOrder(); if (StateManager.HasState(ExecutingStateType.Moving.ToString()))
else if(StateManager.HasState(ExecutingStateType.ACT.ToString())) ActionManager.PauseActions(); {
OrderManager.PauseOrder();
StateManager.TransitionTo(AutoStateType.Paused);
}
else if (StateManager.HasState(ExecutingStateType.ACT.ToString()))
{
ActionManager.PauseActions();
StateManager.TransitionTo(AutoStateType.Paused);
}
} }
} }
public void Resume() public void Resume()
{ {
if(StateManager.HasState(AutoStateType.Paused.ToString()))
{
StateManager.TransitionTo(AutoStateType.Executing);
if (StateManager.HasState(ExecutingStateType.Moving.ToString()))
{
OrderManager.ResumeOrder();
}
else if (StateManager.HasState(ExecutingStateType.ACT.ToString()))
{
ActionManager.ResumeActions();
}
}
} }
} }

View File

@ -241,7 +241,7 @@ public class RobotFactsheet(RobotConnection RobotConnection, RobotConfiguration
ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()],
ActionParameters = [], ActionParameters = [],
ResultDescription = "Robot đã bật chế độ muted base.", ResultDescription = "Robot đã bật chế độ muted base.",
BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString(), BlockingType.HARD.ToString()],
}; };
public readonly static AgvAction MutedBaseOff = new() public readonly static AgvAction MutedBaseOff = new()
@ -251,7 +251,7 @@ public class RobotFactsheet(RobotConnection RobotConnection, RobotConfiguration
ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()],
ActionParameters = [], ActionParameters = [],
ResultDescription = "Robot đã tắt chế độ muted base.", ResultDescription = "Robot đã tắt chế độ muted base.",
BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString(), BlockingType.HARD.ToString()],
}; };
public readonly static AgvAction MutedLoadOn = new() public readonly static AgvAction MutedLoadOn = new()
@ -261,7 +261,7 @@ public class RobotFactsheet(RobotConnection RobotConnection, RobotConfiguration
ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()],
ActionParameters = [], ActionParameters = [],
ResultDescription = "Robot đã bật chế độ muted load.", ResultDescription = "Robot đã bật chế độ muted load.",
BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString(), BlockingType.HARD.ToString()],
}; };
public readonly static AgvAction MutedLoadOff = new() public readonly static AgvAction MutedLoadOff = new()
@ -271,7 +271,7 @@ public class RobotFactsheet(RobotConnection RobotConnection, RobotConfiguration
ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()],
ActionParameters = [], ActionParameters = [],
ResultDescription = "Robot đã tắt chế độ muted load.", ResultDescription = "Robot đã tắt chế độ muted load.",
BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString(), BlockingType.HARD.ToString()],
}; };
public readonly static AgvAction DockTo = new() public readonly static AgvAction DockTo = new()

View File

@ -16,7 +16,6 @@ public class RobotNavigation(RobotConfiguration RobotConfiguration, IServiceProv
private SimulationNavigation? SimNavigation; private SimulationNavigation? SimNavigation;
private readonly bool IsSimulation = RobotConfiguration.IsSimulation; private readonly bool IsSimulation = RobotConfiguration.IsSimulation;
public event Action? OnNavigationStateChanged;
public event Action? OnNavigationFinished; public event Action? OnNavigationFinished;
public void CancelMovement() public void CancelMovement()
@ -85,4 +84,9 @@ public class RobotNavigation(RobotConfiguration RobotConfiguration, IServiceProv
OnNavigationFinished?.Invoke(); OnNavigationFinished?.Invoke();
if(SimNavigation is not null) SimNavigation.OnNavigationFinished -= NavigationFinished; if(SimNavigation is not null) SimNavigation.OnNavigationFinished -= NavigationFinished;
} }
public void SetSpeed(double speed)
{
return;
}
} }

View File

@ -1,45 +1,28 @@
using Microsoft.AspNetCore.Components; using RobotApp.Common.Shares.Enums;
using RobotApp.Common.Shares.Enums;
using RobotApp.Interfaces; using RobotApp.Interfaces;
using RobotApp.Services.Exceptions; using RobotApp.Services.Exceptions;
using RobotApp.Services.State; using RobotApp.Services.State;
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 Action = RobotApp.VDA5050.InstantAction.Action; using Action = RobotApp.VDA5050.InstantAction.Action;
namespace RobotApp.Services.Robot; namespace RobotApp.Services.Robot;
public class RobotOrderController(INavigation NavigationManager, public class RobotOrderController(INavigation NavigationManager,
ILocalization Localization, ILocalization Localization,
IInstantActions ActionManager, IInstantActions ActionManager,
IError ErrorManager, IError ErrorManager,
ISafety SafetyManager,
RobotStateMachine StateManager, RobotStateMachine StateManager,
RobotConfiguration RobotConfiguration,
Logger<RobotOrderController> Logger) : IOrder Logger<RobotOrderController> Logger) : IOrder
{ {
public string OrderId { get; private set; } = string.Empty; public string OrderId { get; private set; } = string.Empty;
public int OrderUpdateId { get; private set; } public int OrderUpdateId { get; private set; }
public NodeState[] NodeStates => [.. Nodes.Select(n => new NodeState public NodeState[] NodeStates { get; private set; } = [];
{ public EdgeState[] EdgeStates { get; private set; } = [];
NodeId = n.NodeId,
Released = n.Released,
SequenceId = n.SequenceId,
NodeDescription = n.NodeDescription,
NodePosition = new()
{
X = n.NodePosition.X,
Y = n.NodePosition.Y,
Theta = n.NodePosition.Theta,
MapId = n.NodePosition.MapId
}
})];
public EdgeState[] EdgeStates => [.. Edges.Select(e => new EdgeState
{
EdgeId = e.EdgeId,
Released = e.Released,
EdgeDescription = e.EdgeDescription,
SequenceId = e.SequenceId,
Trajectory = e.Trajectory
})];
public string LastNodeId => LastNode is null ? "" : LastNode.NodeId; public string LastNodeId => LastNode is null ? "" : LastNode.NodeId;
public int LastNodeSequenceId => LastNode is null ? 0 : LastNode.SequenceId; public int LastNodeSequenceId => LastNode is null ? 0 : LastNode.SequenceId;
@ -47,13 +30,20 @@ public class RobotOrderController(INavigation NavigationManager,
private WatchTimer<RobotOrderController>? OrderTimer; private WatchTimer<RobotOrderController>? OrderTimer;
private readonly Dictionary<string, Action[]> OrderActions = []; private readonly Dictionary<string, Action[]> OrderActions = [];
private readonly ConcurrentQueue<Action> ActionWaitingRunning = [];
private OrderMsg? NewOrder;
private Node[] Nodes = []; private Node[] Nodes = [];
private Edge[] Edges = []; private Edge[] Edges = [];
private Node? CurrentBaseNode; private Node? CurrentBaseNode;
private Node? LastNode; private Node? LastNode;
private readonly Lock LockObject = new(); private readonly Lock LockObject = new();
private OrderMsg? NewOrder;
private bool IsCancelOrder = false;
private bool IsActionRunning = false;
private bool IsWaitingPaused = false;
private Action? ActionHard = null;
public void UpdateOrder(OrderMsg order) public void UpdateOrder(OrderMsg order)
{ {
@ -66,8 +56,7 @@ public class RobotOrderController(INavigation NavigationManager,
public void StopOrder() public void StopOrder()
{ {
NavigationManager.CancelMovement(); if (!string.IsNullOrEmpty(OrderId) || OrderTimer is not null) IsCancelOrder = true;
NavigationFinished();
} }
public void PauseOrder() public void PauseOrder()
@ -95,13 +84,13 @@ public class RobotOrderController(INavigation NavigationManager,
private Node? GetCurrentNode() private Node? GetCurrentNode()
{ {
Node? inNode = null; Node? inNode = null;
double minDistance = double.MaxValue; double minDistance = double.MaxValue;
foreach (var node in Nodes) foreach (var node in Nodes)
{ {
var distance = Localization.DistanceTo(node.NodePosition.X, node.NodePosition.Y); var distance = Localization.DistanceTo(node.NodePosition.X, node.NodePosition.Y);
if(distance <= node.NodePosition.AllowedDeviationXY) if (distance <= node.NodePosition.AllowedDeviationXY)
{ {
if(distance < minDistance) if (distance < minDistance)
{ {
minDistance = distance; minDistance = distance;
inNode = node; inNode = node;
@ -114,43 +103,89 @@ public class RobotOrderController(INavigation NavigationManager,
private void NavigationFinished() private void NavigationFinished()
{ {
HandleOrderStop(); HandleOrderStop();
SafetyManager.OnSafetySpeedChanged -= OnSafetySpeedChanged;
OrderId = string.Empty; OrderId = string.Empty;
OrderUpdateId = 0; OrderUpdateId = 0;
OrderActions.Clear(); OrderActions.Clear();
ActionWaitingRunning.Clear();
CurrentBaseNode = null; CurrentBaseNode = null;
Nodes = []; Nodes = [];
Edges = []; Edges = [];
StateManager.TransitionTo(AutoStateType.Idle); StateManager.TransitionTo(AutoStateType.Idle);
} }
private void HanleNewOrder(OrderMsg order) private void OnSafetySpeedChanged(SafetySpeed safetySpeed)
{
double speed = 0.15;
if (RobotConfiguration.SafetySpeedMap.TryGetValue(safetySpeed, out double safeSpeed)) speed = safeSpeed;
NavigationManager.SetSpeed(speed);
}
private void UpdateState()
{
NodeStates = [.. Nodes.Select(n => new NodeState
{
NodeId = n.NodeId,
Released = n.Released,
SequenceId = n.SequenceId,
NodeDescription = n.NodeDescription,
NodePosition = new()
{
X = n.NodePosition.X,
Y = n.NodePosition.Y,
Theta = n.NodePosition.Theta,
MapId = n.NodePosition.MapId
}
})];
EdgeStates = [.. Edges.Select(e => new EdgeState
{
EdgeId = e.EdgeId,
Released = e.Released,
EdgeDescription = e.EdgeDescription,
SequenceId = e.SequenceId,
Trajectory = e.Trajectory
})];
}
private void HandleNewOrder(OrderMsg order)
{ {
if (NavigationManager.State != NavigationState.Idle) throw new OrderException(RobotErrors.Error1012(NavigationManager.State)); if (NavigationManager.State != NavigationState.Idle) throw new OrderException(RobotErrors.Error1012(NavigationManager.State));
OrderActions.Clear();
for (int i = 0; i < order.Nodes.Length; i++) for (int i = 0; i < order.Nodes.Length; i++)
{ {
if (order.Nodes[i].Actions is not null && order.Nodes[i].Actions.Length > 0) if (order.Nodes[i].Actions is not null && order.Nodes[i].Actions.Length > 0)
{ {
foreach (var item in order.Nodes[i].Actions) foreach (var item in order.Nodes[i].Actions)
{ {
item.ActionDescription += $"\n NodeId: {order.Nodes[i].NodeId}"; item.ActionDescription += $".On NodeId: {order.Nodes[i].NodeId}";
} }
OrderActions.Add(order.Nodes[i].NodeId, order.Nodes[i].Actions); if (OrderActions.TryGetValue(order.Nodes[i].NodeId, out Action[]? actions) && actions is not null)
{
actions = [.. actions, .. order.Nodes[i].Actions];
}
else OrderActions.Add(order.Nodes[i].NodeId, order.Nodes[i].Actions);
} }
if (i < order.Nodes.Length - 1 && order.Edges[i] is not null && order.Edges[i].Length > 0) if (i < order.Nodes.Length - 1 && order.Edges[i].Actions is not null && order.Edges[i].Actions.Length > 0)
{ {
foreach (var item in order.Edges[i].Actions) foreach (var item in order.Edges[i].Actions)
{ {
item.ActionDescription += $"\n NodeId: {order.Nodes[i].NodeId}"; item.ActionDescription += $".On NodeId: {order.Nodes[i].NodeId}";
} }
OrderActions.TryAdd(order.Nodes[i].NodeId, order.Edges[i].Actions); if (OrderActions.TryGetValue(order.Nodes[i].NodeId, out Action[]? actions) && actions is not null)
{
actions = [.. actions, .. order.Edges[i].Actions];
}
else OrderActions.Add(order.Nodes[i].NodeId, order.Edges[i].Actions);
} }
if (order.Nodes[i].SequenceId != i) throw new OrderException(RobotErrors.Error1010(order.Nodes[i].NodeId, order.Nodes[i].SequenceId, i)); if (order.Nodes[i].SequenceId != i) throw new OrderException(RobotErrors.Error1010(order.Nodes[i].NodeId, order.Nodes[i].SequenceId, i));
if (i < order.Nodes.Length - 1 && order.Edges[i].SequenceId != i) throw new OrderException(RobotErrors.Error1011(order.Edges[i].EdgeId, order.Edges[i].SequenceId, i)); if (i < order.Nodes.Length - 1 && order.Edges[i].SequenceId != i) throw new OrderException(RobotErrors.Error1011(order.Edges[i].EdgeId, order.Edges[i].SequenceId, i));
if (order.Nodes[i].Released) CurrentBaseNode = order.Nodes[i]; if (order.Nodes[i].Released) CurrentBaseNode = order.Nodes[i];
} }
ActionManager.ClearInstantActions(); ActionManager.ClearInstantActions();
SafetyManager.OnSafetySpeedChanged += OnSafetySpeedChanged;
if (OrderActions.Count > 0) ActionManager.AddOrderActions([.. OrderActions.Values.SelectMany(a => a)]); if (OrderActions.Count > 0) ActionManager.AddOrderActions([.. OrderActions.Values.SelectMany(a => a)]);
NavigationManager.Move(order.Nodes, order.Edges); NavigationManager.Move(order.Nodes, order.Edges);
NavigationManager.OnNavigationFinished += NavigationFinished; NavigationManager.OnNavigationFinished += NavigationFinished;
@ -159,7 +194,8 @@ public class RobotOrderController(INavigation NavigationManager,
Nodes = order.Nodes; Nodes = order.Nodes;
Edges = order.Edges; Edges = order.Edges;
if (CurrentBaseNode is not null) NavigationManager.UpdateOrder(CurrentBaseNode.NodeId); if (CurrentBaseNode is not null) NavigationManager.UpdateOrder(CurrentBaseNode.NodeId);
if(StateManager.CurrentStateName != AutoStateType.Executing.ToString()) StateManager.TransitionTo(AutoStateType.Executing); if (StateManager.CurrentStateName != AutoStateType.Executing.ToString()) StateManager.TransitionTo(AutoStateType.Executing);
UpdateState();
} }
private bool IsNewPath() private bool IsNewPath()
@ -167,6 +203,18 @@ public class RobotOrderController(INavigation NavigationManager,
return true; return true;
} }
private void ClearLastNode()
{
if (LastNode is null) return;
var currentLastNodeIndex = Array.FindIndex(Nodes, n => n.NodeId == LastNode.NodeId.ToString());
if (currentLastNodeIndex != -1 && currentLastNodeIndex < Nodes.Length - 1)
{
Nodes = [.. Nodes.Skip(currentLastNodeIndex + 1)];
Edges = [.. Edges.Skip(currentLastNodeIndex + 1)];
UpdateState();
}
}
private void HandleUpdateOrder(OrderMsg order) private void HandleUpdateOrder(OrderMsg order)
{ {
if (order.OrderId != OrderId) throw new OrderException(RobotErrors.Error1001(OrderId, order.OrderId)); if (order.OrderId != OrderId) throw new OrderException(RobotErrors.Error1001(OrderId, order.OrderId));
@ -181,15 +229,66 @@ public class RobotOrderController(INavigation NavigationManager,
Nodes = order.Nodes; Nodes = order.Nodes;
Edges = order.Edges; Edges = order.Edges;
OrderUpdateId = order.OrderUpdateId; OrderUpdateId = order.OrderUpdateId;
ClearLastNode();
} }
private void HandleOrder() private void HandleOrder()
{ {
if (IsCancelOrder)
{
IsCancelOrder = false;
NavigationManager.CancelMovement();
NavigationFinished();
return;
}
var currentNode = GetCurrentNode(); var currentNode = GetCurrentNode();
if (currentNode is not null && currentNode.NodeId != LastNode?.NodeId) if (currentNode is not null && currentNode.NodeId != LastNode?.NodeId)
{ {
LastNode = currentNode; LastNode = currentNode;
// xuử lí nếu gặp node mới ClearLastNode();
if (OrderActions.TryGetValue(currentNode.NodeId, out Action[]? actions) && actions is not null && actions.Length > 0)
{
if (actions.Any(a => a.BlockingType == BlockingType.SOFT.ToString() || a.BlockingType == BlockingType.HARD.ToString()))
{
NavigationManager.Pause();
IsWaitingPaused = true;
}
foreach (var action in actions)
{
ActionWaitingRunning.Enqueue(action);
}
}
}
if (ActionHard is not null)
{
var robotAction = ActionManager[ActionHard.ActionId];
if (robotAction is null || (robotAction is not null && robotAction.IsCompleted))
{
NavigationManager.Resume();
ActionHard = null;
}
}
else
{
if (!ActionWaitingRunning.IsEmpty)
{
IsActionRunning = !IsWaitingPaused || (IsWaitingPaused && NavigationManager.State == NavigationState.Paused);
if (IsActionRunning)
{
if (ActionWaitingRunning.TryDequeue(out Action? action) && action is not null)
{
ActionManager.StartOrderAction(action.ActionId);
ActionHard = action.BlockingType == BlockingType.HARD.ToString() ? action : null;
}
}
}
else
{
if (IsWaitingPaused) IsWaitingPaused = false;
}
} }
} }
@ -199,7 +298,6 @@ public class RobotOrderController(INavigation NavigationManager,
{ {
if (NewOrder is not null) if (NewOrder is not null)
{ {
Console.WriteLine("Has new Order");
OrderMsg NewOrderHandler; OrderMsg NewOrderHandler;
lock (LockObject) lock (LockObject)
{ {
@ -211,12 +309,12 @@ public class RobotOrderController(INavigation NavigationManager,
if (NewOrderHandler.Edges.Length < 1) throw new OrderException(RobotErrors.Error1003(NewOrderHandler.Edges.Length)); if (NewOrderHandler.Edges.Length < 1) throw new OrderException(RobotErrors.Error1003(NewOrderHandler.Edges.Length));
if (NewOrderHandler.Edges.Length != NewOrderHandler.Nodes.Length - 1) throw new OrderException(RobotErrors.Error1004(NewOrderHandler.Nodes.Length, NewOrderHandler.Edges.Length)); if (NewOrderHandler.Edges.Length != NewOrderHandler.Nodes.Length - 1) throw new OrderException(RobotErrors.Error1004(NewOrderHandler.Nodes.Length, NewOrderHandler.Edges.Length));
if (string.IsNullOrEmpty(OrderId)) HanleNewOrder(NewOrderHandler); if (string.IsNullOrEmpty(OrderId)) HandleNewOrder(NewOrderHandler);
else HandleUpdateOrder(NewOrderHandler); else HandleUpdateOrder(NewOrderHandler);
} }
HandleOrder(); HandleOrder();
} }
catch (RobotExeption orEx ) catch (RobotExeption orEx)
{ {
if (orEx.Error is not null) if (orEx.Error is not null)
{ {
@ -230,4 +328,5 @@ public class RobotOrderController(INavigation NavigationManager,
Logger.Warning($"Lỗi khi xử lí Order: {ex.Message}"); Logger.Warning($"Lỗi khi xử lí Order: {ex.Message}");
} }
} }
} }

View File

@ -68,6 +68,7 @@ public class DifferentialNavigation : SimulationNavigation
} }
} }
} }
else if (NavState == NavigationState.Paused) VelocityController.Stop();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -16,7 +16,6 @@ public class SimulationNavigation : INavigation, IDisposable
public double VelocityY => Visualization.Vy; public double VelocityY => Visualization.Vy;
public double Omega => Visualization.Omega; public double Omega => Visualization.Omega;
public event Action? OnNavigationStateChanged;
public event Action? OnNavigationFinished; public event Action? OnNavigationFinished;
protected NavigationState NavState = NavigationState.Idle; protected NavigationState NavState = NavigationState.Idle;
@ -182,6 +181,11 @@ public class SimulationNavigation : INavigation, IDisposable
throw new NotImplementedException(); throw new NotImplementedException();
} }
public void SetSpeed(double speed)
{
throw new NotImplementedException();
}
public void Dispose() public void Dispose()
{ {
HandleNavigationStop(); HandleNavigationStop();

View File

@ -1,10 +1,12 @@
using RobotApp.Common.Shares.Enums; using RobotApp.Common.Shares.Enums;
using System.Collections.Concurrent;
namespace RobotApp.Services.State; namespace RobotApp.Services.State;
public record RobotStateMachine(Logger<RobotStateMachine> Logger) : IDisposable public record RobotStateMachine(Logger<RobotStateMachine> Logger) : IDisposable
{ {
private readonly Dictionary<Type, Dictionary<Enum, IRobotState>> StateRegistry = []; private readonly Lock StateLock = new();
private readonly ConcurrentDictionary<Type, Dictionary<Enum, IRobotState>> StateRegistry = [];
public IRobotState CurrentState { get; private set; } = null!; public IRobotState CurrentState { get; private set; } = null!;
public event Action<IRobotState?, IRobotState>? OnStateChanged; public event Action<IRobotState?, IRobotState>? OnStateChanged;
@ -12,8 +14,6 @@ public record RobotStateMachine(Logger<RobotStateMachine> Logger) : IDisposable
public string CurrentStateName => CurrentState.Name.ToString(); public string CurrentStateName => CurrentState.Name.ToString();
public string RootStateName => GetRootStateName(); public string RootStateName => GetRootStateName();
private readonly Lock StateLock = new();
public void InitializeHierarchyStates() public void InitializeHierarchyStates()
{ {
if (IsInitialized) return; if (IsInitialized) return;