From ab5d3e1a1a54502bcafff86c3caee98ce24f2ed8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=90=C4=83ng=20Nguy=E1=BB=85n?= Date: Wed, 22 Oct 2025 11:16:19 +0700 Subject: [PATCH] update --- RobotApp.VDA5050/Factsheet/AgvActions.cs | 3 +- .../Factsheet/ProtocolFeatures.cs | 2 +- RobotApp.VDA5050/Type/ActionType.cs | 42 +- RobotApp/Interfaces/IInstantActions.cs | 10 +- RobotApp/Interfaces/ILocalization.cs | 24 +- RobotApp/Interfaces/IOrder.cs | 2 + RobotApp/Protos/xloc.proto | 285 +++++++++++ RobotApp/RobotApp.csproj | 71 +-- .../Services/Robot/Actions/RobotAction.cs | 191 ++++++++ .../Robot/Actions/RobotCancelOrderAction.cs | 14 + .../Robot/Actions/RobotDockToAction.cs | 14 + .../Actions/RobotFactsheetRequestAction.cs | 14 + .../Robot/Actions/RobotInitPositionAction.cs | 14 + .../Robot/Actions/RobotLiftDownAction.cs | 14 + .../Robot/Actions/RobotLiftRotateAction.cs | 14 + .../Robot/Actions/RobotLiftUpAction.cs | 14 + .../Actions/RobotMoveStraightToCoorAction.cs | 14 + .../RobotMoveStraightWithDistanceAction.cs | 14 + .../Robot/Actions/RobotMutedBaseOffAction.cs | 14 + .../Robot/Actions/RobotMutedBaseOnAction.cs | 14 + .../Robot/Actions/RobotMutedLoadOffAction.cs | 14 + .../Robot/Actions/RobotMutedLoadOnAction.cs | 14 + .../Robot/Actions/RobotRotateAction.cs | 14 + .../Robot/Actions/RobotRotateKeepLift.cs | 14 + .../Robot/Actions/RobotStartChargingAction.cs | 14 + .../Robot/Actions/RobotStartPauseAction.cs | 23 + .../Robot/Actions/RobotStateRequestAction.cs | 14 + .../Robot/Actions/RobotStopChargingAction.cs | 14 + .../Robot/Actions/RobotStopPauseAction.cs | 14 + RobotApp/Services/Robot/RobotAction.cs | 31 -- .../Services/Robot/RobotActionController.cs | 118 +++++ RobotApp/Services/Robot/RobotActionStorage.cs | 33 ++ RobotApp/Services/Robot/RobotConfiguration.cs | 2 + RobotApp/Services/Robot/RobotConnection.cs | 2 +- RobotApp/Services/Robot/RobotController.cs | 14 +- RobotApp/Services/Robot/RobotFactsheet.cs | 328 ++++++++++++- RobotApp/Services/Robot/RobotLocalization.cs | 449 ++++++++++++++++-- .../Services/Robot/RobotOrderController.cs | 10 + RobotApp/Services/RobotExtensions.cs | 7 +- RobotApp/Services/State/RobotState.cs | 1 - 40 files changed, 1769 insertions(+), 145 deletions(-) create mode 100644 RobotApp/Protos/xloc.proto create mode 100644 RobotApp/Services/Robot/Actions/RobotAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotCancelOrderAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotDockToAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotFactsheetRequestAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotInitPositionAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotLiftDownAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotLiftRotateAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotLiftUpAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotMoveStraightToCoorAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotMoveStraightWithDistanceAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotMutedBaseOffAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotMutedBaseOnAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotMutedLoadOffAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotMutedLoadOnAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotRotateAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotRotateKeepLift.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotStartChargingAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotStartPauseAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotStateRequestAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotStopChargingAction.cs create mode 100644 RobotApp/Services/Robot/Actions/RobotStopPauseAction.cs delete mode 100644 RobotApp/Services/Robot/RobotAction.cs create mode 100644 RobotApp/Services/Robot/RobotActionController.cs create mode 100644 RobotApp/Services/Robot/RobotActionStorage.cs diff --git a/RobotApp.VDA5050/Factsheet/AgvActions.cs b/RobotApp.VDA5050/Factsheet/AgvActions.cs index 22b520a..ee08f61 100644 --- a/RobotApp.VDA5050/Factsheet/AgvActions.cs +++ b/RobotApp.VDA5050/Factsheet/AgvActions.cs @@ -9,7 +9,8 @@ public enum ActionScopes NODE, EDGE, } -public class AgvActions + +public class AgvAction { [Required] public string ActionType { get; set; } = string.Empty; diff --git a/RobotApp.VDA5050/Factsheet/ProtocolFeatures.cs b/RobotApp.VDA5050/Factsheet/ProtocolFeatures.cs index ac67457..7b9f744 100644 --- a/RobotApp.VDA5050/Factsheet/ProtocolFeatures.cs +++ b/RobotApp.VDA5050/Factsheet/ProtocolFeatures.cs @@ -8,5 +8,5 @@ public class ProtocolFeatures [Required] public OptionalParameters[] OptionalParameters { get; set; } = []; [Required] - public AgvActions[] AgvActions { get; set; } = []; + public AgvAction[] AgvActions { get; set; } = []; } diff --git a/RobotApp.VDA5050/Type/ActionType.cs b/RobotApp.VDA5050/Type/ActionType.cs index 4b56503..7b58bd5 100644 --- a/RobotApp.VDA5050/Type/ActionType.cs +++ b/RobotApp.VDA5050/Type/ActionType.cs @@ -2,16 +2,32 @@ public enum ActionType { - START_PAUSE, - STOP_PAUSE, - START_CHARGING, - STOP_CHARGING, - INITIALIZATION_POSITION, - PICK, - DROP, - CANCEL_ORDER, - ROTATE, - REQUEST_FACTSHEET, - REQUEST_VISUALIZATION, - REQUEST_STATE, -} + startPause, + stopPause, + startCharging, + stopCharging, + initPosition, + stateRequest, + factsheetRequest, + + logReport, + pick, + drop, + detectObject, + finePositioning, + waitForTrigger, + + cancelOrder, + liftUp, + liftDown, + liftRotate, + rotate, + rotateKeepLift, + mutedBaseOn, + mutedBaseOff, + mutedLoadOn, + mutedLoadOff, + dockTo, + moveStraightToCoor, + moveStraightWithDistance, +} \ No newline at end of file diff --git a/RobotApp/Interfaces/IInstantActions.cs b/RobotApp/Interfaces/IInstantActions.cs index 6e6ab98..d5ea547 100644 --- a/RobotApp/Interfaces/IInstantActions.cs +++ b/RobotApp/Interfaces/IInstantActions.cs @@ -7,8 +7,10 @@ public interface IInstantActions { ActionState[] ActionStates { get; } bool HasActionRunning { get; } - bool AddOrderActions(Action[] actions); - bool StartAction(string actionId); - bool AddInstanceAction(Action action); - bool StopAction(); + void AddOrderActions(Action[] actions); + void AddInstantAction(Action action); + void StartOrderAction(string actionId, bool wait); + void ClearInstantActions(); + void PauseActions(); + void ResumeActions(); } diff --git a/RobotApp/Interfaces/ILocalization.cs b/RobotApp/Interfaces/ILocalization.cs index 9a22d40..dc24872 100644 --- a/RobotApp/Interfaces/ILocalization.cs +++ b/RobotApp/Interfaces/ILocalization.cs @@ -60,11 +60,11 @@ public interface ILocalization MessageResult SetInitializePosition(double x, double y, double theta); /// - /// Bắt đầu quá trình lập bản đồ với độ phân giải chỉ định. + /// Bắt đầu quá trình lập bản đồ. /// /// đơn vị mét/px /// - MessageResult StartMapping(double resolution); + MessageResult StartMapping(); /// /// Dừng quá trình lập bản đồ hiện tại và lưu bản đồ với tên chỉ định. @@ -100,7 +100,7 @@ public interface ILocalization /// /// /// - MessageResult SwitchMap(string mapName, double x, double y, double theta); + MessageResult SwitchMap(string mapName, bool useInitialPose, double x, double y, double theta); /// /// Thay đổi gốc tọa độ của bản đồ hiện tại. @@ -110,4 +110,22 @@ public interface ILocalization /// /// MessageResult ChangeMapOrigin(double x, double y, double theta); + + /// + /// Bắt đầu quá trình cập nhật bản đồ. + /// + /// + MessageResult StartUpdateMap(); + + /// + /// Kết thúc quá trình cập nhật bản đồ. + /// + /// + MessageResult StopUpdateMap(bool save); + + /// + /// Xóa bỏ các lỗi Slam + /// + /// + MessageResult ResetSlamError(); } diff --git a/RobotApp/Interfaces/IOrder.cs b/RobotApp/Interfaces/IOrder.cs index fc6c16e..9e88935 100644 --- a/RobotApp/Interfaces/IOrder.cs +++ b/RobotApp/Interfaces/IOrder.cs @@ -16,4 +16,6 @@ public interface IOrder void StartOrder(string orderId, Node[] nodes, Edge[] edges); void UpdateOrder(int orderUpdateId, Node[] nodes, Edge[] edges); void StopOrder(); + void PauseOrder(); + void ResumeOrder(); } diff --git a/RobotApp/Protos/xloc.proto b/RobotApp/Protos/xloc.proto new file mode 100644 index 0000000..9a9292b --- /dev/null +++ b/RobotApp/Protos/xloc.proto @@ -0,0 +1,285 @@ +syntax = "proto3"; + +package xloc; + +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; +import "google/protobuf/empty.proto"; + +// Std Messages +message Header +{ + uint32 seq = 1; + google.protobuf.Timestamp stamp = 2; + string frame_id = 3; +} +message ColorRGBA +{ + float r = 1; + float g = 2; + float b = 3; + float a = 4; +} + +// Geometry Messages +message Point{ + double x = 1; + double y = 2; + double z = 3; +} +message Point32 +{ + float x = 1; + float y = 2; + float z = 3; +} +message PointStamped +{ + Header header = 1; + Point point = 2; +} +message Quaternion +{ + double x = 1; + double y = 2; + double z = 3; + double w = 4; +} +message Pose +{ + Point position = 1; + Quaternion orientation = 2; +} +message Pose2D +{ + double x = 1; + double y = 2; + double theta = 3; +} +message PoseArray +{ + Header header = 1; + repeated Pose poses = 2; +} +message PoseStamped +{ + Header header = 1; + Pose pose = 2; +} +message Vector3 +{ + double x = 1; + double y = 2; + double z = 3; +} +message Transform +{ + Vector3 translation = 1; + Quaternion rotation = 2; +} +message TransformStamped +{ + Header header = 1; + string child_frame_id = 2; + Transform transform = 3; +} + +// Nav Messages +message MapMetaData +{ + google.protobuf.Timestamp map_load_time = 1; + float resolution = 2; + uint32 width = 3; + uint32 height = 4; + Point origin = 5; +} +message OccupancyGrid +{ + Header header = 1; + MapMetaData info = 2; + repeated int32 data = 3; +} + +// Visualization Messages +message Marker +{ + enum MarkerType + { + ARROW = 0; + CUBE = 1; + SPHERE = 2; + CYLINDER = 3; + LINE_STRIP = 4; + LINE_LIST = 5; + CUBE_LIST = 6; + SPHERE_LIST = 7; + POINTS = 8; + TEXT_VIEW_FACING = 9; + MESH_RESOURCE = 10; + TRIANGLE_LIST = 11; + } + enum Action + { + ADD = 0; + MODIFY = 1; + DELETE = 2; + DELETEALL = 3; + } + Header header = 1; + string ns = 2; + int32 id = 3; + MarkerType type = 4; + Action action = 5; + Pose pose = 6; + Vector3 scale = 7; + ColorRGBA color = 8; + google.protobuf.Duration lifetime = 9; + bool frame_locked = 10; + repeated Point points = 11; + repeated ColorRGBA colors = 12; + string text = 13; + string mesh_resource = 14; + bool mesh_use_embedded_materials = 15; +} +message MarkerArray +{ + repeated Marker markers = 1; +} + +// XLOC Custom Messages +message StatusResponse +{ + enum StatusCode + { + OK=0; + CANCELLED=1; + UNKNOWN=2; + INVALID_ARGUMENT=3; + DEADLINE_EXCEEDED=4; + NOT_FOUND=5; + ALREADY_EXISTS=6; + PERMISSION_DENIED=7; + RESOURCE_EXHAUSTED=8; + FAILED_PRECONDITION=9; + ABORTED=10; + OUT_OF_RANGE=11; + UNIMPLEMENTED=12; + INTERNAL=13; + UNAVAILABLE=14; + DATA_LOSS=15; + } + StatusCode code = 1; + string message = 2; +} + +message XlocDiagnostics +{ + enum SlamState + { + MAPPING = 0; + LOCALIZATION = 1; + PROCESSING = 2; + READY = 3; + ERROR = 4; + } + Header header = 1; + SlamState slam_state = 2; + string slam_state_detail = 3; + string current_active_map = 4; + float reliability = 5; + float matching_score = 6; +} + +// Service definitions + +message ActivateMapRequest +{ + string map_file_name = 1; +} +message ActivateMapResponse +{ + StatusResponse status = 1; +} +message SwitchMapRequest +{ + string map_file_name = 1; + bool use_initial_pose = 2; + Pose initial_pose = 3; +} +message SwitchMapResponse +{ + StatusResponse status = 1; +} +message StartMappingResponse +{ + StatusResponse status = 1; +} +message StopMappingRequest +{ + string save_map_filename = 1; +} +message StopMappingResponse +{ + StatusResponse status = 1; +} +message StartLocalizationResponse +{ + StatusResponse status = 1; + int32 trajectory_id = 2; +} +message StopLocalizationResponse +{ + StatusResponse status = 1; +} +message ChangeMapOriginRequest +{ + Pose new_map_origin = 1; +} +message ChangeMapOriginResponse +{ + StatusResponse status = 1; +} +message StartUpdateMapResponse +{ + StatusResponse status = 1; +} +message StopUpdateMapRequest +{ + bool save_updated_map = 1; +} +message StopUpdateMapResponse +{ + StatusResponse status = 1; +} +message SetInitialPoseRequest +{ + Pose initial_pose = 1; +} +message SetInitialPoseResponse +{ + StatusResponse status = 1; +} +message ResetSlamErrorResponse +{ + StatusResponse status = 1; +} + +service XlocServices { + rpc ActivateMap (ActivateMapRequest) returns (ActivateMapResponse); + rpc SwitchMap (SwitchMapRequest) returns (SwitchMapResponse); + rpc StartMapping (google.protobuf.Empty) returns (StartMappingResponse); + rpc StopMapping (StopMappingRequest) returns (StopMappingResponse); + rpc StartLocalization (google.protobuf.Empty) returns (StartLocalizationResponse); + rpc StopLocalization (google.protobuf.Empty) returns (StopLocalizationResponse); + rpc ChangeMapOrigin (ChangeMapOriginRequest) returns (ChangeMapOriginResponse); + rpc StartUpdateMap (google.protobuf.Empty) returns (StartUpdateMapResponse); + rpc StopUpdateMap (StopUpdateMapRequest) returns (StopUpdateMapResponse); + rpc SetInitialPose (SetInitialPoseRequest) returns (SetInitialPoseResponse); + rpc ResetSlamError (google.protobuf.Empty) returns (ResetSlamErrorResponse); + rpc GetPose (google.protobuf.Empty) returns (stream PoseStamped); + rpc GetStaticMap (google.protobuf.Empty) returns (OccupancyGrid); + rpc GetStreamMap (google.protobuf.Empty) returns (stream OccupancyGrid); + rpc GetTrajectoryNodeListMarker (google.protobuf.Empty) returns (stream MarkerArray); + rpc GetConstraintListMarker (google.protobuf.Empty) returns (stream MarkerArray); + rpc GetDiagnostics (google.protobuf.Empty) returns (stream XlocDiagnostics); +} \ No newline at end of file diff --git a/RobotApp/RobotApp.csproj b/RobotApp/RobotApp.csproj index b89db01..e73f004 100644 --- a/RobotApp/RobotApp.csproj +++ b/RobotApp/RobotApp.csproj @@ -1,36 +1,45 @@ - + - - net9.0 - enable - enable - aspnet-RobotApp-1f61caa2-bbbb-40cd-88b6-409b408a84ea - + + net9.0 + enable + enable + aspnet-RobotApp-1f61caa2-bbbb-40cd-88b6-409b408a84ea + - - - - - - + + + + + + - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/RobotApp/Services/Robot/Actions/RobotAction.cs b/RobotApp/Services/Robot/Actions/RobotAction.cs new file mode 100644 index 0000000..0b30496 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotAction.cs @@ -0,0 +1,191 @@ +using Grpc.Core; +using RobotApp.VDA5050.Factsheet; +using RobotApp.VDA5050.InstantAction; +using RobotApp.VDA5050.State; +using RobotApp.VDA5050.Type; + +namespace RobotApp.Services.Robot.Actions; + +public abstract class RobotAction(IServiceProvider serviceProvider) : IAsyncDisposable +{ + public ActionType Type { get; private set; } + public string Id { get; private set; } = ""; + public string Description { get; private set; } = ""; + public BlockingType BlockingType { get; private set; } + public ActionParameter[] Parameters { get; private set; } = []; + public ActionStatus Status { get; protected set; } = ActionStatus.WAITING; + public string ResultDescription { get; set; } = ""; + public bool IsCompleted => Status == ActionStatus.FINISHED || Status == ActionStatus.FAILED; + public ActionScopes ActionScope { get; set; } + + private WatchTimerAsync? ActionTimer; + private const int ActionInterval = 200; + + protected IServiceProvider ServiceProvider = serviceProvider; + protected VDA5050.InstantAction.Action? Action; + protected IServiceScope? Scope; + protected Logger? Logger; + protected AgvAction? AgvAction; + protected bool IsPaused = false; + protected ActionStatus HistoryStatus; + + public bool Initialize(ActionScopes actionScope, VDA5050.InstantAction.Action action) + { + Status = ActionStatus.WAITING; + ActionScope = actionScope; + Action = action; + return Initialize(); + } + + public void Start() + { + if (Status != ActionStatus.WAITING) return; + Scope ??= ServiceProvider.CreateScope(); + Logger = Scope.ServiceProvider.GetRequiredService>(); + ActionTimer = new(ActionInterval, ActionHandler, Logger); + Status = ActionStatus.INITIALIZING; + ActionTimer.Start(); + } + + public void Pause() + { + HistoryStatus = Status; + Status = ActionStatus.PAUSED; + } + + public void Resume() + { + if (Status == ActionStatus.PAUSED) Status = ActionStatus.WAITING; + } + + public void Cancel() + { + Dispose(); + } + + public async Task WaitAsync(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested && !IsCompleted) + { + await Task.Delay(500, cancellationToken).ConfigureAwait(false); + } + } + + public void Wait(CancellationToken cancellationToken) + { + while (!cancellationToken.IsCancellationRequested && !IsCompleted) + { + Task.Delay(500, cancellationToken).Wait(cancellationToken); + } + } + + protected virtual Task StartAction() + { + 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 bool Initialize() + { + if (Action is null) return false; + Scope ??= ServiceProvider.CreateScope(); + var FactsheetService = Scope.ServiceProvider.GetRequiredService(); + + if (Enum.TryParse(Action.ActionType, out ActionType type)) Type = type; + else return false; + + var actionStore = FactsheetService.GetAction(Type); + if (actionStore is null) return false; + + if (Enum.TryParse(Action.BlockingType, out BlockingType blockingType)) BlockingType = blockingType; + else return false; + + if (!actionStore.BlockingTypes.Any(bt => bt == BlockingType.ToString()) || !actionStore.ActionScopes.Any(sp => sp == ActionScope.ToString())) + { + return false; + } + + if (actionStore.ActionParameters.Length > 0) + { + foreach (var parameterStore in actionStore.ActionParameters) + { + if (!parameterStore.IsOptional) + { + if (!Action.ActionParameters.Any(a => a.Key == parameterStore.Key)) + { + return false; + } + } + } + Parameters = Action.ActionParameters; + } + + Id = Action.ActionId; + Description = Action.ActionDescription; + AgvAction = actionStore; + Status = ActionStatus.WAITING; + return true; + } + + private async Task ActionHandler() + { + try + { + if (Status == ActionStatus.INITIALIZING) + { + Logger?.Info($"Thực hiện action {Type}"); + Status = ActionStatus.RUNNING; + await StartAction(); + } + else if (Status == ActionStatus.RUNNING) + { + await ExecuteAction(); + } + else if (Status == ActionStatus.PAUSED) + { + await PauseAction(); + } + else if (Status == ActionStatus.WAITING && IsPaused) + { + await ResumeAction(); + } + + if (IsCompleted) + { + await Dispose(); + } + } + catch (Exception ex) + { + Logger?.Error($"Thực hiện action {Type} xảy ra lỗi: {ex.Message}"); + Status = ActionStatus.FAILED; + ResultDescription = $"Thực hiện action {Type} xảy ra lỗi: {ex.Message}"; + } + } + + private Task Dispose() + { + ActionTimer?.Dispose(); + return Task.CompletedTask; + } + + public async ValueTask DisposeAsync() + { + await Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotCancelOrderAction.cs b/RobotApp/Services/Robot/Actions/RobotCancelOrderAction.cs new file mode 100644 index 0000000..dd4ad5e --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotCancelOrderAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotCancelOrderAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotDockToAction.cs b/RobotApp/Services/Robot/Actions/RobotDockToAction.cs new file mode 100644 index 0000000..d319905 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotDockToAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotDockToAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotFactsheetRequestAction.cs b/RobotApp/Services/Robot/Actions/RobotFactsheetRequestAction.cs new file mode 100644 index 0000000..c047144 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotFactsheetRequestAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotFactsheetRequestAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotInitPositionAction.cs b/RobotApp/Services/Robot/Actions/RobotInitPositionAction.cs new file mode 100644 index 0000000..10ec054 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotInitPositionAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotInitPositionAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotLiftDownAction.cs b/RobotApp/Services/Robot/Actions/RobotLiftDownAction.cs new file mode 100644 index 0000000..e142a41 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotLiftDownAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotLiftDownAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotLiftRotateAction.cs b/RobotApp/Services/Robot/Actions/RobotLiftRotateAction.cs new file mode 100644 index 0000000..2b4e480 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotLiftRotateAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotLiftRotateAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotLiftUpAction.cs b/RobotApp/Services/Robot/Actions/RobotLiftUpAction.cs new file mode 100644 index 0000000..6e4e796 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotLiftUpAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotLiftUpAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotMoveStraightToCoorAction.cs b/RobotApp/Services/Robot/Actions/RobotMoveStraightToCoorAction.cs new file mode 100644 index 0000000..1d93485 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotMoveStraightToCoorAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotMoveStraightToCoorAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotMoveStraightWithDistanceAction.cs b/RobotApp/Services/Robot/Actions/RobotMoveStraightWithDistanceAction.cs new file mode 100644 index 0000000..eda820f --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotMoveStraightWithDistanceAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotMoveStraightWithDistanceAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotMutedBaseOffAction.cs b/RobotApp/Services/Robot/Actions/RobotMutedBaseOffAction.cs new file mode 100644 index 0000000..871ee29 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotMutedBaseOffAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotMutedBaseOffAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotMutedBaseOnAction.cs b/RobotApp/Services/Robot/Actions/RobotMutedBaseOnAction.cs new file mode 100644 index 0000000..e24b9f1 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotMutedBaseOnAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotMutedBaseOnAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotMutedLoadOffAction.cs b/RobotApp/Services/Robot/Actions/RobotMutedLoadOffAction.cs new file mode 100644 index 0000000..4726427 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotMutedLoadOffAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotMutedLoadOffAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotMutedLoadOnAction.cs b/RobotApp/Services/Robot/Actions/RobotMutedLoadOnAction.cs new file mode 100644 index 0000000..82b2c8b --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotMutedLoadOnAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotMutedLoadOnAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotRotateAction.cs b/RobotApp/Services/Robot/Actions/RobotRotateAction.cs new file mode 100644 index 0000000..ca9bce8 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotRotateAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotRotateAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotRotateKeepLift.cs b/RobotApp/Services/Robot/Actions/RobotRotateKeepLift.cs new file mode 100644 index 0000000..b93d8ec --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotRotateKeepLift.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotRotateKeepLift(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotStartChargingAction.cs b/RobotApp/Services/Robot/Actions/RobotStartChargingAction.cs new file mode 100644 index 0000000..8b054d1 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotStartChargingAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotStartChargingAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotStartPauseAction.cs b/RobotApp/Services/Robot/Actions/RobotStartPauseAction.cs new file mode 100644 index 0000000..1dded4f --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotStartPauseAction.cs @@ -0,0 +1,23 @@ +using RobotApp.VDA5050.State; + +namespace RobotApp.Services.Robot.Actions; + +public class RobotStartPauseAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + Scope ??= ServiceProvider.CreateScope(); + var robotController = Scope.ServiceProvider.GetRequiredService(); + robotController.Pause(); + Status = ActionStatus.FINISHED; + ResultDescription = AgvAction is not null ? AgvAction.ResultDescription : ResultDescription; + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + Status = ActionStatus.FINISHED; + ResultDescription = AgvAction is not null ? AgvAction.ResultDescription : ResultDescription; + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotStateRequestAction.cs b/RobotApp/Services/Robot/Actions/RobotStateRequestAction.cs new file mode 100644 index 0000000..b36f8c7 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotStateRequestAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotStateRequestAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotStopChargingAction.cs b/RobotApp/Services/Robot/Actions/RobotStopChargingAction.cs new file mode 100644 index 0000000..0f07d13 --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotStopChargingAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotStopChargingAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/Actions/RobotStopPauseAction.cs b/RobotApp/Services/Robot/Actions/RobotStopPauseAction.cs new file mode 100644 index 0000000..49f1e0c --- /dev/null +++ b/RobotApp/Services/Robot/Actions/RobotStopPauseAction.cs @@ -0,0 +1,14 @@ +namespace RobotApp.Services.Robot.Actions; + +public class RobotStopPauseAction(IServiceProvider ServiceProvider) : RobotAction(ServiceProvider) +{ + protected override Task StartAction() + { + return base.StartAction(); + } + + protected override Task ExecuteAction() + { + return base.ExecuteAction(); + } +} diff --git a/RobotApp/Services/Robot/RobotAction.cs b/RobotApp/Services/Robot/RobotAction.cs deleted file mode 100644 index 02c9893..0000000 --- a/RobotApp/Services/Robot/RobotAction.cs +++ /dev/null @@ -1,31 +0,0 @@ -using RobotApp.Interfaces; -using RobotApp.VDA5050.State; - -namespace RobotApp.Services.Robot; - -public class RobotAction : IInstantActions -{ - public ActionState[] ActionStates { get; private set; } = []; - - public bool HasActionRunning => throw new NotImplementedException(); - - public bool AddInstanceAction(VDA5050.InstantAction.Action action) - { - throw new NotImplementedException(); - } - - public bool AddOrderActions(VDA5050.InstantAction.Action[] actions) - { - throw new NotImplementedException(); - } - - public bool StartAction(string actionId) - { - throw new NotImplementedException(); - } - - public bool StopAction() - { - throw new NotImplementedException(); - } -} diff --git a/RobotApp/Services/Robot/RobotActionController.cs b/RobotApp/Services/Robot/RobotActionController.cs new file mode 100644 index 0000000..e9cb321 --- /dev/null +++ b/RobotApp/Services/Robot/RobotActionController.cs @@ -0,0 +1,118 @@ +using RobotApp.Interfaces; +using RobotApp.Services.Robot.Actions; +using RobotApp.VDA5050.Factsheet; +using RobotApp.VDA5050.State; +using RobotApp.VDA5050.Type; +using System.Collections.Concurrent; + +namespace RobotApp.Services.Robot; + +public class RobotActionController(Logger Logger, RobotActionStorage ActionStorage) : BackgroundService, IInstantActions +{ + public ActionState[] ActionStates => [.. Actions.Values.Select(a => new ActionState + { + ActionId = a.Id, + ActionType = a.Type.ToString(), + ActionDescription = a.Description, + ActionStatus = a.Status.ToString(), + ResultDescription = a.ResultDescription, + })]; + public bool HasActionRunning => !ActionQueue.IsEmpty || Actions.Values.Any(a => !a.IsCompleted); + + private readonly ConcurrentDictionary Actions = []; + private readonly ConcurrentQueue<(ActionScopes scope, VDA5050.InstantAction.Action action)> ActionQueue = []; + + private WatchTimer? HandlerTimer; + private const int HandlerInterval = 200; + + public void AddInstantAction(VDA5050.InstantAction.Action action) + { + ActionQueue.Enqueue((ActionScopes.INSTANT, action)); + } + + public void AddOrderActions(VDA5050.InstantAction.Action[] actions) + { + foreach (var action in actions) + { + ActionQueue.Enqueue((ActionScopes.NODE, action)); + } + } + + public void StartOrderAction(string actionId, bool wait) + { + if (Actions.TryGetValue(actionId, out RobotAction? robotAction) && robotAction is not null) + { + robotAction.Start(); + if(wait) + { + robotAction.Wait(CancellationToken.None); + } + } + } + + public void PauseActions() + { + foreach(var action in Actions.Values) + { + action.Pause(); + } + } + + public void ResumeActions() + { + foreach (var action in Actions.Values) + { + action.Resume(); + } + } + + private void ActionHandler() + { + try + { + while(!ActionQueue.IsEmpty) + { + if (ActionQueue.TryDequeue(out var result)) + { + if (Actions.ContainsKey(result.action.ActionId)) return; + if (Enum.TryParse(result.action.ActionType, out ActionType actionType)) + { + var robotAction = ActionStorage[actionType]; + if (robotAction is not null) + { + var init = robotAction.Initialize(result.scope, result.action); + if (init) + { + Actions.TryAdd(result.action.ActionId, robotAction); + if(result.scope == ActionScopes.INSTANT) robotAction.Start(); + } + } + } + } + } + } + catch (Exception ex) + { + Logger?.Error($"Khởi tạo action xảy ra lỗi: {ex.Message}"); + } + } + + public void ClearInstantActions() + { + ActionQueue.Clear(); + Actions.Clear(); + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + await Task.Yield(); + HandlerTimer = new(HandlerInterval, ActionHandler, Logger); + HandlerTimer.Start(); + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + HandlerTimer?.Dispose(); + return base.StopAsync(cancellationToken); + } +} diff --git a/RobotApp/Services/Robot/RobotActionStorage.cs b/RobotApp/Services/Robot/RobotActionStorage.cs new file mode 100644 index 0000000..20966f4 --- /dev/null +++ b/RobotApp/Services/Robot/RobotActionStorage.cs @@ -0,0 +1,33 @@ +using RobotApp.Services.Robot.Actions; +using RobotApp.VDA5050.Type; + +namespace RobotApp.Services.Robot; + + +public class RobotActionStorage(IServiceProvider ServiceProvider) +{ + public RobotAction? this[ActionType type] => type switch + { + ActionType.startPause => new RobotStartPauseAction(ServiceProvider), + ActionType.stopPause => new RobotStopPauseAction(ServiceProvider), + ActionType.startCharging => new RobotStartChargingAction(ServiceProvider), + ActionType.stopCharging => new RobotStopChargingAction(ServiceProvider), + ActionType.initPosition => new RobotInitPositionAction(ServiceProvider), + ActionType.stateRequest => new RobotStateRequestAction(ServiceProvider), + ActionType.factsheetRequest => new RobotFactsheetRequestAction(ServiceProvider), + ActionType.cancelOrder => new RobotCancelOrderAction(ServiceProvider), + ActionType.liftUp => new RobotLiftUpAction(ServiceProvider), + ActionType.liftDown => new RobotLiftDownAction(ServiceProvider), + ActionType.liftRotate => new RobotLiftRotateAction(ServiceProvider), + ActionType.rotate => new RobotRotateAction(ServiceProvider), + ActionType.rotateKeepLift => new RobotRotateKeepLift(ServiceProvider), + ActionType.mutedBaseOn => new RobotMutedBaseOnAction(ServiceProvider), + ActionType.mutedBaseOff => new RobotMutedBaseOffAction(ServiceProvider), + ActionType.mutedLoadOn => new RobotMutedLoadOnAction(ServiceProvider), + ActionType.mutedLoadOff => new RobotMutedLoadOffAction(ServiceProvider), + ActionType.dockTo => new RobotDockToAction(ServiceProvider), + ActionType.moveStraightToCoor => new RobotMoveStraightToCoorAction(ServiceProvider), + ActionType.moveStraightWithDistance => new RobotMoveStraightWithDistanceAction(ServiceProvider), + _ => null, + }; +} diff --git a/RobotApp/Services/Robot/RobotConfiguration.cs b/RobotApp/Services/Robot/RobotConfiguration.cs index 54a7c22..659af94 100644 --- a/RobotApp/Services/Robot/RobotConfiguration.cs +++ b/RobotApp/Services/Robot/RobotConfiguration.cs @@ -22,4 +22,6 @@ public class RobotConfiguration public byte PLCUnitId { get; set; } = 1; public bool IsSimulation { get; set; } = true; public SimulationModel SimulationModel { get; set; } = new(); + + public string XlocAddress { get; set; } = ""; } diff --git a/RobotApp/Services/Robot/RobotConnection.cs b/RobotApp/Services/Robot/RobotConnection.cs index ce97d0b..bede774 100644 --- a/RobotApp/Services/Robot/RobotConnection.cs +++ b/RobotApp/Services/Robot/RobotConnection.cs @@ -54,7 +54,7 @@ public class RobotConnection(RobotConfiguration RobotConfiguration, public async Task StartConnection(CancellationToken cancellationToken) { - MqttClient = new MQTTClient(SerialNumber, VDA5050Setting, MQTTClientLogger); + MqttClient = new MQTTClient(RobotConfiguration.SerialNumber, VDA5050Setting, MQTTClientLogger); MqttClient.OrderChanged += OrderChanged; MqttClient.InstanceActionsChanged += InstanceActionsChanged; await MqttClient.ConnectAsync(cancellationToken); diff --git a/RobotApp/Services/Robot/RobotController.cs b/RobotApp/Services/Robot/RobotController.cs index 6a01110..69256fd 100644 --- a/RobotApp/Services/Robot/RobotController.cs +++ b/RobotApp/Services/Robot/RobotController.cs @@ -26,10 +26,10 @@ public partial class RobotController(IOrder OrderManager, await InitializationingHandler(stoppingToken); } - public override async Task StopAsync(CancellationToken cancellationToken) + public override Task StopAsync(CancellationToken cancellationToken) { StopHandler(); - await base.StopAsync(cancellationToken); + return base.StopAsync(cancellationToken); } public void NewOrderUpdated(OrderMsg order) @@ -90,4 +90,14 @@ public partial class RobotController(IOrder OrderManager, } } } + + public void Pause() + { + + } + + public void Resume() + { + + } } diff --git a/RobotApp/Services/Robot/RobotFactsheet.cs b/RobotApp/Services/Robot/RobotFactsheet.cs index 7168631..1210f57 100644 --- a/RobotApp/Services/Robot/RobotFactsheet.cs +++ b/RobotApp/Services/Robot/RobotFactsheet.cs @@ -1,34 +1,348 @@ - -using RobotApp.Common.Shares; +using RobotApp.Common.Shares; using RobotApp.VDA5050; using RobotApp.VDA5050.Factsheet; +using RobotApp.VDA5050.InstantAction; +using RobotApp.VDA5050.Type; using System.Text.Json; namespace RobotApp.Services.Robot; -public class RobotFactsheet(RobotConnection RobotConnection) : BackgroundService + +public class RobotFactsheet(RobotConnection RobotConnection, RobotConfiguration RobotConfiguration) : BackgroundService { + private readonly Dictionary AgvActions = new() + { + { ActionType.startPause, StartPause}, + { ActionType.stopPause, StopPause}, + { ActionType.startCharging, StartCharging}, + { ActionType.stopCharging, StopCharging}, + { ActionType.initPosition, InitPosition}, + { ActionType.stateRequest, StateRequest}, + { ActionType.factsheetRequest, FactsheetRequest}, + { ActionType.cancelOrder, CancelOrder}, + { ActionType.liftUp, LiftUp}, + { ActionType.liftDown, LiftDown}, + { ActionType.liftRotate, LiftRotate}, + { ActionType.rotate, Rotate}, + { ActionType.rotateKeepLift, RotateKeepLift}, + { ActionType.mutedBaseOn, MutedBaseOn}, + { ActionType.mutedBaseOff, MutedBaseOff}, + { ActionType.mutedLoadOn, MutedLoadOn}, + { ActionType.mutedLoadOff, MutedLoadOff}, + { ActionType.dockTo, DockTo}, + { ActionType.moveStraightToCoor, MoveStraightToCoor}, + { ActionType.moveStraightWithDistance, MoveStraightWithDistance}, + }; + + public AgvAction? GetAction(ActionType actionType) => AgvActions.TryGetValue(actionType, out AgvAction? value) && value is not null ? value : null; + public async Task PubFactsheet() { if (!RobotConnection.IsConnected) return; FactSheetMsg factSheet = new() { - SerialNumber = RobotConnection.SerialNumber, + SerialNumber = RobotConfiguration.SerialNumber, ProtocolFeatures = new() { - AgvActions = [], + AgvActions = [..AgvActions.Values], } }; string factSheetJson = JsonSerializer.Serialize(factSheet, JsonOptionExtends.Write); await RobotConnection.Publish(VDA5050Topic.FACTSHEET.ToTopicString(), factSheetJson); } + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { - while(!stoppingToken.IsCancellationRequested) + await Task.Yield(); + while (!stoppingToken.IsCancellationRequested) { - await Task.Delay(1000, stoppingToken); if (RobotConnection.IsConnected) break; + await Task.Delay(1000); } await PubFactsheet(); } + + public readonly static AgvAction StartPause = new() + { + ActionType = ActionType.startPause.ToString(), + ActionDescription = "Tam dừng robot.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã tạm dừng.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction StopPause = new() + { + ActionType = ActionType.stopPause.ToString(), + ActionDescription = "Tiếp tục hoạt động robot sau khi tạm dừng.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã tiếp tục hoạt động.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString(), BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction StartCharging = new() + { + ActionType = ActionType.startCharging.ToString(), + ActionDescription = "Bắt đầu quá trình sạc pin.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã bắt đầu sạc pin.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction StopCharging = new() + { + ActionType = ActionType.stopCharging.ToString(), + ActionDescription = "Kết thúc quá trình sạc pin.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã kết thúc sạc pin.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction InitPosition = new() + { + ActionType = ActionType.initPosition.ToString(), + ActionDescription = "Khởi tạo vị trí robot.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [ + new() + { + Key = "x", + Description = "Tọa độ X của vị trí khởi tạo.", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }, + new() + { + Key = "y", + Description = "Tọa độ Y của vị trí khởi tạo.", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }, + new() + { + Key = "theta", + Description = "Góc quay (theta) của vị trí khởi tạo. (rad)", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }], + ResultDescription = "Robot đã khởi tạo vị trí.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction StateRequest = new() + { + ActionType = ActionType.stateRequest.ToString(), + ActionDescription = "Yêu cầu gửi trạng thái robot ngay lập tức.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã gửi trạng thái ngay lập tức.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction FactsheetRequest = new() + { + ActionType = ActionType.factsheetRequest.ToString(), + ActionDescription = "Yêu cầu gửi Factsheet robot ngay lập tức.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã gửi Factsheet ngay lập tức.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction CancelOrder = new() + { + ActionType = ActionType.cancelOrder.ToString(), + ActionDescription = "Hủy bỏ Order hiện tại của robot.", + ActionScopes = [ActionScopes.INSTANT.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã hủy bỏ Order hiện tại.", + BlockingTypes = [BlockingType.NONE.ToString()], + }; + + public readonly static AgvAction LiftUp = new() + { + ActionType = ActionType.liftUp.ToString(), + ActionDescription = "Nâng cao bàn nâng của robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã nâng cao bàn nâng.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction LiftDown = new() + { + ActionType = ActionType.liftDown.ToString(), + ActionDescription = "Hạ thấp bàn nâng của robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã hạ thấp bàn nâng.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction LiftRotate = new() + { + ActionType = ActionType.liftRotate.ToString(), + ActionDescription = "Xoay bàn nâng của robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [ + new() + { + Key = "angle", + Description = "Góc xoay của bàn nâng. (rad)", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }], + ResultDescription = "Robot đã xoay bàn nâng.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction Rotate = new() + { + ActionType = ActionType.rotate.ToString(), + ActionDescription = "Xoay robot tại chỗ.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [ + new() + { + Key = "angle", + Description = "Góc xoay của robot. (rad)", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }], + ResultDescription = "Robot đã xoay tại chỗ.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction RotateKeepLift = new() + { + ActionType = ActionType.rotateKeepLift.ToString(), + ActionDescription = "Xoay robot tại chỗ giữ nguyên trạng thái bàn nâng.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [ + new() + { + Key = "angle", + Description = "Góc xoay của robot. (rad)", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }], + ResultDescription = "Robot đã xoay tại chỗ giữ nguyên trạng thái bàn nâng.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction MutedBaseOn = new() + { + ActionType = ActionType.mutedBaseOn.ToString(), + ActionDescription = "Bật chế độ muted base robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã bật chế độ muted base.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction MutedBaseOff = new() + { + ActionType = ActionType.mutedBaseOff.ToString(), + ActionDescription = "Tắt chế độ muted base robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã tắt chế độ muted base.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction MutedLoadOn = new() + { + ActionType = ActionType.mutedLoadOn.ToString(), + ActionDescription = "Bật chế độ muted load robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã bật chế độ muted load.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction MutedLoadOff = new() + { + ActionType = ActionType.mutedLoadOff.ToString(), + ActionDescription = "Tắt chế độ muted load robot.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [], + ResultDescription = "Robot đã tắt chế độ muted load.", + BlockingTypes = [BlockingType.NONE.ToString(), BlockingType.SOFT.ToString()], + }; + + public readonly static AgvAction DockTo = new() + { + ActionType = ActionType.dockTo.ToString(), + ActionDescription = "Robot di chuyển vào vị trí đặc biết", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [ + new() + { + Key = "dockId", + Description = "ID của vị trí dock.", + ValueDataType = ValueDataType.STRING.ToString(), + IsOptional = false, + }], + ResultDescription = "Robot đã dock đến vị trí sạc hoặc bến đỗ.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction MoveStraightToCoor = new() + { + ActionType = ActionType.moveStraightToCoor.ToString(), + ActionDescription = "Di chuyển thẳng đến tọa độ xác định.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [ + new() + { + Key = "x", + Description = "Tọa độ X đích đến.", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }, + new() + { + Key = "y", + Description = "Tọa độ Y đích đến.", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }], + ResultDescription = "Robot đã di chuyển thẳng đến tọa độ xác định.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; + + public readonly static AgvAction MoveStraightWithDistance = new() + { + ActionType = ActionType.moveStraightWithDistance.ToString(), + ActionDescription = "Di chuyển thẳng với khoảng cách xác định.", + ActionScopes = [ActionScopes.INSTANT.ToString(), ActionScopes.NODE.ToString()], + ActionParameters = [ + new() + { + Key = "distance", + Description = "Khoảng cách di chuyển. (m)", + ValueDataType = ValueDataType.FLOAT.ToString(), + IsOptional = false, + }, + new() + { + Key = "direction", + Description = "Hướng di chuyển: 1 - tiến, -1 - lùi.", + ValueDataType = ValueDataType.INTEGER.ToString(), + IsOptional = false, + }, + 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.ToString(), + IsOptional = true, + }], + ResultDescription = "Robot đã di chuyển thẳng với khoảng cách xác định.", + BlockingTypes = [BlockingType.HARD.ToString()], + }; } diff --git a/RobotApp/Services/Robot/RobotLocalization.cs b/RobotApp/Services/Robot/RobotLocalization.cs index 7d5c30f..2926cd0 100644 --- a/RobotApp/Services/Robot/RobotLocalization.cs +++ b/RobotApp/Services/Robot/RobotLocalization.cs @@ -1,83 +1,438 @@ -using RobotApp.Common.Shares; +using Google.Protobuf.WellKnownTypes; +using Grpc.Core; +using Grpc.Net.Client; +using RobotApp.Common.Shares; using RobotApp.Interfaces; using RobotApp.Services.Robot.Simulation; +using System; +using Xloc; +using static Xloc.XlocDiagnostics.Types; namespace RobotApp.Services.Robot; -public class Xloc +public class XlocData { + public UInt32 Sequence { get; set; } + public Timestamp Stamp { get; set; } = new(); + public string FrameId { get; set; } = ""; public double X { get; set; } public double Y { get; set; } public double Theta { get; set; } - public string SlamState { get; set; } = string.Empty; + public SlamState SlamState { get; set; } public string SlamStateDetail { get; set; } = string.Empty; public string CurrentActiveMap { get; set; } = string.Empty; public double Reliability { get; set; } public double MatchingScore { get; set; } - public bool IsReady { get; set; } + public bool IsReady { get; set; } = false; } -public class RobotLocalization(RobotConfiguration RobotConfiguration, SimulationVisualization SimVisualization) : ILocalization +public class RobotLocalization(RobotConfiguration RobotConfiguration, SimulationVisualization SimVisualization, Logger Logger) : BackgroundService, ILocalization { - public double X => IsSimulation ? SimVisualization.X : Xloc.X; + public UInt32 Sequence => IsSimulation ? 0 : XlocData.Sequence; + public Timestamp Stamp => IsSimulation ? new() : XlocData.Stamp; + public string FrameId => IsSimulation ? "" : XlocData.FrameId; + public double X => IsSimulation ? SimVisualization.X : XlocData.X; + public double Y => IsSimulation ? SimVisualization.Y : XlocData.Y; + public double Theta => IsSimulation ? SimVisualization.Theta * Math.PI / 180 : XlocData.Theta; + public string SlamState => IsSimulation ? "Localization" : XlocData.SlamState.ToString(); + public string SlamStateDetail => IsSimulation ? "" : XlocData.SlamStateDetail; + public string CurrentActiveMap => IsSimulation ? "" : XlocData.CurrentActiveMap; + public double Reliability => IsSimulation ? 100 : XlocData.Reliability; + public double MatchingScore => IsSimulation ? 100 : XlocData.MatchingScore; + public bool IsReady => IsSimulation || XlocData.IsReady; - public double Y => IsSimulation ? SimVisualization.Y : Xloc.Y; - - public double Theta => IsSimulation ? SimVisualization.Theta * Math.PI / 180 : Xloc.Theta; - - public string SlamState => IsSimulation ? "Localization" : Xloc.SlamState; - - public string SlamStateDetail => IsSimulation ? "" : Xloc.SlamStateDetail; - - public string CurrentActiveMap => IsSimulation ? "" : Xloc.CurrentActiveMap; - - public double Reliability => IsSimulation ? 100 : Xloc.Reliability; - - public double MatchingScore => IsSimulation ? 100 : Xloc.MatchingScore; - - public bool IsReady => IsSimulation || Xloc.IsReady; - - - private readonly Xloc Xloc = new(); + private readonly XlocData XlocData = new(); private readonly bool IsSimulation = RobotConfiguration.IsSimulation; + private readonly XlocServices.XlocServicesClient XlocClient = new(GrpcChannel.ForAddress(RobotConfiguration.XlocAddress)); + private WatchTimerAsync? ReaderTimer; + private const int ReaderInterval = 50; + private int TimerCounter = 0; + + private AsyncServerStreamingCall? XlocPose; + private AsyncServerStreamingCall? XlocDiagnostics; + public MessageResult ActivateMap(string mapName) { - throw new NotImplementedException(); + try + { + var response = XlocClient.ActivateMap(new ActivateMapRequest() + { + MapFileName = mapName + }); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Kích hoạt bản đồ '{mapName}' thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, $"Kích hoạt bản đồ '{mapName}' thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Kích hoạt bản đồ '{mapName}' thất bại: {ex.Message}"); + return new(false, $"Kích hoạt bản đồ '{mapName}' thất bại"); + } } - public MessageResult ChangeMapOrigin(double x, double y, double theta) + public MessageResult SwitchMap(string mapName, bool useInitialPose, double x, double y, double theta) { - throw new NotImplementedException(); + try + { + var xyzw = QuaternionToXYZW(0, 0, theta); + var response = XlocClient.SwitchMap(new SwitchMapRequest() + { + MapFileName = mapName, + UseInitialPose = useInitialPose, + InitialPose = new Pose() + { + Position = new Point() + { + X = x, + Y = y, + Z = 0, + }, + Orientation = new Quaternion() + { + X = xyzw.x, + Y = xyzw.y, + Z = xyzw.z, + W = xyzw.z + } + } + }); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Chuyển đổi bản đồ '{mapName}' thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, $"Chuyển đổi bản đồ '{mapName}' thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Chuyển đổi bản đồ '{mapName}' thất bại: {ex.Message}"); + return new(false, $"Chuyển đổi bản đồ '{mapName}' thất bại"); + } } - public MessageResult SetInitializePosition(double x, double y, double theta) + public MessageResult StartMapping() { - throw new NotImplementedException(); - } - - public MessageResult StartLocalization() - { - throw new NotImplementedException(); - } - - public MessageResult StartMapping(double resolution) - { - throw new NotImplementedException(); - } - - public MessageResult StopLocalization() - { - throw new NotImplementedException(); + try + { + var response = XlocClient.StartMapping(new()); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Bắt đầu quá trình Mapping thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Bắt đầu quá trình Mapping thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Bắt đầu quá trình Mapping thất bại: {ex.Message}"); + return new(false, $"Bắt đầu quá trình Mapping thất bại"); + } } public MessageResult StopMapping(string mapName) { - throw new NotImplementedException(); + try + { + var response = XlocClient.StopMapping(new StopMappingRequest() + { + SaveMapFilename = mapName, + }); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Kết thúc quá trình Mapping với tên '{mapName}' thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, $"Kết thúc quá trình Mapping với tên '{mapName}' thất bại."); + } + } + catch (Exception ex) + { + Logger.Warning($"Kết thúc quá trình Mapping với tên '{mapName}' thất bại: {ex.Message}"); + return new(false, $"Kết thúc quá trình Mapping với tên '{mapName}' thất bại."); + } } - public MessageResult SwitchMap(string mapName, double x, double y, double theta) + public MessageResult StartLocalization() { - throw new NotImplementedException(); + try + { + var response = XlocClient.StartLocalization(new()); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Bắt đầu quá trình Localization thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Bắt đầu quá trình Localization thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Bắt đầu quá trình Localization thất bại: {ex.Message}"); + return new(false, $"Bắt đầu quá trình Localization thất bại"); + } + } + + public MessageResult StopLocalization() + { + try + { + var response = XlocClient.StopLocalization(new()); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Kết thúc quá trình Localization thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Kết thúc quá trình Localization thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Kết thúc quá trình Localization thất bại: {ex.Message}"); + return new(false, $"Kết thúc quá trình Localization thất bại"); + } + } + + public MessageResult ChangeMapOrigin(double x, double y, double theta) + { + try + { + var xyzw = QuaternionToXYZW(0, 0, theta); + var response = XlocClient.ChangeMapOrigin(new ChangeMapOriginRequest() + { + NewMapOrigin = new Pose() + { + Position = new Point() + { + X = x, + Y = y, + Z = 0, + }, + Orientation = new Quaternion() + { + X = xyzw.x, + Y = xyzw.y, + Z = xyzw.z, + W = xyzw.w + } + } + }); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Thay đổi gốc bản đồ thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Thay đổi gốc bản đồ thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Thay đổi gốc bản đồ thất bại: {ex.Message}"); + return new(false, $"Thay đổi gốc bản đồ thất bại"); + } + } + + public MessageResult StartUpdateMap() + { + try + { + var response = XlocClient.StartUpdateMap(new()); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Bắt đầu quá trình cập nhật bản đồ thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Bắt đầu quá trình cập nhật bản đồ thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Bắt đầu quá trình cập nhật bản đồ thất bại: {ex.Message}"); + return new(false, $"Bắt đầu quá trình cập nhật bản đồ thất bại"); + } + } + + public MessageResult StopUpdateMap(bool save) + { + try + { + var response = XlocClient.StopUpdateMap(new StopUpdateMapRequest() + { + SaveUpdatedMap = save, + }); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Kết thúc quá trình cập nhật bản đồ thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Kết thúc quá trình cập nhật bản đồ thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Kết thúc quá trình cập nhật bản đồ thất bại: {ex.Message}"); + return new(false, $"Kết thúc quá trình cập nhật bản đồ thất bại"); + } + } + + public MessageResult SetInitializePosition(double x, double y, double theta) + { + try + { + var xyzw = QuaternionToXYZW(0, 0, theta); + var response = XlocClient.SetInitialPose(new SetInitialPoseRequest() + { + InitialPose = new Pose() + { + Position = new Point() + { + X = x, + Y = y, + Z = 0, + }, + Orientation = new Quaternion() + { + X = xyzw.x, + Y = xyzw.y, + Z = xyzw.z, + W = xyzw.w + } + } + }); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning("Khởi tạo vị trí cho robot thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Khởi tạo vị trí cho robot thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Khởi tạo vị trí cho robot thất bại: {ex.Message}"); + return new(false, $"Khởi tạo vị trí cho robot thất bại"); + } + } + + public MessageResult ResetSlamError() + { + try + { + var response = XlocClient.ResetSlamError(new()); + if (response.Status.Code == StatusResponse.Types.StatusCode.Ok) return new(true); + else + { + Logger.Warning($"Xóa bỏ lỗi Slam thất bại. Kết quả trả về: {response.Status.Code} - {response.Status.Message}"); + return new(false, "Xóa bỏ lỗi Slam thất bại"); + } + } + catch (Exception ex) + { + Logger.Warning($"Xóa bỏ lỗi Slam thất bại: {ex.Message}"); + return new(false, $"Xóa bỏ lỗi Slam thất bại"); + } + } + + private static (double roll, double pitch, double yaw) QuaternionToRPY(double x, double y, double z, double w) + { + double sinr_cosp = 2.0 * (w * x + y * z); + double cosr_cosp = 1.0 - 2.0 * (x * x + y * y); + double roll = Math.Atan2(sinr_cosp, cosr_cosp); + + double sinp = 2.0 * (w * y - z * x); + double pitch; + if (sinp >= 1.0) pitch = Math.PI / 2.0; + else if (sinp <= -1.0) pitch = -Math.PI / 2.0; + else pitch = Math.Asin(sinp); + + double siny_cosp = 2.0 * (w * z + x * y); + double cosy_cosp = 1.0 - 2.0 * (y * y + z * z); + double yaw = Math.Atan2(siny_cosp, cosy_cosp); + return (roll, pitch, yaw); + } + + private static (double x, double y, double z, double w) QuaternionToXYZW(double roll, double pitch, double yaw) + { + double cy = Math.Cos(yaw * 0.5); + double sy = Math.Sin(yaw * 0.5); + double cp = Math.Cos(pitch * 0.5); + double sp = Math.Sin(pitch * 0.5); + double cr = Math.Cos(roll * 0.5); + double sr = Math.Sin(roll * 0.5); + + double w = cr * cp * cy + sr * sp * sy; + double x = sr * cp * cy - cr * sp * sy; + double y = cr * sp * cy + sr * cp * sy; + double z = cr * cp * sy - sr * sp * cy; + return (x, y, z, w); + } + + private async Task XlocReader() + { + try + { + if (XlocPose is null || !await XlocPose.ResponseStream.MoveNext()) + { + XlocPose = XlocClient.GetPose(new()); + return; + } + + XlocData.Sequence = XlocPose.ResponseStream.Current.Header.Seq; + XlocData.Stamp = XlocPose.ResponseStream.Current.Header.Stamp; + XlocData.FrameId = XlocPose.ResponseStream.Current.Header.FrameId; + XlocData.X = XlocPose.ResponseStream.Current.Pose.Position.X; + XlocData.Y = XlocPose.ResponseStream.Current.Pose.Position.Y; + XlocData.Theta = QuaternionToRPY(XlocPose.ResponseStream.Current.Pose.Orientation.X, + XlocPose.ResponseStream.Current.Pose.Orientation.Y, + XlocPose.ResponseStream.Current.Pose.Orientation.Z, + XlocPose.ResponseStream.Current.Pose.Orientation.W).yaw; + + if (TimerCounter++ >= 10) + { + if (XlocDiagnostics is null || !await XlocDiagnostics.ResponseStream.MoveNext()) + { + XlocDiagnostics = XlocClient.GetDiagnostics(new()); + return; + } + + XlocData.SlamState = XlocDiagnostics.ResponseStream.Current.SlamState; + XlocData.SlamStateDetail = XlocDiagnostics.ResponseStream.Current.SlamStateDetail; + XlocData.CurrentActiveMap = XlocDiagnostics.ResponseStream.Current.CurrentActiveMap; + XlocData.Reliability = XlocDiagnostics.ResponseStream.Current.Reliability; + XlocData.MatchingScore = XlocDiagnostics.ResponseStream.Current.MatchingScore; + } + } + catch (RpcException rpcEx) + { + XlocData.IsReady = false; + Logger.Warning($"Lỗi giao tiếp rpc với module Xloc: {rpcEx.Message}"); + await Task.Delay(1000); + } + catch (Exception ex) + { + XlocData.IsReady = false; + Logger.Warning($"Lỗi giao tiếp với module Xloc: {ex.Message}"); + await Task.Delay(1000); + } + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + if (IsSimulation) return; + + while (!stoppingToken.IsCancellationRequested) + { + XlocPose = XlocClient.GetPose(new(), cancellationToken: stoppingToken); + XlocDiagnostics = XlocClient.GetDiagnostics(new(), cancellationToken: stoppingToken); + if (await XlocPose.ResponseStream.MoveNext(stoppingToken) && await XlocPose.ResponseStream.MoveNext(stoppingToken)) break; + await Task.Delay(2000); + } + + ReaderTimer = new(ReaderInterval, XlocReader, Logger); + ReaderTimer.Start(); + + XlocData.IsReady = true; + } + + public override Task StopAsync(CancellationToken cancellationToken) + { + ReaderTimer?.Dispose(); + return base.StopAsync(cancellationToken); } } diff --git a/RobotApp/Services/Robot/RobotOrderController.cs b/RobotApp/Services/Robot/RobotOrderController.cs index 97d595b..7861839 100644 --- a/RobotApp/Services/Robot/RobotOrderController.cs +++ b/RobotApp/Services/Robot/RobotOrderController.cs @@ -154,6 +154,16 @@ public class RobotOrderController(INavigation NavigationManager, IInstantActions EdgeStates = []; } + public void PauseOrder() + { + throw new NotImplementedException(); + } + + public void ResumeOrder() + { + throw new NotImplementedException(); + } + private void HandleNavigationStart() { OrderTimer = new(CycleHandlerMilliseconds, OrderHandler, Logger); diff --git a/RobotApp/Services/RobotExtensions.cs b/RobotApp/Services/RobotExtensions.cs index c9d435f..2203a8a 100644 --- a/RobotApp/Services/RobotExtensions.cs +++ b/RobotApp/Services/RobotExtensions.cs @@ -18,12 +18,13 @@ public static class RobotExtensions services.AddInterfaceServiceSingleton(); services.AddInterfaceServiceSingleton(); services.AddInterfaceServiceSingleton(); - services.AddInterfaceServiceSingleton(); - services.AddInterfaceServiceSingleton(); + services.AddInterfaceServiceSingleton(); services.AddInterfaceServiceSingleton(); - services.AddHostedInterfaceServiceSingleton(); services.AddInterfaceServiceSingleton(); + services.AddHostedInterfaceServiceSingleton(); + services.AddHostedInterfaceServiceSingleton(); + services.AddHostedServiceSingleton(); services.AddHostedServiceSingleton(); return services; diff --git a/RobotApp/Services/State/RobotState.cs b/RobotApp/Services/State/RobotState.cs index 5640e50..d70857d 100644 --- a/RobotApp/Services/State/RobotState.cs +++ b/RobotApp/Services/State/RobotState.cs @@ -14,7 +14,6 @@ public abstract class RobotState(Enum name, IRobotState? superState = null) : public event Action? OnEnter; public event Action? OnExit; - public event Action? OnTransition; public virtual void Enter() {