using Microsoft.EntityFrameworkCore; using RobotNet.MapShares; using RobotNet.MapShares.Dtos; using RobotNet.MapShares.Enums; using RobotNet.RobotManager.Data; using RobotNet.RobotManager.Services.Planner.Space; using RobotNet.RobotManager.Services.Robot; using RobotNet.RobotManager.Services.Simulation.Models; using RobotNet.RobotManager.Services.Traffic; using RobotNet.RobotShares.Dtos; using RobotNet.RobotShares.VDA5050.Factsheet; using RobotNet.RobotShares.VDA5050.FactsheetExtend; using RobotNet.RobotShares.VDA5050.State; using RobotNet.RobotShares.VDA5050.Visualization; using RobotNet.Shares; using Action = RobotNet.RobotShares.VDA5050.InstantAction.Action; namespace RobotNet.RobotManager.Services.Simulation; public class RobotSimulation : IRobotController, IDisposable { public string SerialNumber { get; } public bool IsOnline { get; set; } = true; public bool IsWorking => NavigationService is not null && NavigationService.IsProcessing; public string State { get; } = "Simulation"; public AutoResetEvent RobotUpdated { get; set; } = new(false); public string[] CurrentZones => NavigationService is null ? [] : [.. NavigationService.CurrentZones.Select(z => z.Name)]; public StateMsg StateMsg { get => GetState(); set { } } public VisualizationMsg VisualizationMsg { get => GetVisualization(); set { } } public FactSheetMsg FactSheetMsg { get; set; } = new(); public FactsheetExtendMsg FactsheetExtendMsg { get; set; } = new(); public VisualizationService Visualization { get; set; } public NavigationPathEdge[] BasePath => NavigationService is null ? [] : NavigationService.BasePath; public NavigationPathEdge[] FullPath => NavigationService is null ? [] : NavigationService.FullPath; public RobotOrderDto OrderState => GetOrderState(); public RobotActionDto[] ActionStates => []; private INavigationService? NavigationService; private readonly IServiceProvider ServiceProvider; private readonly LoggerController Logger; private readonly RobotSimulationModel RobotSimulationModel; private CancellationTokenSource? CancelRandom; public RobotSimulation(string robotid, RobotModel model, IServiceProvider serviceProvider) { SerialNumber = robotid; RobotSimulationModel = new RobotSimulationModel() { RobotId = robotid, Acceleration = 2, Deceleration = 1, Length = model.Length / 2, Width = model.Width, MaxAngularVelocity = 0.3, MaxVelocity = 1.5, RadiusWheel = 0.1, NavigationType = model.NavigationType, }; Visualization = new VisualizationService().WithRadiusWheel(RobotSimulationModel.RadiusWheel).WithRadiusRobot(RobotSimulationModel.Width); ServiceProvider = serviceProvider; Logger = ServiceProvider.GetRequiredService>(); } public void Initialize(double x, double y, double theta) => Visualization.LocalizationInitialize(x, y, theta); public MessageResult Rotate(double angle) { if (NavigationService is not null && NavigationService.NavigationState != NavigationStateType.Ready) return new(false, "Robot chưa sẵn sàng thực hiện nhiệm vụ"); var olderNavigationService = NavigationService; NavigationService = NavigationManager.GetNavigation(Visualization, RobotSimulationModel, ServiceProvider); if (NavigationService is not null) { NavigationService.CurrentZones = olderNavigationService is null ? [] : olderNavigationService.CurrentZones; return NavigationService.Rotate(angle); } return new(false, "Model chưa được thiết lập"); } public MessageResult MoveStraight(double x, double y) { try { var scope = ServiceProvider.CreateAsyncScope(); var PathPlanningService = scope.ServiceProvider.GetRequiredService(); var headRobotNode = new NodeDto() { Id = Guid.NewGuid(), X = Visualization.X * Math.Acos(Visualization.Theta * Math.PI / 180), Y = Visualization.Y * Math.Asin(Visualization.Theta * Math.PI / 180), }; var currentRobotNode = new NodeDto() { Id = Guid.NewGuid(), X = Visualization.X, Y = Visualization.Y, }; var goalNode = new NodeDto() { Id = Guid.NewGuid(), X = x, Y = y, }; goalNode.Theta = MapEditorHelper.GetAngle(currentRobotNode, headRobotNode, goalNode) > 90 ? Math.Atan2(currentRobotNode.Y - goalNode.Y, currentRobotNode.X - goalNode.X) : Math.Atan2(goalNode.Y - currentRobotNode.Y, goalNode.X - currentRobotNode.X); currentRobotNode.Theta = goalNode.Theta; NodeDto[] nodesplanning = [currentRobotNode, goalNode]; EdgeDto[] edgesplanning = [new() { Id= Guid.NewGuid(), DirectionAllowed = DirectionAllowed.Both, TrajectoryDegree = TrajectoryDegree.One, StartNodeId = currentRobotNode.Id, EndNodeId = goalNode.Id }]; var resultSplit = PathPlanningService.PathSplit(nodesplanning, edgesplanning); if (!resultSplit.IsSuccess) return new(false, resultSplit.Message); if (resultSplit.Data is null || resultSplit.Data.Length < 1) return new(false, "Không tìm thấy đường dẫn tới đích"); if (resultSplit.Data.Length < 1) return new(false, "Dữ liệu truyền vào không đúng"); var navigationPath = resultSplit.Data.Select(n => new NavigationNode() { Id = Guid.NewGuid(), X = n.X, Y = n.Y, Theta = n.Theta, Direction = n.Direction == Direction.FORWARD ? RobotShares.Enums.RobotDirection.FORWARD : n.Direction == Direction.BACKWARD ? RobotShares.Enums.RobotDirection.BACKWARD : RobotShares.Enums.RobotDirection.NONE, Actions = n.Actions, }).ToArray(); if (NavigationService is not null && NavigationService.NavigationState != NavigationStateType.Ready) return new(false, "Robot chưa sẵn sàng thực hiện nhiệm vụ"); var olderNavigationService = NavigationService; NavigationService = NavigationManager.GetNavigation(Visualization, RobotSimulationModel, ServiceProvider); if (NavigationService is not null) { NavigationService.CurrentZones = olderNavigationService is null ? [] : olderNavigationService.CurrentZones; var moveTask = NavigationService.MoveStraight(navigationPath); return moveTask; } return new(false, "Model chưa được thiết lập"); } catch (Exception ex) { return new(false, ex.Message); } } public Task CancelOrder() { NavigationService?.Cancel(); CancelRandom?.Cancel(); return Task.FromResult(new(true)); } public async Task MoveToNode(string goalName, IDictionary>? actions = null, double? lastAngle = null) { try { var scope = ServiceProvider.CreateAsyncScope(); var PathPlanner = scope.ServiceProvider.GetRequiredService(); var AppDb = scope.ServiceProvider.GetRequiredService(); var TrafficManager = ServiceProvider.GetRequiredService(); var robotDb = await AppDb.Robots.FirstOrDefaultAsync(r => r.RobotId == SerialNumber); if (robotDb is null) return new(false, "RobotId không tồn tại"); var robotModelDb = await AppDb.RobotModels.FirstOrDefaultAsync(r => r.Id == robotDb.ModelId); if (robotModelDb is null) return new(false, "Robot Model không tồn tại"); var path = await PathPlanner.Planning(Visualization.X, Visualization.Y, Visualization.Theta, RobotSimulationModel.NavigationType, robotDb.MapId, goalName); if (!path.IsSuccess) return new(false, path.Message); if (path.IsSuccess && path.Data.Nodes.Length < 2) { NavigationService?.CreateComledted(); return new(true, ""); } if (path.Data.Nodes is null || path.Data.Edges is null) return new(false, $"Đường dẫn tới đích {goalName} từ [{Visualization.X} - {Visualization.Y} - {Visualization.Theta}] không tồn tại"); if (NavigationService is not null && NavigationService.NavigationState != NavigationStateType.Ready) return new(false, "Robot chưa sẵn sàng thực hiện nhiệm vụ"); var olderNavigationService = NavigationService; NavigationService = NavigationManager.GetNavigation(Visualization, RobotSimulationModel, ServiceProvider); if (NavigationService is not null) { var nodes = path.Data.Nodes; var createAgent = TrafficManager.CreateAgent(robotDb.MapId, this, new() { NavigationType = robotModelDb.NavigationType, Length = robotModelDb.Length, Width = robotModelDb.Width, NavigationPointX = robotModelDb.OriginX, NavigationPointY = robotModelDb.OriginY, }, [..nodes.Select(n => new TrafficNodeDto() { Id = n.Id, Name = n.Name, X = n.X, Y = n.Y, Theta = n.Theta, Direction = MapCompute.GetRobotDirection(n.Direction), })], [..path.Data.Edges.Select(n => new TrafficEdgeDto() { Id = n.Id, StartNodeId = n.StartNodeId, EndNodeId = n.EndNodeId, TrajectoryDegree = n.TrajectoryDegree, ControlPoint1X = n.ControlPoint1X, ControlPoint1Y = n.ControlPoint1Y, ControlPoint2X = n.ControlPoint2X, ControlPoint2Y = n.ControlPoint2Y, })]); if (!createAgent.IsSuccess) { Logger.Warning($"{SerialNumber} - Không thể tạo traffic agent: {createAgent.Message}"); return new(false, $"Không thể tạo traffic agent: {createAgent.Message}"); } NavigationService.MapId = robotDb.MapId; NavigationService.CurrentZones = olderNavigationService is null ? [] : olderNavigationService.CurrentZones; var moveTask = NavigationService.Move(nodes, path.Data.Edges); return moveTask; } return new(false, "Model chưa được thiết lập"); } catch (Exception ex) { return new(false, ex.Message); } } private VisualizationMsg GetVisualization() { var scope = ServiceProvider.CreateAsyncScope(); var AppDb = scope.ServiceProvider.GetRequiredService(); var robotDb = AppDb.Robots.FirstOrDefault(r => r.RobotId == SerialNumber); return new() { SerialNumber = SerialNumber, MapId = robotDb is null ? string.Empty : robotDb.MapId.ToString(), AgvPosition = new() { X = Visualization.X, Y = Visualization.Y, Theta = Visualization.Theta, PositionInitialized = true, LocalizationScore = 100, DeviationRange = 100, }, Velocity = new() { Vx = Visualization.Vx, Vy = Visualization.Vy, Omega = Visualization.Omega, } }; } private StateMsg GetState() { return new() { SerialNumber = SerialNumber, Timestamp = DateTime.Now.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), NewBaseRequest = true, BatteryState = new() { BatteryHealth = 100, BatteryCharge = 0, BatteryVoltage = 24, Charging = false, }, ActionStates = [], Errors = [], Information = [], NodeStates = [], Loads = [], }; } public Task> InstantAction(Action action, bool waittingFeedback) { return Task.FromResult>(new(true)); } public void Log(string message, LogLevel level = LogLevel.Information) { switch (level) { case LogLevel.Trace: Logger.Trace($"{SerialNumber} - {message}"); break; case LogLevel.Error: Logger.Error($"{SerialNumber} - {message}"); break; case LogLevel.Warning: Logger.Warning($"{SerialNumber} - {message}"); break; case LogLevel.Critical: Logger.Critical($"{SerialNumber} - {message}"); break; case LogLevel.Information: Logger.Info($"{SerialNumber} - {message}"); break; case LogLevel.Debug: Logger.Debug($"{SerialNumber} - {message}"); break; default: Logger.Debug($"{SerialNumber} - {message}"); break; } } public Task MoveRandom(List nodes) { try { CancelRandom = new CancellationTokenSource(); var random = new Random(); var randomTask = Task.Run(async () => { while (!CancelRandom.IsCancellationRequested) { var index = random.Next(nodes.Count); await MoveToNode(nodes[index]); while (!CancelRandom.IsCancellationRequested) { if (!IsWorking) break; await Task.Delay(1000); } await Task.Delay(2000); } }, cancellationToken: CancelRandom.Token); } catch { } return Task.FromResult(new(true)); } public Task CancelAction() { return Task.FromResult(new(true, "")); } public RobotOrderDto GetOrderState() { return new() { OrderId = StateMsg.OrderId, IsCompleted = NavigationService is not null && NavigationService.IsCompleted, IsError = NavigationService is not null && NavigationService.IsError, IsProcessing = NavigationService is not null && NavigationService.IsProcessing, IsCanceled = NavigationService is not null && NavigationService.IsCanceled, Errors = NavigationService is not null ? NavigationService.Errors : [], }; } public void Dispose() { GC.SuppressFinalize(this); } }