Initial commit
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Tắt đèn camera (M918 - Light on OFF).
|
||||
/// </summary>
|
||||
[RobotAction(ActionType.CAMERA_LIGHT_OFF,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Tắt đèn camera (M918 Light on OFF).",
|
||||
"Đèn camera đã tắt (M918 Light on OFF).")]
|
||||
public class CameraLightOffAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetLightOn(false); // M918 Light on OFF
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (PlcController is not null && !PlcController.SetLightOnValue)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Bật đèn camera (M918 - coil 2966).
|
||||
/// action tương tự các action PLC toggle khác: ghi ON, rồi chờ PLC phản hồi đã ON.
|
||||
/// </summary>
|
||||
[RobotAction(ActionType.CAMERA_LIGHT_ON,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Bật đèn camera (M918 Light on).",
|
||||
"Đèn camera đã bật (M918 Light on).")]
|
||||
public class CameraLightOnAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetLightOn(true); // M918 Light on
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (PlcController is not null && PlcController.SetLightOnValue)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Devices;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Bật đèn camera (M918 - coil 2966).
|
||||
/// action tương tự các action PLC toggle khác: ghi ON, rồi chờ PLC phản hồi đã ON.
|
||||
/// </summary>
|
||||
[RobotAction(ActionType.CONTROL_LIGHT,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Bật đèn camera (M918 Light on).",
|
||||
"Đèn camera đã bật (M918 Light on).")]
|
||||
public class ControlLightAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
private bool IsLightOn = false;
|
||||
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var controlTypeParam = Action?.ActionParameters?.FirstOrDefault(p =>
|
||||
string.Equals(p.Key, "CONTROL_TYPE", StringComparison.OrdinalIgnoreCase));
|
||||
if (controlTypeParam is null || string.IsNullOrWhiteSpace(controlTypeParam.Value))
|
||||
{
|
||||
throw new ActionException("ControlLight requires actionParameter CONTROL_TYPE (CONTROL_ON/CONTROL_OFF).");
|
||||
}
|
||||
|
||||
var controlType = controlTypeParam.Value.Trim();
|
||||
if (string.Equals(controlType, "CONTROL_ON", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
IsLightOn = true;
|
||||
}
|
||||
else if (string.Equals(controlType, "CONTROL_OFF", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
IsLightOn = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new ActionException($"CONTROL_TYPE value '{controlType}' is invalid. Expected CONTROL_ON or CONTROL_OFF.");
|
||||
}
|
||||
|
||||
CountTimeout = 0;
|
||||
}
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetLightOn(IsLightOn); // M918 Light on
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (PlcController is not null && PlcController.SetLightOnValue == IsLightOn)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Devices;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Action homing camera (lift): gọi trực tiếp xuống động cơ CiA402 (giống device/hub).
|
||||
/// blockingType NONE: gửi lệnh homing và kết thúc ngay, không chờ hoàn thành.
|
||||
/// </summary>
|
||||
[RobotAction(ActionType.HOMING_CAMERA,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE],
|
||||
"Homing camera (lift).",
|
||||
"Homing camera requested.")]
|
||||
public class HomingCameraAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = ServiceProvider.GetRequiredService<IConfiguration>();
|
||||
var deviceProvider = ServiceProvider.GetRequiredService<IDeviceProvider>();
|
||||
|
||||
var deviceId = config.GetValue<string>("Modules:LiftModule:DeviceId") ?? "lift-motor";
|
||||
var enable = config.GetValue<bool>("Modules:LiftModule:Enable", true);
|
||||
if (!enable)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module is disabled in config.";
|
||||
return;
|
||||
}
|
||||
|
||||
var device = deviceProvider.GetDevice(deviceId);
|
||||
if (device is not ICiA402Servo servo)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Device '{deviceId}' not found or is not ICiA402Servo.";
|
||||
return;
|
||||
}
|
||||
|
||||
var homingMethod = config.GetValue<byte>("Modules:LiftModule:HomingMethod", 21);
|
||||
var homingSpeed = config.GetValue<int>("Modules:LiftModule:HomingSpeed", 30000);
|
||||
var homingOffset = config.GetValue<int>("Modules:LiftModule:HomingOffset", 0);
|
||||
|
||||
// Giống device UI: bước 1 SetParam (Apply Params), bước 2 Start Homing
|
||||
Logger?.LogInformation("HomingCamera: SetParam (method={Method}, speed={Speed}, offset={Offset}) then Start Homing.", homingMethod, homingSpeed, homingOffset);
|
||||
await servo.SetHomingMethodAsync(homingMethod, CancellationToken.None);
|
||||
await servo.SetHomingSpeedAsync(homingSpeed, CancellationToken.None);
|
||||
await servo.SetHomingOffsetAsync(homingOffset, CancellationToken.None);
|
||||
await Task.Delay(100, CancellationToken.None); // delay giữa SetParam và Start như khi bấm trên device
|
||||
await servo.StartHomingAsync(homingMethod, homingSpeed, CancellationToken.None);
|
||||
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = "Homing camera requested (direct to drive).";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Homing camera failed: {ex.Message}";
|
||||
}
|
||||
|
||||
await base.StartAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
// using RobotNet.VDA5050.Type;
|
||||
// using RobotNet10.RobotApp.Devices;
|
||||
// using RobotNet10.RobotApp.Services.Exceptions;
|
||||
// using System.Globalization;
|
||||
|
||||
// namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
// /// <summary>
|
||||
// /// Action đưa camera (lift) tới vị trí theo chiều cao (m).
|
||||
// /// Gọi trực tiếp xuống động cơ CiA402 (giống device/hub), không qua LiftModule state machine.
|
||||
// /// actionParameters: HEIGHT (unit: m), ví dụ "0.825".
|
||||
// /// blockingType NONE: gửi lệnh di chuyển và kết thúc ngay, không chờ hoàn thành.
|
||||
// /// </summary>
|
||||
// [RobotAction(ActionType.LIFT_CAMERA_BY_HEIGHT,
|
||||
// [ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
// [BlockingType.NONE, BlockingType.HARD, BlockingType.SOFT],
|
||||
// "Lift camera with height (unit: m).",
|
||||
// "Lift camera move requested.")]
|
||||
// public class LiftCameraByHeightAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
// {
|
||||
// private double _heightM;
|
||||
// private double _timeoutMs = 20 * 1000;
|
||||
|
||||
// protected override void Initialize()
|
||||
// {
|
||||
// base.Initialize();
|
||||
|
||||
// var heightParam = Action?.ActionParameters?.FirstOrDefault(p =>
|
||||
// string.Equals(p.Key, "HEIGHT", StringComparison.OrdinalIgnoreCase));
|
||||
// if (heightParam is null || string.IsNullOrWhiteSpace(heightParam.Value))
|
||||
// {
|
||||
// throw new ActionException("LiftCameraByHeight requires actionParameter HEIGHT (unit: m).");
|
||||
// }
|
||||
|
||||
// if (!double.TryParse(heightParam.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out _heightM))
|
||||
// {
|
||||
// throw new ActionException($"HEIGHT value '{heightParam.Value}' is not a valid number.");
|
||||
// }
|
||||
// }
|
||||
|
||||
// protected override async Task StartAction()
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// var config = ServiceProvider.GetRequiredService<IConfiguration>();
|
||||
// var deviceProvider = ServiceProvider.GetRequiredService<IDeviceProvider>();
|
||||
|
||||
// var deviceId = config.GetValue<string>("Modules:LiftModule:DeviceId") ?? "lift-motor";
|
||||
// var enable = config.GetValue<bool>("Modules:LiftModule:Enable", true);
|
||||
// if (!enable)
|
||||
// {
|
||||
// SetStatus(ActionEvent.FAILED);
|
||||
// ResultDescription = "Lift module is disabled in config.";
|
||||
// return;
|
||||
// }
|
||||
|
||||
// var device = deviceProvider.GetDevice(deviceId);
|
||||
// if (device is not ICiA402Servo servo)
|
||||
// {
|
||||
// SetStatus(ActionEvent.FAILED);
|
||||
// ResultDescription = $"Device '{deviceId}' not found or is not ICiA402Servo.";
|
||||
// return;
|
||||
// }
|
||||
|
||||
// // Đọc config để map height (m) -> position (encoder), giống LiftModuleService.GetPositionFromHeightM
|
||||
// var minHeightM = config.GetValue<double>("Modules:LiftModule:MinHeightM", 0);
|
||||
// var maxHeightM = config.GetValue<double>("Modules:LiftModule:MaxHeightM", 1);
|
||||
// var minPosition = config.GetValue<int>("Modules:LiftModule:MinPosition", 0);
|
||||
// var maxPosition = config.GetValue<int>("Modules:LiftModule:MaxPosition", 1000000);
|
||||
|
||||
// int position;
|
||||
// if (maxHeightM <= minHeightM)
|
||||
// {
|
||||
// position = minPosition;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// var t = Math.Clamp((_heightM - minHeightM) / (maxHeightM - minHeightM), 0, 1);
|
||||
// position = minPosition + (int)(t * (maxPosition - minPosition));
|
||||
// }
|
||||
|
||||
// var velocity = config.GetValue<uint>("Modules:LiftModule:ProfileVelocity", 100000);
|
||||
// var acceleration = config.GetValue<uint>("Modules:LiftModule:ProfileAcceleration", 50000);
|
||||
// var deceleration = config.GetValue<uint>("Modules:LiftModule:ProfileDeceleration", 50000);
|
||||
|
||||
// Logger?.LogInformation("LiftCameraByHeight: calling servo directly (deviceId={DeviceId}), MoveToPositionAsync(position={Position}, height={HeightM} m).", deviceId, position, _heightM);
|
||||
// await servo.MoveToPositionAsync(position, velocity, acceleration, deceleration, CancellationToken.None);
|
||||
|
||||
// // Timeout = (int)_timeoutMs;
|
||||
// SetStatus(ActionEvent.FINISHED);
|
||||
// ResultDescription = $"Lift camera move to height {_heightM} m requested (direct to drive).";
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// SetStatus(ActionEvent.FAILED);
|
||||
// ResultDescription = $"Lift camera by height failed: {ex.Message}";
|
||||
// }
|
||||
|
||||
// await base.StartAction();
|
||||
// }
|
||||
|
||||
// // protected override async Task ExecuteAction()
|
||||
// // {
|
||||
// // // to do: wait for the lift camera to reach the target height
|
||||
|
||||
// // }
|
||||
|
||||
// // protected override async Task CleanupAction()
|
||||
// // {
|
||||
// // // to do: cleanup the lift camera
|
||||
// // }
|
||||
// }
|
||||
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.CANOpen.CiA402.Enums;
|
||||
using RobotNet10.RobotApp.Devices;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Globalization;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
/// <summary>
|
||||
/// Action đưa camera (lift) tới vị trí theo chiều cao (m).
|
||||
/// Gọi trực tiếp xuống động cơ CiA402 (giống device/hub), không qua LiftModule state machine.
|
||||
/// actionParameters: HEIGHT (unit: m), ví dụ "0.825".
|
||||
/// blockingType NONE: gửi lệnh di chuyển và kết thúc ngay, không chờ hoàn thành.
|
||||
/// </summary>
|
||||
[RobotAction(ActionType.LIFT_CAMERA_BY_HEIGHT,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.HARD],
|
||||
"Lift camera with height (unit: m).",
|
||||
"Lift camera move requested.")]
|
||||
public class LiftCameraByHeightAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private double _heightM;
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
var heightParam = Action?.ActionParameters?.FirstOrDefault(p =>
|
||||
string.Equals(p.Key, "HEIGHT", StringComparison.OrdinalIgnoreCase));
|
||||
if (heightParam is null || string.IsNullOrWhiteSpace(heightParam.Value))
|
||||
{
|
||||
throw new ActionException("LiftCameraByHeight requires actionParameter HEIGHT (unit: m).");
|
||||
}
|
||||
|
||||
if (!double.TryParse(heightParam.Value, NumberStyles.Any, CultureInfo.InvariantCulture, out _heightM))
|
||||
{
|
||||
throw new ActionException($"HEIGHT value '{heightParam.Value}' is not a valid number.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
var config = ServiceProvider.GetRequiredService<IConfiguration>();
|
||||
var deviceProvider = ServiceProvider.GetRequiredService<IDeviceProvider>();
|
||||
|
||||
var deviceId = config.GetValue<string>("Modules:LiftModule:DeviceId") ?? "lift-motor";
|
||||
var enable = config.GetValue<bool>("Modules:LiftModule:Enable", true);
|
||||
if (!enable)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module is disabled in config.";
|
||||
return;
|
||||
}
|
||||
|
||||
var device = deviceProvider.GetDevice(deviceId);
|
||||
if (device is not ICiA402Servo servo)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Device '{deviceId}' not found or is not ICiA402Servo.";
|
||||
return;
|
||||
}
|
||||
|
||||
// Đọc config để map height (m) -> position (encoder), giống LiftModuleService.GetPositionFromHeightM
|
||||
var minHeightM = config.GetValue<double>("Modules:LiftModule:MinHeightM", 0);
|
||||
var maxHeightM = config.GetValue<double>("Modules:LiftModule:MaxHeightM", 1);
|
||||
var minPosition = config.GetValue<int>("Modules:LiftModule:MinPosition", 0);
|
||||
var maxPosition = config.GetValue<int>("Modules:LiftModule:MaxPosition", 1000000);
|
||||
|
||||
int position;
|
||||
if (maxHeightM <= minHeightM)
|
||||
{
|
||||
position = minPosition;
|
||||
}
|
||||
else
|
||||
{
|
||||
var t = Math.Clamp((_heightM - minHeightM) / (maxHeightM - minHeightM), 0, 1);
|
||||
position = minPosition + (int)(t * (maxPosition - minPosition));
|
||||
}
|
||||
|
||||
var velocity = config.GetValue<uint>("Modules:LiftModule:ProfileVelocity", 100000);
|
||||
var acceleration = config.GetValue<uint>("Modules:LiftModule:ProfileAcceleration", 50000);
|
||||
var deceleration = config.GetValue<uint>("Modules:LiftModule:ProfileDeceleration", 50000);
|
||||
var tolerance = config.GetValue<int>("Modules:LiftModule:ActionTargetTolerance", 900);
|
||||
var checkIntervalMs = config.GetValue<int>("Modules:LiftModule:ActionStatusWordCheckIntervalMs", 100);
|
||||
var timeoutMs = config.GetValue<int>("Modules:LiftModule:ActionMoveTimeoutMs", 300000);
|
||||
|
||||
Logger?.LogInformation("LiftCameraByHeight: calling servo directly (deviceId={DeviceId}), MoveToPositionAsync(position={Position}, height={HeightM} m).", deviceId, position, _heightM);
|
||||
await servo.MoveToPositionAsync(position, velocity, acceleration, deceleration, CancellationToken.None);
|
||||
await WaitForMovementCompletedAsync(servo, position, tolerance, checkIntervalMs, timeoutMs, CancellationToken.None);
|
||||
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = $"Lift camera reached height {_heightM} m (direct to drive).";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Lift camera by height failed: {ex.Message}";
|
||||
}
|
||||
|
||||
await base.StartAction();
|
||||
}
|
||||
|
||||
private static async Task WaitForMovementCompletedAsync(
|
||||
ICiA402Servo servo,
|
||||
int targetPosition,
|
||||
int tolerance,
|
||||
int checkIntervalMs,
|
||||
int timeoutMs,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var startedAt = DateTime.UtcNow;
|
||||
var pollInterval = Math.Max(10, checkIntervalMs);
|
||||
var maxWait = TimeSpan.FromMilliseconds(Math.Max(1000, timeoutMs));
|
||||
|
||||
while (DateTime.UtcNow - startedAt < maxWait)
|
||||
{
|
||||
var statusword = await servo.GetStatuswordAsync(ct);
|
||||
if (statusword.GetState() == DriveState.Fault)
|
||||
{
|
||||
throw new ActionException("Lift movement failed: servo entered fault state.");
|
||||
}
|
||||
|
||||
var currentPosition = await servo.GetActualPositionAsync(ct);
|
||||
if (statusword.TargetReached && Math.Abs(currentPosition - targetPosition) <= Math.Max(50, tolerance))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
await Task.Delay(pollInterval, ct);
|
||||
}
|
||||
|
||||
throw new TimeoutException($"Lift movement timeout after {maxWait.TotalSeconds:F0}s.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,360 @@
|
||||
using Appccelerate.StateMachine;
|
||||
using Appccelerate.StateMachine.Machine;
|
||||
using RobotNet.VDA5050;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.Common;
|
||||
using RobotNet10.NavigationTune.Shared.Models;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Diagnostics;
|
||||
using System.Reflection;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
public abstract class RobotAction : IAsyncDisposable
|
||||
{
|
||||
public ActionType Type { get; }
|
||||
public string Id { get; private set; } = "";
|
||||
public string? Description { get; private set; }
|
||||
public BlockingType BlockingType { get; private set; }
|
||||
public RobotNet.VDA5050.InstantAction.ActionParameter[] Parameters { get; protected set; } = [];
|
||||
public ActionStatus Status => CurrentStatus;
|
||||
public string ResultDescription { get; set; } = "";
|
||||
public bool IsCompleted => CurrentStatus == ActionStatus.FINISHED || CurrentStatus == ActionStatus.FAILED;
|
||||
public long CompletionTime { get; private set; } = 0;
|
||||
public ActionScope ActionScope { get; set; }
|
||||
public RobotActionAttribute ActionAttribute { get; }
|
||||
public long SequenceNumber { get; internal set; }
|
||||
|
||||
private WatchThreadAsync<RobotAction>? ActionTimer;
|
||||
protected const int ActionInterval = 100;
|
||||
|
||||
protected IServiceProvider ServiceProvider;
|
||||
protected RobotNet.VDA5050.InstantAction.Action? Action;
|
||||
protected ILogger<RobotAction>? Logger;
|
||||
protected bool IsPaused = false;
|
||||
protected ActionStatus HistoryStatus;
|
||||
private bool _justResumed = false;
|
||||
|
||||
private bool _isDisposed = false;
|
||||
private bool IsCancelAction = false;
|
||||
private PassiveStateMachine<ActionStatus, ActionEvent>? _stateMachine;
|
||||
private ActionStatus CurrentStatus;
|
||||
private long StartTime;
|
||||
private int Timeout;
|
||||
|
||||
private readonly Lock _lock = new();
|
||||
|
||||
public RobotAction(IServiceProvider serviceProvider)
|
||||
{
|
||||
var derivedType = GetType();
|
||||
|
||||
ActionAttribute = derivedType.GetCustomAttribute<RobotActionAttribute>()
|
||||
?? throw new InvalidOperationException(
|
||||
$"Class {derivedType.Name} must have RobotActionAttribute");
|
||||
|
||||
Type = ActionAttribute.ActionType;
|
||||
ServiceProvider = serviceProvider;
|
||||
|
||||
Logger = ServiceProvider.GetRequiredService<ILogger<RobotAction>>();
|
||||
InitializeStatus();
|
||||
}
|
||||
|
||||
public void Initialize(ActionScope actionScope, RobotNet.VDA5050.InstantAction.Action action)
|
||||
{
|
||||
ActionScope = actionScope;
|
||||
Action = action;
|
||||
BlockingType = action.BlockingType;
|
||||
Id = action.ActionId;
|
||||
Description = action.ActionDescription;
|
||||
Initialize();
|
||||
}
|
||||
|
||||
public void Start()
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (Status != ActionStatus.WAITING) return;
|
||||
ActionTimer = new(ActionInterval, ActionHandler, Logger);
|
||||
SetStatus(ActionEvent.INITIALIZING);
|
||||
ActionTimer.Start();
|
||||
StartTime = GetCurrentTimeMs();
|
||||
}
|
||||
}
|
||||
|
||||
public void Pause()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
lock (_lock)
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
HistoryStatus = Status;
|
||||
SetStatus(ActionEvent.PAUSED);
|
||||
IsPaused = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void Resume()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
lock (_lock)
|
||||
{
|
||||
if (Status == ActionStatus.PAUSED)
|
||||
{
|
||||
IsPaused = false;
|
||||
_justResumed = true;
|
||||
// Restore về trạng thái trước khi pause
|
||||
ActionEvent resumeEvent = HistoryStatus switch
|
||||
{
|
||||
ActionStatus.RUNNING => ActionEvent.RUNNING,
|
||||
ActionStatus.INITIALIZING => ActionEvent.INITIALIZING,
|
||||
_ => ActionEvent.WAITING
|
||||
};
|
||||
SetStatus(resumeEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
lock (_lock)
|
||||
{
|
||||
if (!IsCompleted) IsCancelAction = true;
|
||||
if (Status == ActionStatus.WAITING) _ = StopAction();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// VDA5050: Gracefully finish an action (e.g., when robot leaves an edge).
|
||||
/// Sets status to FINISHED instead of FAILED (Cancel).
|
||||
/// </summary>
|
||||
public void Finish()
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
lock (_lock)
|
||||
{
|
||||
if (IsCompleted) return;
|
||||
// State machine allows FINISHED from RUNNING and INITIALIZING
|
||||
if (CurrentStatus == ActionStatus.RUNNING || CurrentStatus == ActionStatus.INITIALIZING)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
if (string.IsNullOrEmpty(ResultDescription))
|
||||
ResultDescription = "Action completed (edge transition).";
|
||||
}
|
||||
else
|
||||
{
|
||||
// WAITING or PAUSED: cancel as fallback
|
||||
IsCancelAction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual Task StartAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual Task StopAction()
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Action bị hủy bỏ.";
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual Task CompleteAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Giải phóng tài nguyên được sử dụng trong action.
|
||||
/// Luôn được gọi trong DisposeAsync, đảm bảo cleanup cả khi FINISHED lẫn FAILED/Cancel.
|
||||
/// </summary>
|
||||
protected virtual Task CleanupAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual Task ExecuteAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual Task PauseAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual Task ResumeAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
if (Action is null) throw new ActionException("Khởi tạo Action không tồn tại");
|
||||
if (EnumHelper.TryParse(Action.ActionType, out ActionType type))
|
||||
{
|
||||
if (type != Type) throw new ActionException($"ActionType {Action.ActionType} không khớp với action hiện tại {Type}.");
|
||||
}
|
||||
else throw new ActionException($"ActionType {Action.ActionType} không hợp lệ.");
|
||||
|
||||
if (!ActionAttribute.BlockingTypes.Any(bt => bt == BlockingType)) throw new ActionException($"BlockingType {BlockingType} không được hỗ trợ cho action {Type}.");
|
||||
if (!ActionAttribute.ActionScopes.Any(sp => sp == ActionScope)) throw new ActionException($"ActionScope {ActionScope} không được hỗ trợ cho action {Type}.");
|
||||
|
||||
if (Action.ActionParameters != null && Action.ActionParameters.Length > 0)
|
||||
{
|
||||
var para = Action.ActionParameters.FirstOrDefault(p => p.Key == "timeout");
|
||||
if (para is not null && int.TryParse(para.Value, out int miliseconds) && miliseconds > 100) Timeout = miliseconds;
|
||||
else Timeout = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ActionHandler()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (Timeout > 0)
|
||||
{
|
||||
long now = GetCurrentTimeMs();
|
||||
if (now >= (StartTime + Timeout)) throw new TimeoutException($"Action [{Type} - {Id}] timeout. Timeout: {Timeout} ms ");
|
||||
}
|
||||
|
||||
ActionStatus status;
|
||||
bool isCancel = false;
|
||||
bool justResumed = false;
|
||||
lock (_lock)
|
||||
{
|
||||
status = CurrentStatus;
|
||||
isCancel = IsCancelAction;
|
||||
justResumed = _justResumed;
|
||||
if (_justResumed) _justResumed = false;
|
||||
}
|
||||
|
||||
if (isCancel)
|
||||
{
|
||||
await StopAction();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (status == ActionStatus.INITIALIZING)
|
||||
{
|
||||
Logger?.LogInformation($"Executing action {Type}");
|
||||
SetStatus(ActionEvent.RUNNING);
|
||||
await StartAction();
|
||||
}
|
||||
else if (status == ActionStatus.RUNNING)
|
||||
{
|
||||
if (justResumed)
|
||||
{
|
||||
await ResumeAction();
|
||||
}
|
||||
await ExecuteAction();
|
||||
}
|
||||
else if (status == ActionStatus.PAUSED)
|
||||
{
|
||||
await PauseAction();
|
||||
}
|
||||
}
|
||||
|
||||
if (IsCompleted)
|
||||
{
|
||||
await CompleteAction();
|
||||
await DisposeAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger?.LogError($"Action [{Type} - {Id}] execution error: {ex.Message}");
|
||||
lock (_lock)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
}
|
||||
ResultDescription = $"Thực hiện action [{Type} - {Id}] xảy ra lỗi: {ex.Message}";
|
||||
await DisposeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
private void InitializeStatus()
|
||||
{
|
||||
var builder = new StateMachineDefinitionBuilder<ActionStatus, ActionEvent>();
|
||||
|
||||
builder.In(ActionStatus.WAITING)
|
||||
.ExecuteOnEntry(() => { CurrentStatus = ActionStatus.WAITING; })
|
||||
.On(ActionEvent.INITIALIZING).Goto(ActionStatus.INITIALIZING)
|
||||
.On(ActionEvent.PAUSED).Goto(ActionStatus.PAUSED)
|
||||
.On(ActionEvent.FAILED).Goto(ActionStatus.FAILED);
|
||||
|
||||
|
||||
builder.In(ActionStatus.INITIALIZING)
|
||||
.ExecuteOnEntry(() => { CurrentStatus = ActionStatus.INITIALIZING; })
|
||||
.On(ActionEvent.RUNNING).Goto(ActionStatus.RUNNING)
|
||||
.On(ActionEvent.PAUSED).Goto(ActionStatus.PAUSED)
|
||||
.On(ActionEvent.FINISHED).Goto(ActionStatus.FINISHED)
|
||||
.On(ActionEvent.FAILED).Goto(ActionStatus.FAILED);
|
||||
|
||||
builder.In(ActionStatus.RUNNING)
|
||||
.ExecuteOnEntry(() => { CurrentStatus = ActionStatus.RUNNING; })
|
||||
.On(ActionEvent.PAUSED).Goto(ActionStatus.PAUSED)
|
||||
.On(ActionEvent.FINISHED).Goto(ActionStatus.FINISHED)
|
||||
.On(ActionEvent.FAILED).Goto(ActionStatus.FAILED);
|
||||
|
||||
builder.In(ActionStatus.PAUSED)
|
||||
.ExecuteOnEntry(() => { CurrentStatus = ActionStatus.PAUSED; })
|
||||
.On(ActionEvent.WAITING).Goto(ActionStatus.WAITING)
|
||||
.On(ActionEvent.INITIALIZING).Goto(ActionStatus.INITIALIZING)
|
||||
.On(ActionEvent.RUNNING).Goto(ActionStatus.RUNNING)
|
||||
.On(ActionEvent.FAILED).Goto(ActionStatus.FAILED);
|
||||
|
||||
builder.In(ActionStatus.FINISHED)
|
||||
.ExecuteOnEntry(() =>
|
||||
{
|
||||
CurrentStatus = ActionStatus.FINISHED;
|
||||
CompletionTime = GetCurrentTimeMs();
|
||||
});
|
||||
|
||||
builder.In(ActionStatus.FAILED)
|
||||
.ExecuteOnEntry(() =>
|
||||
{
|
||||
CurrentStatus = ActionStatus.FAILED;
|
||||
CompletionTime = GetCurrentTimeMs();
|
||||
});
|
||||
|
||||
_stateMachine = builder
|
||||
.WithInitialState(ActionStatus.WAITING)
|
||||
.Build()
|
||||
.CreatePassiveStateMachine();
|
||||
|
||||
_stateMachine.Start();
|
||||
}
|
||||
|
||||
private static long GetCurrentTimeMs()
|
||||
{
|
||||
return Stopwatch.GetTimestamp() * 1000 / Stopwatch.Frequency;
|
||||
}
|
||||
|
||||
protected void SetStatus(ActionEvent eventStatus)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_stateMachine?.Fire(eventStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync()
|
||||
{
|
||||
bool shouldStop;
|
||||
lock (_lock)
|
||||
{
|
||||
if (_isDisposed) return;
|
||||
_isDisposed = true;
|
||||
shouldStop = !IsCompleted;
|
||||
}
|
||||
|
||||
if (shouldStop) await StopAction();
|
||||
await CleanupAction();
|
||||
ActionTimer?.Dispose();
|
||||
ActionTimer = null;
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
|
||||
public class RobotActionAttribute : Attribute
|
||||
{
|
||||
public ActionType ActionType { get; }
|
||||
public IReadOnlyList<ActionScope> ActionScopes { get; }
|
||||
public IReadOnlyList<BlockingType> BlockingTypes { get; }
|
||||
public string? ActionDescription { get; }
|
||||
public string? ResultDescription { get; }
|
||||
public RobotActionAttribute(
|
||||
ActionType actionType,
|
||||
ActionScope[] scopes,
|
||||
BlockingType[] blockingTypes,
|
||||
string? description = null,
|
||||
string? resultDescription = null)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(scopes);
|
||||
ArgumentNullException.ThrowIfNull(blockingTypes);
|
||||
|
||||
if (!Enum.IsDefined(actionType))
|
||||
{
|
||||
throw new ArgumentException("Invalid action type.", nameof(actionType));
|
||||
}
|
||||
|
||||
if (scopes.Length == 0)
|
||||
throw new ArgumentException("Scopes cannot be empty.", nameof(scopes));
|
||||
if (blockingTypes.Length == 0)
|
||||
throw new ArgumentException("Blocking types cannot be empty.", nameof(blockingTypes));
|
||||
|
||||
ActionType = actionType;
|
||||
ActionScopes = Array.AsReadOnly(scopes);
|
||||
BlockingTypes = Array.AsReadOnly(blockingTypes);
|
||||
ActionDescription = description;
|
||||
ResultDescription = resultDescription;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.EXAMPLE,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"This is an example robot action.",
|
||||
"Example robot action completed.")]
|
||||
public class RobotActionExample(IServiceProvider serviceProvider) : RobotAction(serviceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new() {
|
||||
Key = "exampleParam",
|
||||
ValueDataType = ValueDataType.STRING,
|
||||
Description = "This is an example string parameter.",
|
||||
IsOptional = true
|
||||
},
|
||||
}.AsReadOnly();
|
||||
|
||||
string paramExample = "";
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var para = Parameters.FirstOrDefault(p => p.Key == "exampleParam") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'exampleParam'");
|
||||
paramExample = para.Value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task PauseAction()
|
||||
{
|
||||
return base.PauseAction();
|
||||
}
|
||||
|
||||
protected override Task ResumeAction()
|
||||
{
|
||||
return base.ResumeAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
|
||||
public class RobotActionProvider(Logger<RobotActionProvider> Logger, IServiceProvider ServiceProvider) : BackgroundService, IRobotActionProvider
|
||||
{
|
||||
public bool IsInitialized { get; private set; }
|
||||
private Dictionary<ActionType, Type> Actions = [];
|
||||
|
||||
public RobotAction GetRobotAction(ActionType type)
|
||||
{
|
||||
if (!Actions.TryGetValue(type, out var actionType))
|
||||
{
|
||||
Logger.Error($"RobotAction not found for ActionType: {type}");
|
||||
throw new InvalidOperationException($"RobotAction not found for ActionType: {type}");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var instance = ActivatorUtilities.CreateInstance(ServiceProvider, actionType);
|
||||
|
||||
if (instance is not RobotAction robotAction)
|
||||
{
|
||||
Logger.Error($"Type {actionType.Name} is not a RobotAction");
|
||||
throw new InvalidOperationException($"Type {actionType.Name} is not a RobotAction");
|
||||
}
|
||||
|
||||
return robotAction;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error($"Error creating instance of {actionType.Name}: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private static Dictionary<ActionType, Type> DiscoverActions()
|
||||
{
|
||||
var actionTypes = new Dictionary<ActionType, Type>();
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies().Where(a => a.FullName?.StartsWith("RobotNet10.RobotApp") == true);
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
var types = assembly.GetTypes().Where(t => t.IsClass && !t.IsAbstract && t.IsSubclassOf(typeof(RobotAction)));
|
||||
foreach (var type in types)
|
||||
{
|
||||
var attributes = type.GetCustomAttributes(typeof(RobotActionAttribute), false);
|
||||
if (attributes.Length > 0)
|
||||
{
|
||||
foreach(var attribute in attributes)
|
||||
{
|
||||
if(attribute is RobotActionAttribute robotAttribute)
|
||||
{
|
||||
actionTypes[robotAttribute.ActionType] = type;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return actionTypes;
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
||||
{
|
||||
await Task.Yield();
|
||||
|
||||
try
|
||||
{
|
||||
Logger.Info("Initializing RobotActionProvider...");
|
||||
Actions = DiscoverActions();
|
||||
IsInitialized = true;
|
||||
Logger.Info($"Discovered {Actions.Count} robot actions.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Warning($"Failed to discover robot actions. {ex}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public RobotAction[] GetRobotActions()
|
||||
{
|
||||
try
|
||||
{
|
||||
List<RobotAction> robotActions = [];
|
||||
foreach (var actionType in Actions.Values)
|
||||
{
|
||||
var instance = ActivatorUtilities.CreateInstance(ServiceProvider, actionType);
|
||||
if (instance is not RobotAction robotAction)
|
||||
{
|
||||
Logger.Error($"Type {actionType.Name} is not a RobotAction");
|
||||
throw new InvalidOperationException($"Type {actionType.Name} is not a RobotAction");
|
||||
}
|
||||
robotActions.Add(robotAction);
|
||||
}
|
||||
return [.. robotActions];
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new Exception ($"Error get RobotActions: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
|
||||
[RobotAction(ActionType.CANCEL_ORDER,
|
||||
[ActionScope.INSTANT],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Hủy bỏ Order hiện tại của robot.",
|
||||
"Robot đã hủy bỏ Order hiện tại.")]
|
||||
public class RobotCancelOrderAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
|
||||
private IOrder? RobotOrder;
|
||||
private IAction? RobotAction;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
RobotOrder = ServiceProvider.GetRequiredService<IOrder>();
|
||||
RobotAction = ServiceProvider.GetRequiredService<IAction>();
|
||||
RobotOrder.StopOrder();
|
||||
RobotAction.StopOrderAction();
|
||||
CountTimeout = 0;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (RobotOrder is null || RobotAction is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Không thể tìm thấy module quản lý {(RobotOrder is null ? "Order" : RobotAction is null ? "Action" : "")}";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (RobotOrder.NodeStates.Length == 0 && RobotOrder.EdgeStates.Length == 0 && !RobotAction.HasActionRunning)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if(CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Data;
|
||||
using RobotNet10.RobotApp.Detection;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using RobotNet10.RobotApp.Shared.Enums;
|
||||
using RobotNet10.Shared.Detection;
|
||||
using RobotNet10.Shared.Enum;
|
||||
using RobotNet10.Shared.Numbers;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.DOCK_TO,
|
||||
[ActionScope.INSTANT, ActionScope.NODE],
|
||||
[BlockingType.HARD],
|
||||
"Robot di chuyển vào vị trí đặc biệt",
|
||||
"Robot đã dock đến vị trí sạc hoặc bến đỗ.")]
|
||||
public class RobotDockToAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "stationId",
|
||||
Description = "ID của vị trí dock.",
|
||||
ValueDataType = ValueDataType.STRING,
|
||||
IsOptional = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "direction",
|
||||
Description = "Hướng di chuyển: FORWARD, BACKWARD",
|
||||
ValueDataType = ValueDataType.STRING,
|
||||
IsOptional = true,
|
||||
},
|
||||
}.AsReadOnly();
|
||||
|
||||
private IMarkerDetector? MarkerDetector;
|
||||
private INavigation? Navigation;
|
||||
private IDetectSession? DetectSession;
|
||||
private string? StationId;
|
||||
private RobotDirection? Direction = null;
|
||||
|
||||
private const int MAX_TIMEOUT = 60000 * 5;
|
||||
private int CountTimeout = 0;
|
||||
private bool IsFindedGoal = false;
|
||||
private bool IsStartDockTo = false;
|
||||
private bool IsHasLoad = false;
|
||||
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
MarkerDetector = ServiceProvider.GetRequiredService<IMarkerDetector>();
|
||||
Navigation = ServiceProvider.GetRequiredService<INavigation>();
|
||||
|
||||
if (!string.IsNullOrEmpty(StationId))
|
||||
{
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var dbContext = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
|
||||
var config = await dbContext.DockStationConfigs
|
||||
.Include(d => d.MarkerEntries)
|
||||
.FirstOrDefaultAsync(d => d.StationId == StationId && d.IsActive);
|
||||
|
||||
if (config is not null)
|
||||
{
|
||||
var MarkersSearchRequest = new MarkersSearchRequest
|
||||
{
|
||||
X = config.X,
|
||||
Y = config.Y,
|
||||
Yaw = config.Yaw,
|
||||
Width = config.Width,
|
||||
Length = config.Length,
|
||||
MarkerSearchRequests = [.. config.MarkerEntries
|
||||
.OrderBy(m => m.Priority)
|
||||
.Select(m => new MarkerEntry
|
||||
{
|
||||
MarkerId = m.MarkerId ?? string.Empty,
|
||||
Type = (MarkerType)m.Type,
|
||||
Priority = m.Priority,
|
||||
DeviceId = m.DeviceId ?? string.Empty,
|
||||
Code = m.Code ?? string.Empty,
|
||||
ReferencePoints = DeserializeReferencePoints(m.ReferencePointsJson)
|
||||
})]
|
||||
};
|
||||
DetectSession = await MarkerDetector.CreateSessionAsync(MarkersSearchRequest);
|
||||
var plcController = scope.ServiceProvider.GetRequiredService<IPlcController>();
|
||||
IsHasLoad = plcController.SetHasLoadValue;
|
||||
//var StateMachine = scope.ServiceProvider.GetRequiredService<RobotStateMachine>();
|
||||
//StateMachine.Fire(RobotEventType.StartDocking);
|
||||
await base.StartAction();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Cannot get Marker Detection";
|
||||
}
|
||||
}
|
||||
|
||||
private static Vector2[] DeserializeReferencePoints(string? json)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(json)) return [];
|
||||
try { return JsonSerializer.Deserialize<Vector2[]>(json) ?? []; }
|
||||
catch { return []; }
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
Navigation?.CancelMovement();
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task CleanupAction()
|
||||
{
|
||||
DetectSession?.Dispose();
|
||||
DetectSession = null;
|
||||
MarkerDetector?.Dispose();
|
||||
MarkerDetector = null;
|
||||
//using var scope = ServiceProvider.CreateAsyncScope();
|
||||
//var StateMachine = scope.ServiceProvider.GetRequiredService<RobotStateMachine>();
|
||||
//StateMachine.Fire(RobotEventType.CompleteDocking);
|
||||
return base.CleanupAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (DetectSession is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Module Detect Marker is not existed";
|
||||
}
|
||||
else if (Navigation is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Module Navigation is not existed";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsFindedGoal) IsFindedGoal = DetectSession.Goal is not null;
|
||||
|
||||
if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
Navigation?.CancelMovement();
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
else if (!IsFindedGoal) base.ExecuteAction();
|
||||
else
|
||||
{
|
||||
if (!IsStartDockTo)
|
||||
{
|
||||
Navigation.DockTo(DetectSession, IsHasLoad, Direction);
|
||||
IsStartDockTo = true;
|
||||
}
|
||||
if (Navigation.State == NavigationState.Completed)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (Navigation.State == NavigationState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Action Handle [{Type} - {Id}] is failed: Navigation Failed";
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var stationPara = Parameters.FirstOrDefault(p => p.Key == "stationId") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'stationId'");
|
||||
StationId = stationPara.Value;
|
||||
var directionPara = Parameters.FirstOrDefault(p => p.Key == "direction");
|
||||
Direction = directionPara is not null && Enum.TryParse<RobotDirection>(directionPara.Value, out var direction) ? direction : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Client.Pages;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Modules;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.DROP,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.HARD],
|
||||
"Hạ thấp pallet.",
|
||||
"Robot đã hạ thấp pallet.")]
|
||||
public class RobotDropAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private const int MAX_TIMEOUT = 60000;
|
||||
private int CountTimeout = 0;
|
||||
|
||||
private ILoad? LoadManager;
|
||||
private ILiftModule? LiftModule;
|
||||
private IPlcController? PlcController;
|
||||
private CancellationTokenSource CancellationToken = new();
|
||||
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadManager = ServiceProvider.GetRequiredService<ILoad>();
|
||||
LiftModule = ServiceProvider.GetRequiredService<ILiftModule>();
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
|
||||
if (!LiftModule.IsReady)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module not ready";
|
||||
return;
|
||||
}
|
||||
|
||||
CancellationToken = new CancellationTokenSource();
|
||||
PlcController.SetOperationState(OperationState.Lifting);
|
||||
await LiftModule.LiftDownAsync(CancellationToken.Token);
|
||||
CountTimeout = 0;
|
||||
await base.StartAction();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Drop Failed: {ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
CancellationToken.Cancel();
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task CleanupAction()
|
||||
{
|
||||
CancellationToken.Dispose();
|
||||
PlcController?.SetOperationState(OperationState.None);
|
||||
return base.CleanupAction();
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAction()
|
||||
{
|
||||
if (LiftModule is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module not found";
|
||||
}
|
||||
else if (LiftModule.State == LiftModuleState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module error";
|
||||
}
|
||||
else if (LiftModule.Position == LiftPosition.Bottom)
|
||||
{
|
||||
LoadManager?.ClearLoad();
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? "Robot has dropped the load." : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
CancellationToken.Cancel();
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
await base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.FACTSHEET_REQUEST,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Yêu cầu gửi Factsheet robot ngay lập tức.",
|
||||
"Robot đã gửi Factsheet ngay lập tức.")]
|
||||
public class RobotFactsheetRequestAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
var RobotFactsheet = ServiceProvider.GetRequiredService<IFactsheet>();
|
||||
await RobotFactsheet.PubFactsheet();
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.INIT_POSITION,
|
||||
[ActionScope.INSTANT, ActionScope.NODE],
|
||||
[BlockingType.HARD],
|
||||
"Khởi tạo lại vị trí robot.",
|
||||
"Robot đã khởi tạo lại vị trí.")]
|
||||
public class RobotInitPositionAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "x",
|
||||
Description = "Tọa độ X của vị trí khởi tạo.",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "y",
|
||||
Description = "Tọa độ Y của vị trí khởi tạo.",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "theta",
|
||||
Description = "Góc quay (theta) của vị trí khởi tạo. (rad)",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
}
|
||||
}.AsReadOnly();
|
||||
|
||||
double X = 0;
|
||||
double Y = 0;
|
||||
double Theta = 0;
|
||||
ILocalization? Localization;
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
Localization = ServiceProvider.GetRequiredService<ILocalization>();
|
||||
var initPose = Localization.SetInitializePosition(X, Y, Theta);
|
||||
if (!initPose.IsSuccess)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = initPose.Message;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var xPara = Parameters.FirstOrDefault(p => p.Key == "x") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'x'");
|
||||
var yPara = Parameters.FirstOrDefault(p => p.Key == "y") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'y'");
|
||||
var thetaPara = Parameters.FirstOrDefault(p => p.Key == "theta") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'theta'");
|
||||
|
||||
var xParse = double.TryParse(xPara.Value, out double xData);
|
||||
var yParse = double.TryParse(yPara.Value, out double yData);
|
||||
var thetaParse = double.TryParse(thetaPara.Value, out double thetaData);
|
||||
|
||||
if (!xParse) throw new ActionException($"Action {Type} có parameter 'x' không đúng kiểu dữ liệu");
|
||||
if (!yParse) throw new ActionException($"Action {Type} có parameter 'y' không đúng kiểu dữ liệu");
|
||||
if (!thetaParse) throw new ActionException($"Action {Type} có parameter 'theta' không đúng kiểu dữ liệu");
|
||||
X = xData;
|
||||
Y = yData;
|
||||
Theta = thetaData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Modules;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.LIFT_ROTATE,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.HARD],
|
||||
"Xoay bàn nâng của robot.",
|
||||
"Robot đã xoay bàn nâng.")]
|
||||
public class RobotLiftRotateAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly IReadOnlyList<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "angle",
|
||||
Description = "Góc xoay của bàn nâng. (rad)",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
}
|
||||
}.AsReadOnly();
|
||||
private double Angle = 0; // Degree
|
||||
private IRotationModule? RotationModule;
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 60000 * 2;
|
||||
private int CountTimeout = 0;
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
RotationModule = ServiceProvider.GetRequiredService<IRotationModule>();
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetOperationState(OperationState.LiftRotating);
|
||||
RotationModule.RotateToAngleAsync(Angle);
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task CleanupAction()
|
||||
{
|
||||
PlcController?.SetOperationState(OperationState.None);
|
||||
return base.CleanupAction();
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAction()
|
||||
{
|
||||
if (RotationModule is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Không tìm thấy mô-đun xoay.";
|
||||
}
|
||||
else if (RotationModule.State == RotationModuleState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Mô-đun xoay gặp lỗi.";
|
||||
|
||||
}
|
||||
else if (RotationModule.State == RotationModuleState.Ready && Math.Abs(await RotationModule.GetCurrentAngleAsync() - Angle) < 1)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ >= MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
await base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var anglePara = Parameters.FirstOrDefault(p => p.Key == "angle") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'angle'");
|
||||
var angleParse = double.TryParse(anglePara.Value, out double angleData);
|
||||
if (!angleParse) throw new ActionException($"Action {Type} có parameter 'angle' không đúng kiểu dữ liệu");
|
||||
Angle = angleData * 180 / Math.PI;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using RobotNet10.RobotApp.Shared.Enums;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.MOVE_STRAIGHT_TO_COOR,
|
||||
[ActionScope.INSTANT],
|
||||
[BlockingType.HARD],
|
||||
"Di chuyển thẳng đến tọa độ xác định.",
|
||||
"Robot đã di chuyển thẳng đến tọa độ xác định.")]
|
||||
public class RobotMoveStraightToCoorAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "x",
|
||||
Description = "Tọa độ X đích đến.",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "y",
|
||||
Description = "Tọa độ Y đích đến.",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "direction",
|
||||
Description = "Hướng di chuyển: FORWARD, BACKWARD",
|
||||
ValueDataType = ValueDataType.STRING,
|
||||
IsOptional = true,
|
||||
},
|
||||
}.AsReadOnly();
|
||||
|
||||
private INavigation? Navigation;
|
||||
private double TargetX;
|
||||
private double TargetY;
|
||||
private RobotDirection? Direction = null;
|
||||
|
||||
private const int MAX_TIMEOUT = 60000 * 5;
|
||||
private int CountTimeout = 0;
|
||||
private bool IsStartMoveStraight = false;
|
||||
private bool IsHasLoad = false;
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
Navigation = ServiceProvider.GetRequiredService<INavigation>();
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var plcController = scope.ServiceProvider.GetRequiredService<IPlcController>();
|
||||
IsHasLoad = plcController.SetHasLoadValue;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
Navigation?.CancelMovement();
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (Navigation is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Module Navigation is not existed";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
Navigation?.CancelMovement();
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsStartMoveStraight)
|
||||
{
|
||||
Navigation.MoveStraight(TargetX, TargetY, IsHasLoad, Direction);
|
||||
IsStartMoveStraight = true;
|
||||
}
|
||||
if (Navigation.State == NavigationState.Completed)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (Navigation.State == NavigationState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Action Handle [{Type} - {Id}] is failed: Navigation Failed";
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key))
|
||||
throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var xPara = Parameters.FirstOrDefault(p => p.Key == "x") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'x'");
|
||||
var yPara = Parameters.FirstOrDefault(p => p.Key == "y") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'y'");
|
||||
TargetX = double.Parse(xPara.Value);
|
||||
TargetY = double.Parse(yPara.Value);
|
||||
var directionPara = Parameters.FirstOrDefault(p => p.Key == "direction");
|
||||
Direction = directionPara is not null && Enum.TryParse<RobotDirection>(directionPara.Value, out var direction) ? direction : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using RobotNet10.RobotApp.Shared.Enums;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.MOVE_STRAIGHT_WITH_DISTANCE,
|
||||
[ActionScope.INSTANT],
|
||||
[BlockingType.HARD],
|
||||
"Di chuyển thẳng với khoảng cách xác định.",
|
||||
"Robot đã di chuyển thẳng với khoảng cách xác định.")]
|
||||
public class RobotMoveStraightWithDistanceAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "distance",
|
||||
Description = "Khoảng cách di chuyển. (m)",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "direction",
|
||||
Description = "Hướng di chuyển: FORWARD, BACKWARD",
|
||||
ValueDataType = ValueDataType.STRING,
|
||||
IsOptional = true,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Key = "angle",
|
||||
Description = "Góc di chuyển so với hướng hiện tại của robot. (rad)",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
}
|
||||
}.AsReadOnly();
|
||||
|
||||
private INavigation? Navigation;
|
||||
private ILocalization? Localization;
|
||||
private double Distance;
|
||||
private double Angle;
|
||||
private RobotDirection? Direction = null;
|
||||
|
||||
private const int MAX_TIMEOUT = 60000 * 5;
|
||||
private int CountTimeout = 0;
|
||||
private bool IsStartMoveStraight = false;
|
||||
private bool IsHasLoad = false;
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
Navigation = ServiceProvider.GetRequiredService<INavigation>();
|
||||
Localization = ServiceProvider.GetRequiredService<ILocalization>();
|
||||
using var scope = ServiceProvider.CreateScope();
|
||||
var plcController = scope.ServiceProvider.GetRequiredService<IPlcController>();
|
||||
IsHasLoad = plcController.SetHasLoadValue;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
Navigation?.CancelMovement();
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (Navigation is null || Localization is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Module Navigation or Localization is not existed";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
Navigation?.CancelMovement();
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!IsStartMoveStraight)
|
||||
{
|
||||
double targetX = Localization.X + Distance * Math.Cos(Angle);
|
||||
double targetY = Localization.Y + Distance * Math.Sin(Angle);
|
||||
Navigation.MoveStraight(targetX, targetY, IsHasLoad, Direction);
|
||||
IsStartMoveStraight = true;
|
||||
}
|
||||
if (Navigation.State == NavigationState.Completed)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (Navigation.State == NavigationState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Action Handle [{Type} - {Id}] is failed: Navigation Failed";
|
||||
}
|
||||
}
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key))
|
||||
throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var distancePara = Parameters.FirstOrDefault(p => p.Key == "distance") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'distance'");
|
||||
Distance = double.Parse(distancePara.Value);
|
||||
var anglePara = Parameters.FirstOrDefault(p => p.Key == "angle") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'angle'");
|
||||
Angle = double.Parse(anglePara.Value);
|
||||
var directionPara = Parameters.FirstOrDefault(p => p.Key == "direction");
|
||||
Direction = directionPara is not null && Enum.TryParse<RobotDirection>(directionPara.Value, out var direction) ? direction : null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
|
||||
[RobotAction(ActionType.MUTED_BASE_OFF,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Tắt chế độ muted base robot.",
|
||||
"Robot đã tắt chế độ muted base.")]
|
||||
public class RobotMutedBaseOffAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetMutedBase(false);
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (PlcController is not null && !PlcController.MutedBase)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.MUTED_BASE_ON,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Bật chế độ muted base robot.",
|
||||
"Robot đã bật chế độ muted base.")]
|
||||
public class RobotMutedBaseOnAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetMutedBase(true);
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if(PlcController is not null && PlcController.MutedBase)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.MUTED_LOAD_OFF,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Tắt chế độ muted load robot.",
|
||||
"Robot đã tắt chế độ muted load.")]
|
||||
public class RobotMutedLoadOffAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetMutedLoad(false);
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (PlcController is not null && !PlcController.MutedLoad)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.MUTED_LOAD_ON,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Bật chế độ muted load robot.",
|
||||
"Robot đã bật chế độ muted load.")]
|
||||
public class RobotMutedLoadOnAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private IPlcController? PlcController;
|
||||
|
||||
private const int MAX_TIMEOUT = 4000;
|
||||
private int CountTimeout = 0;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
PlcController.SetMutedLoad(true);
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (PlcController is not null && PlcController.MutedLoad)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
using NLog;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Client.Pages;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Modules;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.PICK,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.HARD],
|
||||
"Nâng cao pallet.",
|
||||
"Robot đã nâng cao pallet.")]
|
||||
public class RobotPickAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private const int MAX_TIMEOUT = 60000;
|
||||
private int CountTimeout = 0;
|
||||
private ILoad? LoadManager;
|
||||
private ILiftModule? LiftModule;
|
||||
private IPlcController? PlcController;
|
||||
private CancellationTokenSource CancellationToken = new();
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
try
|
||||
{
|
||||
LoadManager = ServiceProvider.GetRequiredService<ILoad>();
|
||||
LiftModule = ServiceProvider.GetRequiredService<ILiftModule>();
|
||||
PlcController = ServiceProvider.GetRequiredService<IPlcController>();
|
||||
|
||||
if (!LiftModule.IsReady)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module not ready";
|
||||
return;
|
||||
}
|
||||
|
||||
CancellationToken = new CancellationTokenSource();
|
||||
PlcController.SetOperationState(OperationState.Lifting);
|
||||
_ = LiftModule.LiftUpAsync(CancellationToken.Token);
|
||||
CountTimeout = 0;
|
||||
await base.StartAction();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Pick failed: " + ex.Message;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
CancellationToken.Cancel();
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task CleanupAction()
|
||||
{
|
||||
CancellationToken.Dispose();
|
||||
PlcController?.SetOperationState(OperationState.None);
|
||||
return base.CleanupAction();
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAction()
|
||||
{
|
||||
if (LiftModule is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module not found";
|
||||
}
|
||||
else if (LiftModule.State == LiftModuleState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Lift module error";
|
||||
}
|
||||
else if (LiftModule.Position == LiftPosition.Top)
|
||||
{
|
||||
LoadManager?.AddLoad(new());
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? "Robot has picked up the load." : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
CancellationToken.Cancel();
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
await base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
using MudBlazor.Extensions;
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.ROTATE,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.HARD],
|
||||
"Xoay robot tại chỗ.",
|
||||
"Robot đã xoay tại chỗ.")]
|
||||
public class RobotRotateAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "angle",
|
||||
Description = "Góc xoay của robot. (rad)",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
}
|
||||
}.AsReadOnly();
|
||||
|
||||
private double Angle = 0;
|
||||
private INavigation? RobotNavigation;
|
||||
private ILocalization? Localization;
|
||||
|
||||
private const int MAX_TIMEOUT = 60000 * 2;
|
||||
private int CountTimeout = 0;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
RobotNavigation = ServiceProvider.GetRequiredService<INavigation>();
|
||||
Localization = ServiceProvider.GetRequiredService<ILocalization>();
|
||||
RobotNavigation.Rotate(Angle);
|
||||
CountTimeout = 0;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task StopAction()
|
||||
{
|
||||
RobotNavigation?.CancelMovement();
|
||||
return base.StopAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
if (RobotNavigation is null)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Module Navigation is not existed";
|
||||
}
|
||||
else if (RobotNavigation.State == NavigationState.Completed && Localization is not null && Math.Abs(Localization.Theta - Angle) * 180 / Math.PI < 5)
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
else if (RobotNavigation.State == NavigationState.Error)
|
||||
{
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = $"Action Handle [{Type} - {Id}] - angle: {Angle} is failed";
|
||||
}
|
||||
else if (CountTimeout++ > MAX_TIMEOUT / ActionInterval)
|
||||
{
|
||||
RobotNavigation?.CancelMovement();
|
||||
SetStatus(ActionEvent.FAILED);
|
||||
ResultDescription = "Timeout action";
|
||||
}
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var anglePara = Parameters.FirstOrDefault(p => p.Key == "angle") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'angle'");
|
||||
var angleParse = double.TryParse(anglePara.Value, out double angleData);
|
||||
if (!angleParse) throw new ActionException($"Action {Type} có parameter 'angle' không đúng kiểu dữ liệu");
|
||||
Angle = angleData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.ROTATE_KEEP_LIFT,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.HARD],
|
||||
"Xoay robot tại chỗ giữ nguyên trạng thái bàn nâng.",
|
||||
"Robot đã xoay tại chỗ giữ nguyên trạng thái bàn nâng.")]
|
||||
public class RobotRotateKeepLift(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Key = "angle",
|
||||
Description = "Góc xoay của robot. (rad)",
|
||||
ValueDataType = ValueDataType.FLOAT,
|
||||
IsOptional = false,
|
||||
}
|
||||
}.AsReadOnly();
|
||||
double Angle = 0;
|
||||
protected override Task StartAction()
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var anglePara = Parameters.FirstOrDefault(p => p.Key == "angle") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'angle'");
|
||||
var angleParse = double.TryParse(anglePara.Value, out double angleData);
|
||||
if (!angleParse) throw new ActionException($"Action {Type} có parameter 'angle' không đúng kiểu dữ liệu");
|
||||
Angle = angleData;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
using RobotNet.VDA5050.Factsheet;
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Services.Exceptions;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.SCRIPT,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"This is an script robot action.",
|
||||
"Script robot action completed.")]
|
||||
public class RobotScriptAction(IServiceProvider serviceProvider) : RobotAction(serviceProvider)
|
||||
{
|
||||
private static readonly ReadOnlyCollection<ActionParameter> ActionParameters = new List<ActionParameter>
|
||||
{
|
||||
new() {
|
||||
Key = "missionName",
|
||||
ValueDataType = ValueDataType.STRING,
|
||||
Description = "This is an mission name of script mission.",
|
||||
IsOptional = true
|
||||
},
|
||||
}.AsReadOnly();
|
||||
|
||||
private string Name = "";
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
if (ActionParameters.Count > 0)
|
||||
{
|
||||
foreach (var parameterStore in ActionParameters)
|
||||
{
|
||||
if (!parameterStore.IsOptional)
|
||||
{
|
||||
if (Action is null || Action.ActionParameters is null || !Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) throw new ActionException($"Thiếu tham số bắt buộc '{parameterStore.Key}' cho action {Type}.");
|
||||
}
|
||||
}
|
||||
Parameters = Action is null || Action.ActionParameters is null ? [] : Action.ActionParameters;
|
||||
}
|
||||
|
||||
var para = Parameters.FirstOrDefault(p => p.Key == "missionName") ?? throw new ActionException($"Action {Type} không tìm thấy parameter key 'missionName'");
|
||||
if(string.IsNullOrEmpty(para.Value)) throw new ActionException($"Action {Type}, parameter 'missionName' có value rỗng");
|
||||
Name = para.Value;
|
||||
}
|
||||
|
||||
protected override Task StartAction()
|
||||
{
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.START_CHARGING,
|
||||
[ActionScope.INSTANT, ActionScope.NODE],
|
||||
[BlockingType.HARD],
|
||||
"Bắt đầu quá trình sạc pin.",
|
||||
"Robot đã bắt đầu sạc pin.")]
|
||||
public class RobotStartChargingAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override Task StartAction()
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.START_PAUSE,
|
||||
[ActionScope.INSTANT],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Tam dừng robot.",
|
||||
"Robot đã tạm dừng.")]
|
||||
public class RobotStartPauseAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override Task StartAction()
|
||||
{
|
||||
var RobotController = ServiceProvider.GetRequiredService<IRobotController>();
|
||||
RobotController.Pause();
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.STATE_REQUEST,
|
||||
[ActionScope.INSTANT, ActionScope.NODE, ActionScope.EDGE],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Yêu cầu gửi trạng thái robot ngay lập tức.",
|
||||
"Robot đã gửi trạng thái ngay lập tức.")]
|
||||
public class RobotStateRequestAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override async Task StartAction()
|
||||
{
|
||||
var RobotStates = ServiceProvider.GetRequiredService<IState>();
|
||||
await RobotStates.PubState();
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.STOP_CHARGING,
|
||||
[ActionScope.INSTANT, ActionScope.NODE],
|
||||
[BlockingType.HARD],
|
||||
"Kết thúc quá trình sạc pin.",
|
||||
"Robot đã kết thúc sạc pin.")]
|
||||
public class RobotStopChargingAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override Task StartAction()
|
||||
{
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
using RobotNet.VDA5050.Type;
|
||||
using RobotNet10.RobotApp.Interfaces;
|
||||
|
||||
namespace RobotNet10.RobotApp.Services.Robot.Actions;
|
||||
|
||||
[RobotAction(ActionType.STOP_PAUSE,
|
||||
[ActionScope.INSTANT],
|
||||
[BlockingType.NONE, BlockingType.SOFT, BlockingType.HARD],
|
||||
"Tiếp tục hoạt động robot sau khi tạm dừng.",
|
||||
"Robot đã tiếp tục hoạt động.")]
|
||||
public class RobotStopPauseAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider)
|
||||
{
|
||||
protected override Task StartAction()
|
||||
{
|
||||
var RobotController = ServiceProvider.GetRequiredService<IRobotController>();
|
||||
RobotController.Resume();
|
||||
SetStatus(ActionEvent.FINISHED);
|
||||
ResultDescription = string.IsNullOrEmpty(ActionAttribute.ResultDescription) ? ResultDescription : ActionAttribute.ResultDescription;
|
||||
return base.StartAction();
|
||||
}
|
||||
|
||||
protected override Task ExecuteAction()
|
||||
{
|
||||
return base.ExecuteAction();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user