using Microsoft.AspNetCore.SignalR.Client; using RobotNet.Clients; using RobotNet.RobotShares.Models; using RobotNet.Script; using RobotNet.Script.Expressions; using RobotNet.ScriptManager.Helpers; using RobotNet.Shares; namespace RobotNet.ScriptManager.Clients; public class RobotManagerHubClient(string RobotId, string hubUrl, Func> accessTokenProvider) : HubClient(new Uri(hubUrl), accessTokenProvider), IRobot { private async Task GetRobotStateModel() { var result = await Connection.InvokeAsync>(nameof(GetState), RobotId); if (!result.IsSuccess) { throw new InvalidOperationException($"Failed to get robot state: {result.Message}"); } else { if (result.Data is null) { throw new InvalidOperationException("Robot state data is null."); } return result.Data; } } private static string RobotErrorToString(IEnumerable errors) { var sb = new System.Text.StringBuilder(); foreach (var error in errors) { if (sb.Length > 0) { sb.Append(", "); } sb.Append($"{error.ErrorLevel}: {error.ErrorType} {error.ErrorDescription} - {error.ErrorHint}"); foreach (var reference in error.ErrorReferences ?? []) { sb.Append($" [{reference.ReferenceKey}: {reference.ReferenceValue}]"); } } return sb.ToString(); } public async Task GetState() => VDA5050ScriptHelper.ConvertToRobotState(await GetRobotStateModel()); public async Task GetLoads() { var stateModel = await GetRobotStateModel(); if (stateModel.Loads is null || stateModel.Loads.Length == 0) { return []; } return [.. stateModel.Loads.Select(load => new RobotLoad(load.LoadId, load.LoadType, load.LoadPosition, load.Weight))]; } public async Task Execute(RobotAction action, CancellationToken cancellationToken) { var state = await GetState(); if (!state.IsReady) { throw new InvalidOperationException($"Robot '{RobotId}' không sẵn sàng để thực hiện action [{action.ActionType}]!"); } var model = VDA5050ScriptHelper.ConvertToRobotInstantActionModel(RobotId, action); var result = await Connection.InvokeAsync>("InstantAction", model, cancellationToken); if (!result.IsSuccess) { throw new InvalidOperationException($"Failed to perform instant action: {result.Message}"); } if (string.IsNullOrEmpty(result.Data)) { throw new InvalidOperationException("Instant action ID is empty."); } bool isRunning = true; bool isSuccess = false; string message = ""; while (isRunning) { if (cancellationToken.IsCancellationRequested) { isRunning = false; message = $"Action [{action.ActionType} is canceled"; break; } var stateModel = await GetRobotStateModel(); var stateAction = stateModel.ActionStates.FirstOrDefault(a => a.ActionId == result.Data); if (stateAction is null) { isRunning = false; message = $"Action [{action.ActionType}] not found in robot state."; } else if (stateAction.IsError) { isRunning = false; message = $"Action [{action.ActionType}] failed with result: {stateAction.Action?.ResultDescription}, Error: {RobotErrorToString(stateModel.Errors)}"; } else if (stateAction.IsProcessing) { if (cancellationToken.IsCancellationRequested) { isRunning = false; message = $"Action [{action.ActionType}] wait cancel"; } else { try { await Task.Delay(50, cancellationToken); } catch (TaskCanceledException) { isRunning = false; message = $"Action [{action.ActionType}] wait cancel"; } } } else if (stateAction.IsCompleted) { isRunning = false; isSuccess = true; } else { isRunning = false; message = $"Action [{action.ActionType}] is in an unexpected state"; } } return new(isSuccess, message); } public async Task AbortMovement() { var result = await Connection.InvokeAsync("CancelOrder", RobotId); if (!result.IsSuccess) { throw new InvalidOperationException($"Failed to cancel robot operation: {result.Message}"); } } private async Task WaitOrder(string premessage, CancellationToken cancellationToken) { bool isRunning = true; bool isSuccess = false; string message = ""; while (isRunning) { if (cancellationToken.IsCancellationRequested) { message = $"{premessage} is canceled"; break; } var stateModel = await GetRobotStateModel(); if (stateModel.OrderState.IsError) { isRunning = false; message = $"{premessage} failed with error: {string.Join("\n\t", stateModel.OrderState.Errors)} \n Robot Error: {RobotErrorToString(stateModel.Errors)}"; } else if (stateModel.OrderState.IsProcessing) { if (cancellationToken.IsCancellationRequested) { isRunning = false; message = $"{premessage} is canceled"; } else { try { await Task.Delay(50, cancellationToken); } catch (TaskCanceledException) { isRunning = false; message = $"{premessage} is canceled"; } } } else if (stateModel.OrderState.IsCompleted) { isRunning = false; isSuccess = true; } else { isRunning = false; message = $"{premessage} is in an unexpected state"; } } return new(isSuccess, message); } private async Task MoveToNode(string nodeName, IDictionary> actions, double? lastAngle, CancellationToken cancellationToken) { var state = await GetState(); if (!state.IsReady) { throw new InvalidOperationException($"Robot '{RobotId}' không sẵn sàng để thực hiện di chuyển đến [{nodeName}]!"); } var model = VDA5050ScriptHelper.ConvertToRobotMoveToNodeModel(RobotId, nodeName, actions, lastAngle); var result = await Connection.InvokeAsync("MoveToNode", model, cancellationToken); if (!result.IsSuccess) { throw new InvalidOperationException($"Failed to move to node: {result.Message}"); } return await WaitOrder($"Robot move to node [{nodeName}]", cancellationToken); } public Task Move(string nodeName, IDictionary> actions, double lastAngle, CancellationToken cancellationToken) => MoveToNode(nodeName, actions, lastAngle, cancellationToken); public Task Move(string nodeName, IDictionary> actions, CancellationToken cancellationToken) => MoveToNode(nodeName, actions, null, cancellationToken); public Task Move(string nodeName, IEnumerable lastActions, double lastAngle, CancellationToken cancellationToken) => MoveToNode(nodeName, new Dictionary> { { nodeName, lastActions } }, lastAngle, cancellationToken); public Task Move(string nodeName, IEnumerable lastActions, CancellationToken cancellationToken) => MoveToNode(nodeName, new Dictionary> { { nodeName, lastActions } }, null, cancellationToken); public Task Move(string nodeName, double lastAngle, CancellationToken cancellationToken) => MoveToNode(nodeName, new Dictionary>(), lastAngle, cancellationToken); public Task Move(string nodeName, CancellationToken cancellationToken) => MoveToNode(nodeName, new Dictionary>(), null, cancellationToken); public async Task SimMoveStraight(double x, double y, CancellationToken cancellationToken) { var state = await GetState(); if (!state.IsReady) { throw new InvalidOperationException($"Robot '{RobotId}' không sẵn sàng để thực hiện di chuyển đến [{x}; {y}]!"); } var result = await Connection.InvokeAsync("MoveStraight", new RobotMoveStraightModel() { RobotId = RobotId, X = x, Y = y }, cancellationToken); if (!result.IsSuccess) { throw new InvalidOperationException($"Failed to move straight to [{x}; {y}]: {result.Message}"); } return await WaitOrder($"Robot move straight to [{x}; {y}]", cancellationToken); } public async Task SimRotate(double angle, CancellationToken cancellationToken) { var state = await GetState(); if (!state.IsReady) { throw new InvalidOperationException($"Robot '{RobotId}' không sẵn sàng để thực hiện rotate to [{angle}]!"); } var result = await Connection.InvokeAsync("Rotate", new RobotRotateModel() { RobotId = RobotId, Angle = angle }, cancellationToken); if (!result.IsSuccess) { throw new InvalidOperationException($"Failed to rotate to [{angle}]: {result.Message}"); } return await WaitOrder($"Robot rotate to [{angle}]", cancellationToken); } public async Task WaitForReady(CancellationToken cancellationToken) { var state = await GetState(); while(!state.IsReady && !cancellationToken.IsCancellationRequested) { try { await Task.Delay(50, cancellationToken); } catch (TaskCanceledException) { return false; // Operation was canceled } state = await GetState(); } if (cancellationToken.IsCancellationRequested) { return false; // Operation was canceled } return state.IsReady; } public async Task RequestACSIn(string id) { var result = await Connection.InvokeAsync(nameof(RequestACSIn), RobotId, id); return result.IsSuccess; } public async Task RequestACSOut(string id) { var result = await Connection.InvokeAsync(nameof(RequestACSOut), RobotId, id); return result.IsSuccess; } public async Task CancelOrder() { var result = await Connection.InvokeAsync(nameof(CancelOrder), RobotId); return result.IsSuccess; } public async Task CancelAction() { var result = await Connection.InvokeAsync(nameof(CancelOrder), RobotId); return result.IsSuccess; } }