using RobotNet.MapShares.Dtos; using RobotNet.RobotManager.Services.Planner.Space; using RobotNet.RobotManager.Services.Simulation.Algorithm; using RobotNet.RobotManager.Services.Simulation.Models; using RobotNet.RobotShares.Enums; using RobotNet.Shares; namespace RobotNet.RobotManager.Services.Simulation; public class DifferentialNavigationService(VisualizationService visualization, RobotSimulationModel model, IServiceProvider ServiceProvider) : NavigationService(visualization, model, ServiceProvider) { public override MessageResult Rotate(double angle) { RotatePID = new PID().WithKp(10).WithKi(0.01).WithKd(0.1); TargetAngle = MathExtension.CheckLimit(angle, 180, -180); Action = NavigationAction.Rotate; NavStart(); CheckingStart(); return new(true); } public override MessageResult MoveStraight(NavigationNode[] path) { if (path.Length < 2) return new(false, "Đường dẫn không hợp lệ"); NavigationPath = [.. path]; CheckingNodes = [NavigationPath[0], NavigationPath[^1]]; InNode = CheckingNodes[0]; MovePID = new PID().WithKp(1).WithKi(0.0001).WithKd(0.6); MoveFuzzy = new FuzzyLogic().WithGainP(1.1); MovePurePursuit = new PurePursuit().WithLookheadDistance(0.35).WithPath(path); double Angle = Math.Atan2(path[1].Y - path[0].Y, path[1].X - path[0].X); Rotate(Angle * 180 / Math.PI); UpdateGoal(NavigationPath[^1]); FinalGoal = NavigationPath[^1]; return new(true); } public override MessageResult Move(NavigationNode[] path) { if (path.Length < 2) return new(false, "Đường dẫn không hợp lệ"); NavigationPath = [.. path]; CheckingNodes = [.. NavigationPath.Where(n => n.Actions == "CheckingNode")]; if (CheckingNodes.Count < 2) return new(false, "Đường dẫn traffic không hợp lệ"); InNode = CheckingNodes[0]; MovePID = new PID().WithKp(1).WithKi(0.0001).WithKd(0.6); MoveFuzzy = new FuzzyLogic().WithGainP(1.1); MovePurePursuit = new PurePursuit().WithLookheadDistance(0.35).WithPath([.. NavigationPath]); double Angle = Math.Atan2(NavigationPath[1].Y - NavigationPath[0].Y, NavigationPath[1].X - NavigationPath[0].X); Rotate(Angle * 180 / Math.PI); return new(true); } public override MessageResult Move(NodeDto[] nodes, EdgeDto[] edges) { if (nodes.Length < 2) return new(false, "Đường dẫn không hợp lệ"); var pathSplit = PathPlanner.PathSplit(nodes, edges); if (!pathSplit.IsSuccess) return new(false, pathSplit.Message); if (pathSplit.Data is null) return new(false, "Đường dẫn không hợp lệ"); NavigationPath = [..pathSplit.Data.Select(n => new NavigationNode() { Id = n.Id, X = n.X, Y = n.Y, Theta = n.Theta, Direction = MapCompute.GetRobotDirection(n.Direction), Actions = n.Actions, })]; CheckingNodes = [.. NavigationPath.Where(n => n.Actions == "CheckingNode")]; InNode = CheckingNodes[0]; FinalGoal = CheckingNodes[^1]; MovePID = new PID().WithKp(1).WithKi(0.0001).WithKd(0.6); MoveFuzzy = new FuzzyLogic().WithGainP(1.1); MovePurePursuit = new PurePursuit().WithLookheadDistance(0.35).WithPath([.. NavigationPath]); double angleFoward = Math.Atan2(NavigationPath[1].Y - NavigationPath[0].Y, NavigationPath[1].X - NavigationPath[0].X) * 180 / Math.PI; double angleBacward = Math.Atan2(NavigationPath[0].Y - NavigationPath[1].Y, NavigationPath[0].X - NavigationPath[1].X) * 180 / Math.PI; Rotate(CheckingNodes[0].Direction == RobotDirection.FORWARD ? angleFoward : angleBacward); return new(true); } protected override void NavigationHandler() { try { if (Action == NavigationAction.Rotate) { if (RotatePID is not null) { NavigationState = NavigationStateType.Rotating; double Error = Visualization.Theta - TargetAngle; if (Error > 180) Error -= 360; else if (Error < -180) Error += 360; if (Math.Abs(Error) < 1) { if (NavigationPath is not null) Action = NavigationAction.Move; else Dispose(); } else { var SpeedCal = RotatePID.PID_step(Error * MathExtension.ToRad, AngularVelocity, -AngularVelocity, SampleTimeMilliseconds / 1000.0); VelocityController.SetSpeed(SpeedCal, SpeedCal); } } } else if (Action == NavigationAction.Move) { if (NavigationPath is not null && NavigationGoal is not null) { if (MovePID is not null && MoveFuzzy is not null && MovePurePursuit is not null && FinalGoal is not null) { var DistanceToGoal = Math.Sqrt(Math.Pow(Visualization.X - FinalGoal.X, 2) + Math.Pow(Visualization.Y - FinalGoal.Y, 2)); var DistanceToCheckingNode = Math.Sqrt(Math.Pow(Visualization.X - NavigationGoal.X, 2) + Math.Pow(Visualization.Y - NavigationGoal.Y, 2)); var deviation = NavigationGoal.Id == FinalGoal.Id ? 0.02 : 0.1; if (DistanceToCheckingNode > deviation) { NavigationState = NavigationStateType.Moving; double SpeedTarget = MovePID.PID_step(DistanceToCheckingNode, RobotModel.MaxVelocity, 0, SampleTimeMilliseconds / 1000.0); double AngularVel = MovePurePursuit.PurePursuit_step(Visualization.X, Visualization.Y, Visualization.Theta * Math.PI / 180); AngularVel *= NavigationPath[MovePurePursuit.OnNodeIndex].Direction == RobotDirection.FORWARD ? 1 : -1; (double AngularVelocityLeft, double AngularVelocityRight) = MoveFuzzy.Fuzzy_step(SpeedTarget, AngularVel, SampleTimeMilliseconds / 1000.0); //AngularVelocityLeft /= RobotModel.RadiusWheel; //AngularVelocityRight = AngularVelocityRight / RobotModel.RadiusWheel * -1; if (NavigationPath[MovePurePursuit.OnNodeIndex].Direction == RobotDirection.FORWARD) { AngularVelocityLeft /= RobotModel.RadiusWheel; AngularVelocityRight = AngularVelocityRight / RobotModel.RadiusWheel * -1; } else { AngularVelocityLeft = AngularVelocityLeft / RobotModel.RadiusWheel * -1; AngularVelocityRight /= RobotModel.RadiusWheel; } VelocityController.SetSpeed(AngularVelocityLeft, AngularVelocityRight); if (MovePurePursuit.OnNodeIndex < NavigationPath.Count) { var inNode = NavigationPath[MovePurePursuit.OnNodeIndex]; if (CheckingNodes.Any(n => n.Id == inNode.Id)) InNode = inNode; else { var inNodeIndex = CheckingNodes.IndexOf(InNode ?? new()); inNodeIndex = inNodeIndex < 0 ? 0 : inNodeIndex; for (int i = inNodeIndex; i < CheckingNodes.Count; i++) { if (Math.Sqrt(Math.Pow(CheckingNodes[i].X - inNode.X, 2) + Math.Pow(CheckingNodes[i].Y - inNode.Y, 2)) < 0.2) { InNode = CheckingNodes[i]; break; } } } } } else if (DistanceToGoal < 0.02) Dispose(); else NavigationState = NavigationStateType.Waitting; } } } } catch (Exception ex) { Logger.Warning($"{RobotModel.RobotId} Nav ex: {ex.Message}"); } } }