# RobotNet10.GlobalPathPlanner Thư viện path planning cho hệ thống robot, cung cấp các thuật toán tìm đường tối ưu trên graph với hỗ trợ nhiều loại robot khác nhau. ## 📋 Mục lục - [Tổng quan](#tổng-quan) - [Cài đặt](#cài-đặt) - [Bắt đầu nhanh](#bắt-đầu-nhanh) - [Hướng dẫn sử dụng](#hướng-dẫn-sử-dụng) - [Các loại Path Planner](#các-loại-path-planner) - [Configuration Options](#configuration-options) - [API Reference](#api-reference) - [Ví dụ nâng cao](#ví-dụ-nâng-cao) - [Best Practices](#best-practices) - [Troubleshooting](#troubleshooting) ## 🎯 Tổng quan `RobotNet10.GlobalPathPlanner` là một thư viện .NET cung cấp các thuật toán path planning cho robot, bao gồm: - **A* Algorithm**: Thuật toán tìm đường tối ưu trên graph - **SSE A* Algorithm**: State-Space Enhanced A* cho forklift robots - **Hỗ trợ nhiều loại robot**: Differential drive, Forklift, Omni-directional drive - **Bezier Curve Support**: Hỗ trợ edges với curves bậc 1, 2, hoặc 3 - **KD-Tree Optimization**: Tối ưu hóa tìm kiếm nearest neighbor - **Cancellation & Timeout**: Hỗ trợ hủy bỏ và timeout cho operations ## 📦 Cài đặt ### Thêm Project Reference Thêm reference đến project trong file `.csproj`: ```xml ``` ### Using Statements ```csharp using RobotNet10.GlobalPathPlanner; using RobotNet10.GlobalPathPlanner.Model; ``` ## 🚀 Bắt đầu nhanh ### Ví dụ cơ bản ```csharp using RobotNet10.GlobalPathPlanner; using RobotNet10.GlobalPathPlanner.Model; // 1. Tạo factory và planner var factory = new PathPlannerFactory(); var planner = factory.CreateDifferentialPlanner(); // 2. Chuẩn bị dữ liệu map (nodes và edges) var nodes = new GlobalNode[] { new GlobalNode { Id = Guid.NewGuid(), X = 0, Y = 0, Name = "Start" }, new GlobalNode { Id = Guid.NewGuid(), X = 10, Y = 10, Name = "Goal" } }; var edges = new GlobalEdge[] { new GlobalEdge { Id = Guid.NewGuid(), StartNodeId = nodes[0].Id, EndNodeId = nodes[1].Id, Degree = 1 // Linear edge } }; // 3. Set data cho planner planner.SetData(nodes, edges); // 4. Tính toán đường đi var (pathNodes, pathEdges) = planner.PathPlanning( x: 0.0, y: 0.0, theta: 0.0, goalId: nodes[1].Id ); // 5. Sử dụng kết quả foreach (var node in pathNodes) { Console.WriteLine($"Node: {node.Name} at ({node.X}, {node.Y})"); } ``` ## 📖 Hướng dẫn sử dụng ### Workflow cơ bản 1. **Tạo Planner**: Sử dụng `PathPlannerFactory` để tạo planner phù hợp với loại robot 2. **Set Data**: Gọi `SetData()` để load map data (nodes và edges) 3. **Configure Options** (Optional): Gọi `SetOptions()` để cấu hình 4. **Path Planning**: Gọi các method path planning để tính toán đường đi 5. **Xử lý kết quả**: Sử dụng mảng nodes và edges trả về ### Tạo Path Planner ```csharp var factory = new PathPlannerFactory(); // Cho differential drive robot var differentialPlanner = factory.CreateDifferentialPlanner(); // Cho forklift robot (version 1) var forkliftPlanner = factory.CreateForkliftPlanner(); // Cho forklift robot (version 2 - enhanced) var forkliftPlannerV2 = factory.CreateForkliftPlannerV2(); // Cho omni-directional drive robot var omniPlanner = factory.CreateOmniDrivePlanner(); ``` ### Load Map Data ```csharp // Chuẩn bị nodes (waypoints) var nodes = new GlobalNode[] { new GlobalNode { Id = Guid.Parse("..."), MapId = Guid.Parse("..."), X = 0.0, Y = 0.0, Name = "Node1", Orientation = Orientation.FORWARD }, // ... thêm các nodes khác }; // Chuẩn bị edges (connections) var edges = new GlobalEdge[] { new GlobalEdge { Id = Guid.Parse("..."), MapId = Guid.Parse("..."), StartNodeId = nodes[0].Id, EndNodeId = nodes[1].Id, Degree = 2, // Bezier curve bậc 2 ControlPoint1X = 5.0, ControlPoint1Y = 5.0, ControlPoint2X = 0.0, ControlPoint2Y = 0.0 }, // ... thêm các edges khác }; // Set data cho planner planner.SetData(nodes, edges); ``` ### Path Planning Methods #### 1. Path Planning cơ bản (từ tọa độ) ```csharp var (nodes, edges) = planner.PathPlanning( x: 5.0, // Starting X coordinate y: 5.0, // Starting Y coordinate theta: 45.0, // Starting orientation (degrees) goalId: goalNodeId // Goal node ID ); ``` #### 2. Path Planning từ Node ID ```csharp var (nodes, edges) = planner.PathPlanning( startNodeId: startNodeId, // Starting node ID theta: 45.0, // Current orientation goalId: goalNodeId // Goal node ID ); ``` #### 3. Path Planning với Starting Direction ```csharp var (nodes, edges) = planner.PathPlanningWithStartDirection( x: 5.0, y: 5.0, theta: 45.0, goalId: goalNodeId, startDiretion: Orientation.FORWARD // FORWARD, BACKWARD, or NONE ); ``` #### 4. Path Planning với Final Direction ```csharp var (nodes, edges) = planner.PathPlanningWithFinalDirection( x: 5.0, y: 5.0, theta: 45.0, goalId: goalNodeId, goalDirection: Orientation.BACKWARD // FORWARD, BACKWARD, or NONE ); ``` #### 5. Path Planning với Final Angle ```csharp var (nodes, edges) = planner.PathPlanningWithAngle( x: 5.0, y: 5.0, theta: 45.0, goalId: goalNodeId, goalAngle: 90.0 // Desired final angle in degrees ); ``` ### Sử dụng Cancellation Token ```csharp using var cts = new CancellationTokenSource(); // Set timeout cts.CancelAfter(TimeSpan.FromSeconds(5)); try { var (nodes, edges) = planner.PathPlanning( x: 5.0, y: 5.0, theta: 45.0, goalId: goalNodeId, cancellationToken: cts.Token ); } catch (OperationCanceledException) { Console.WriteLine("Path planning was cancelled"); } catch (TimeoutException) { Console.WriteLine("Path planning timed out"); } ``` ## 🤖 Các loại Path Planner ### 1. DifferentialPlanner **Sử dụng cho:** - Differential drive robots (2 bánh độc lập) - Omni-directional drive robots **Đặc điểm:** - Sử dụng A* algorithm cơ bản - Tính toán orientation (FORWARD/BACKWARD) tự động - Default options phù hợp cho hầu hết các trường hợp **Ví dụ:** ```csharp var planner = factory.CreateDifferentialPlanner(); ``` ### 2. ForkliftPathPlanner **Sử dụng cho:** - Forklift robots (phiên bản 1) **Đặc điểm:** - Xử lý các ràng buộc đặc thù của forklift - Tính toán turning radius phù hợp **Ví dụ:** ```csharp var planner = factory.CreateForkliftPlanner(); ``` ### 3. ForkLiftPathPlannerV2 **Sử dụng cho:** - Forklift robots (phiên bản 2 - enhanced) **Đặc điểm:** - Sử dụng SSE A* algorithm (State-Space Enhanced A*) - Hiệu năng và chất lượng đường đi tốt hơn - Khuyến nghị sử dụng cho forklift **Ví dụ:** ```csharp var planner = factory.CreateForkliftPlannerV2(); ``` ### 4. OmniDrivePlanner **Sử dụng cho:** - Omni-directional drive robots **Lưu ý:** Hiện tại sử dụng cùng planner với DifferentialPlanner **Ví dụ:** ```csharp var planner = factory.CreateOmniDrivePlanner(); ``` ## ⚙️ Configuration Options ### PathPlannerOptions ```csharp var options = new PathPlannerOptions { // Khoảng cách tối đa đến edge để được coi là "gần" edge LimitDistanceToEdge = 1.0, // Khoảng cách tối đa đến node để được coi là "tại" node LimitDistanceToNode = 0.3, // Độ phân giải khi split path (khoảng cách giữa các điểm) ResolutionSplit = 0.1, // Timeout cho path planning operation TimeOut = TimeSpan.FromSeconds(10), // Góc (degrees) để quyết định đổi hướng (FORWARD/BACKWARD) ChangeOrientationAngle = 89.0 }; planner.SetOptions(options); ``` ### Giải thích các tham số | Tham số | Mô tả | Giá trị mặc định | Đơn vị | |---------|-------|------------------|--------| | `LimitDistanceToEdge` | Khoảng cách tối đa để được coi là gần edge | 1.0 | meters | | `LimitDistanceToNode` | Khoảng cách tối đa để được coi là tại node | 0.3 | meters | | `ResolutionSplit` | Độ phân giải khi chia nhỏ path | 0.1 | meters | | `TimeOut` | Timeout cho operation | null (no timeout) | TimeSpan | | `ChangeOrientationAngle` | Góc để quyết định đổi hướng | 89.0 | degrees | ## 📚 API Reference ### IPathPlanner Interface #### SetData ```csharp void SetData(GlobalNode[] nodes, GlobalEdge[] edges) ``` Thiết lập dữ liệu graph (nodes và edges) cho planner. **Phải gọi trước khi thực hiện path planning.** #### SetOptions ```csharp void SetOptions(PathPlannerOptions options) ``` Cấu hình options cho planner. **Optional**, nếu không gọi sẽ dùng default options. #### PathPlanning (từ tọa độ) ```csharp (GlobalNode[] Nodes, GlobalEdge[] Edges) PathPlanning( double x, double y, double theta, Guid goalId, CancellationToken? cancellationToken = null ) ``` Tính toán đường đi từ tọa độ (x, y) đến goal node. #### PathPlanning (từ Node ID) ```csharp (GlobalNode[] Nodes, GlobalEdge[] Edges) PathPlanning( Guid startNodeId, double theta, Guid goalId, CancellationToken? cancellationToken = null ) ``` Tính toán đường đi từ start node đến goal node. **Hiệu quả hơn** khi robot đã ở tại một node đã biết. #### PathPlanningWithStartDirection ```csharp (GlobalNode[] Nodes, GlobalEdge[] Edges) PathPlanningWithStartDirection( double x, double y, double theta, Guid goalId, Orientation startDiretion = Orientation.NONE, CancellationToken? cancellationToken = null ) ``` Tính toán đường đi với ràng buộc hướng bắt đầu. #### PathPlanningWithFinalDirection ```csharp (GlobalNode[] Nodes, GlobalEdge[] Edges) PathPlanningWithFinalDirection( double x, double y, double theta, Guid goalId, Orientation goalDirection = Orientation.NONE, CancellationToken? cancellationToken = null ) ``` Tính toán đường đi với ràng buộc hướng kết thúc. #### PathPlanningWithAngle ```csharp (GlobalNode[] Nodes, GlobalEdge[] Edges) PathPlanningWithAngle( double x, double y, double theta, Guid goalId, double goalAngle, CancellationToken? cancellationToken = null ) ``` Tính toán đường đi với ràng buộc góc kết thúc. ### Data Models #### GlobalNode ```csharp public class GlobalNode { public Guid Id { get; set; } // Unique identifier public Guid MapId { get; set; } // Map identifier public string? Name { get; set; } // Node name public double X { get; set; } // X coordinate public double Y { get; set; } // Y coordinate public Orientation Orientation { get; set; } // FORWARD, BACKWARD, or NONE public double DistanceTo(GlobalNode other); // Calculate distance to another node } ``` #### GlobalEdge ```csharp public record GlobalEdge { public Guid Id { get; set; } // Unique identifier public Guid MapId { get; set; } // Map identifier public Guid StartNodeId { get; set; } // Start node ID public Guid EndNodeId { get; set; } // End node ID public int Degree { get; set; } // Curve degree (1, 2, or 3) public double ControlPoint1X { get; set; } // Control point 1 X (for Bezier) public double ControlPoint1Y { get; set; } // Control point 1 Y (for Bezier) public double ControlPoint2X { get; set; } // Control point 2 X (for Bezier) public double ControlPoint2Y { get; set; } // Control point 2 Y (for Bezier) } ``` #### Orientation Enum ```csharp public enum Orientation { FORWARD, // Di chuyển tiến BACKWARD, // Di chuyển lùi NONE // Không ràng buộc } ``` ## 💡 Ví dụ nâng cao ### Ví dụ 1: Path Planning với Error Handling ```csharp try { var factory = new PathPlannerFactory(); var planner = factory.CreateDifferentialPlanner(); planner.SetData(nodes, edges); var options = new PathPlannerOptions { LimitDistanceToEdge = 1.0, LimitDistanceToNode = 0.3, ResolutionSplit = 0.1, TimeOut = TimeSpan.FromSeconds(5), ChangeOrientationAngle = 89.0 }; planner.SetOptions(options); var (pathNodes, pathEdges) = planner.PathPlanning( x: currentX, y: currentY, theta: currentTheta, goalId: goalNodeId ); Console.WriteLine($"Path found with {pathNodes.Length} nodes"); } catch (Exception ex) when (ex.Message.Contains("does not exist")) { Console.WriteLine($"Error: {ex.Message}"); // Handle case when goal node doesn't exist or no path found } catch (TimeoutException) { Console.WriteLine("Path planning timed out"); // Handle timeout } catch (OperationCanceledException) { Console.WriteLine("Path planning was cancelled"); // Handle cancellation } ``` ### Ví dụ 2: Sử dụng với Async/Await ```csharp public async Task<(GlobalNode[] Nodes, GlobalEdge[] Edges)> PlanPathAsync( IPathPlanner planner, double x, double y, double theta, Guid goalId) { using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10)); return await Task.Run(() => { return planner.PathPlanning(x, y, theta, goalId, cts.Token); }, cts.Token); } // Usage var planner = factory.CreateDifferentialPlanner(); planner.SetData(nodes, edges); var (pathNodes, pathEdges) = await PlanPathAsync( planner, currentX, currentY, currentTheta, goalNodeId ); ``` ### Ví dụ 3: Path Planning với Direction Constraints ```csharp // Robot cần bắt đầu di chuyển lùi var (nodes, edges) = planner.PathPlanningWithStartDirection( x: 5.0, y: 5.0, theta: 180.0, goalId: goalNodeId, startDiretion: Orientation.BACKWARD ); // Robot cần đến đích và quay mặt về phía trước var (nodes2, edges2) = planner.PathPlanningWithFinalDirection( x: 5.0, y: 5.0, theta: 0.0, goalId: goalNodeId, goalDirection: Orientation.FORWARD ); // Robot cần đến đích với góc cụ thể (90 độ) var (nodes3, edges3) = planner.PathPlanningWithAngle( x: 5.0, y: 5.0, theta: 0.0, goalId: goalNodeId, goalAngle: 90.0 ); ``` ### Ví dụ 4: Xử lý kết quả Path ```csharp var (pathNodes, pathEdges) = planner.PathPlanning( startNodeId: startNodeId, theta: currentTheta, goalId: goalNodeId ); // Kiểm tra kết quả if (pathNodes.Length == 0) { Console.WriteLine("No path found"); return; } // In thông tin path Console.WriteLine($"Path contains {pathNodes.Length} nodes and {pathEdges.Length} edges"); for (int i = 0; i < pathNodes.Length; i++) { var node = pathNodes[i]; Console.WriteLine($"Node {i}: {node.Name} at ({node.X:F2}, {node.Y:F2}) " + $"Orientation: {node.Orientation}"); if (i < pathEdges.Length) { var edge = pathEdges[i]; Console.WriteLine($" Edge {i}: {edge.Id} (Degree: {edge.Degree})"); } } // Tính tổng khoảng cách double totalDistance = 0; for (int i = 0; i < pathNodes.Length - 1; i++) { totalDistance += pathNodes[i].DistanceTo(pathNodes[i + 1]); } Console.WriteLine($"Total path distance: {totalDistance:F2} meters"); ``` ## ✅ Best Practices ### 1. Chọn đúng Planner cho Robot Type ```csharp // ✅ Đúng: Sử dụng ForkliftPlannerV2 cho forklift var forkliftPlanner = factory.CreateForkliftPlannerV2(); // ❌ Sai: Không dùng DifferentialPlanner cho forklift var wrongPlanner = factory.CreateDifferentialPlanner(); // Không phù hợp ``` ### 2. Luôn Set Data trước khi Planning ```csharp // ✅ Đúng planner.SetData(nodes, edges); var (nodes, edges) = planner.PathPlanning(...); // ❌ Sai: Quên set data var (nodes, edges) = planner.PathPlanning(...); // Sẽ lỗi ``` ### 3. Sử dụng Node ID khi có thể ```csharp // ✅ Tốt hơn: Sử dụng Node ID khi robot đã ở tại node var (nodes, edges) = planner.PathPlanning(startNodeId, theta, goalId); // ⚠️ Chấp nhận được: Sử dụng tọa độ khi robot không ở node var (nodes, edges) = planner.PathPlanning(x, y, theta, goalId); ``` ### 4. Cấu hình Timeout cho Operations dài ```csharp var options = new PathPlannerOptions { TimeOut = TimeSpan.FromSeconds(10) // Tránh hang }; planner.SetOptions(options); ``` ### 5. Xử lý Exceptions đúng cách ```csharp try { var (nodes, edges) = planner.PathPlanning(...); } catch (Exception ex) when (ex.Message.Contains("does not exist")) { // Handle: Goal không tồn tại hoặc không tìm thấy đường đi } catch (TimeoutException) { // Handle: Timeout } catch (OperationCanceledException) { // Handle: Cancellation } ``` ### 6. Reuse Planner Instance ```csharp // ✅ Tốt: Tạo một lần, dùng nhiều lần var planner = factory.CreateDifferentialPlanner(); planner.SetData(nodes, edges); for (int i = 0; i < 100; i++) { var (pathNodes, pathEdges) = planner.PathPlanning(...); // Process path } // ❌ Không hiệu quả: Tạo mới mỗi lần for (int i = 0; i < 100; i++) { var planner = factory.CreateDifferentialPlanner(); // Không cần thiết planner.SetData(nodes, edges); var (pathNodes, pathEdges) = planner.PathPlanning(...); } ``` ## 🔧 Troubleshooting ### Vấn đề: "Destination does not exist in the map" **Nguyên nhân:** Goal node ID không có trong danh sách nodes đã set. **Giải pháp:** ```csharp // Kiểm tra goal node có tồn tại không var goalNode = nodes.FirstOrDefault(n => n.Id == goalId); if (goalNode == null) { throw new ArgumentException($"Goal node {goalId} not found in map"); } ``` ### Vấn đề: "The path does not exist" **Nguyên nhân:** Không có đường đi từ start đến goal (graph không liên thông). **Giải pháp:** - Kiểm tra edges có kết nối start và goal không - Đảm bảo graph liên thông - Kiểm tra `LimitDistanceToEdge` và `LimitDistanceToNode` có quá nhỏ không ### Vấn đề: Timeout thường xuyên **Nguyên nhân:** Graph quá lớn hoặc phức tạp. **Giải pháp:** ```csharp // Tăng timeout var options = new PathPlannerOptions { TimeOut = TimeSpan.FromSeconds(30) // Tăng từ 10s lên 30s }; planner.SetOptions(options); ``` ### Vấn đề: Path không mượt **Nguyên nhân:** `ResolutionSplit` quá lớn. **Giải pháp:** ```csharp // Giảm ResolutionSplit để có nhiều điểm hơn var options = new PathPlannerOptions { ResolutionSplit = 0.05 // Giảm từ 0.1 xuống 0.05 }; planner.SetOptions(options); ``` ### Vấn đề: Robot không đổi hướng đúng **Nguyên nhân:** `ChangeOrientationAngle` không phù hợp. **Giải pháp:** ```csharp // Điều chỉnh góc đổi hướng var options = new PathPlannerOptions { ChangeOrientationAngle = 85.0 // Thử các giá trị khác nhau }; planner.SetOptions(options); ``` ## 📝 Lưu ý - **Thread Safety**: Planner instances không thread-safe. Mỗi thread nên có planner riêng. - **Memory**: Planner lưu toàn bộ nodes và edges trong memory. Với map lớn, cần xem xét memory usage. - **Performance**: Path planning với Node ID nhanh hơn so với tọa độ vì không cần tìm nearest node/edge. ## 🤝 Đóng góp Nếu bạn phát hiện bug hoặc có đề xuất cải thiện, vui lòng tạo issue hoặc pull request. ## 📄 License [Thêm thông tin license nếu có] --- **Version**: 1.0 **Last Updated**: 2024