< Summary

Line coverage
0%
Covered lines: 0
Uncovered lines: 198
Coverable lines: 198
Total lines: 315
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 70
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
File 1: .cctor()100%210%
File 2: .ctor(...)0%4260%
File 2: EuclideanDistance(...)100%210%
File 2: ManhattanDistance(...)100%210%
File 2: PathPlanningGeneratePath(...)0%2040%
File 2: PathPlanning(...)0%420200%
File 2: GenerateWaypoint(...)0%210140%
File 2: GeneratePath()0%702260%

File(s)

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/obj/Debug/net9.0/Microsoft.Extensions.Logging.Generators/Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator/LoggerMessage.g.cs

File '/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/obj/Debug/net9.0/Microsoft.Extensions.Logging.Generators/Microsoft.Extensions.Logging.Generators.LoggerMessageGenerator/LoggerMessage.g.cs' does not exist (any more).

/builds/yukaitung/lgdxrobot2-cloud/LGDXRobotCloud.API/Services/Automation/AutoTaskPathPlanner.cs

#LineLine coverage
 1using LGDXRobotCloud.API.Repositories;
 2using LGDXRobotCloud.API.Services.Navigation;
 3using LGDXRobotCloud.Data.DbContexts;
 4using LGDXRobotCloud.Data.Entities;
 5using LGDXRobotCloud.Protos;
 6using LGDXRobotCloud.Utilities.Enums;
 7using Microsoft.EntityFrameworkCore;
 8
 9namespace LGDXRobotCloud.API.Services.Automation;
 10
 11public interface IAutoTaskPathPlannerService
 12{
 13  Task<List<RobotClientsPath>> GeneratePath(AutoTask autoTask);
 14}
 15
 016public partial class AutoTaskPathPlannerService(
 017    ILogger<AutoTaskPathPlannerService> logger,
 018    IMapEditorService mapEditorService,
 019    IRobotDataRepository robotDataRepository,
 020    LgdxContext context
 021  ) : IAutoTaskPathPlannerService
 22{
 023  private readonly IMapEditorService _mapEditorService = mapEditorService ?? throw new ArgumentNullException(nameof(mapE
 024  private readonly IRobotDataRepository _robotDataRepository = robotDataRepository ?? throw new ArgumentNullException(na
 025  private readonly LgdxContext _context = context ?? throw new ArgumentNullException(nameof(context));
 26
 27  [LoggerMessage(EventId = 0, Level = LogLevel.Error, Message = "AutoTaskPathPlannerService Path planning: The task deta
 28  public partial void TheTaskDetailDoesNotHaveWaypoint();
 29
 30  [LoggerMessage(EventId = 1, Level = LogLevel.Error, Message = "AutoTaskPathPlannerService Path planning: Unable to fin
 31  public partial void TheTaskDetailDoesNotHaveWaypoint(int startWaypointId, int endWaypointId);
 32
 33  [LoggerMessage(EventId = 2, Level = LogLevel.Error, Message = "AutoTaskPathPlannerService Path planning: Robot data no
 34  public partial void RobotDataNotFoundForRobotId(Guid robotId);
 35
 36  private static double EuclideanDistance(double x1, double y1, double x2, double y2)
 037  {
 038    return Math.Sqrt(Math.Pow(x2 - x1, 2) + Math.Pow(y2 - y1, 2));
 039  }
 40
 41  private static double ManhattanDistance(double x1, double y1, double x2, double y2)
 042  {
 043    return Math.Abs(x2 - x1) + Math.Abs(y2 - y1);
 044  }
 45
 46  private static List<RobotClientsDof> PathPlanningGeneratePath(Dictionary<int, int> parentMap, int currentId, Waypoints
 047  {
 048    List<int> path = [currentId];
 049    while (parentMap.TryGetValue(currentId, out int parentId))
 050    {
 051      path.Add(parentId);
 052      currentId = parentId;
 053    }
 054    path.Reverse();
 55
 056    var pathPlanningPath = new List<RobotClientsDof>();
 057    foreach (var id in path)
 058    {
 059      var waypoint = waypointsTraffic.Waypoints[id]!;
 060      pathPlanningPath.Add(new RobotClientsDof
 061      {
 062        X = waypoint.X,
 063        Y = waypoint.Y,
 064        Rotation = waypoint.Rotation,
 065      });
 066    }
 067    return pathPlanningPath;
 068  }
 69
 70  private List<RobotClientsDof> PathPlanning(AutoTaskDetail start, AutoTaskDetail end, WaypointsTraffic waypointsTraffic
 071  {
 72    // Check waypoint
 073    if (start.WaypointId == null || end.WaypointId == null)
 074    {
 075      TheTaskDetailDoesNotHaveWaypoint();
 076      throw new Exception();
 77    }
 078    int startWaypointId = (int)start.WaypointId;
 079    List<int> openList = [startWaypointId];
 080    List<int> closedList = [];
 81
 082    var gScore = new Dictionary<int, double> { [startWaypointId] = 0 };
 083    var hScore = new Dictionary<int, double> { [startWaypointId] = ManhattanDistance(start.Waypoint!.X, start.Waypoint!.
 084    var parentMap = new Dictionary<int, int>();
 85
 086    while (openList.Count > 0)
 087    {
 088      var currentId = openList.OrderBy(w => gScore[w] + hScore[w]).First();
 089      if (currentId == end.WaypointId)
 090      {
 091        return PathPlanningGeneratePath(parentMap, currentId, waypointsTraffic);
 92      }
 93
 094      openList.Remove(currentId);
 095      closedList.Add(currentId);
 96
 097      foreach (var neighborId in waypointsTraffic.WaypointTraffics[currentId])
 098      {
 099        var neighbor = waypointsTraffic.Waypoints[neighborId];
 0100        if (closedList.Contains(neighbor.Id))
 0101        {
 0102          continue;
 103        }
 104
 0105        var current = waypointsTraffic.Waypoints[currentId];
 0106        double newGScore = gScore[current.Id] + ManhattanDistance(current.X, current.Y, neighbor.X, neighbor.Y);
 0107        if (!gScore.TryGetValue(neighbor.Id, out double currentGScore) || newGScore < currentGScore)
 0108        {
 109          // Update gScore and hScore
 0110          gScore[neighbor.Id] = newGScore;
 0111          hScore[neighbor.Id] = ManhattanDistance(neighbor.X, neighbor.Y, end.Waypoint!.X, end.Waypoint!.Y);
 112
 113          // Add the neighbor to the open list
 0114          parentMap[neighbor.Id] = current.Id;
 0115          if (!openList.Contains(neighbor.Id))
 0116          {
 0117            openList.Add(neighbor.Id);
 0118          }
 0119        }
 0120      }
 0121    }
 122
 0123    TheTaskDetailDoesNotHaveWaypoint(start.WaypointId.Value, end.WaypointId.Value);
 0124    throw new Exception("");
 0125  }
 126
 127  private static RobotClientsDof GenerateWaypoint(AutoTaskDetail taskDetail)
 0128  {
 0129    if (taskDetail.Waypoint != null)
 0130    {
 0131      var waypoint = new RobotClientsDof
 0132      { X = taskDetail.Waypoint.X, Y = taskDetail.Waypoint.Y, Rotation = taskDetail.Waypoint.Rotation };
 0133      if (taskDetail.CustomX != null)
 0134        waypoint.X = (double)taskDetail.CustomX;
 0135      if (taskDetail.CustomY != null)
 0136        waypoint.X = (double)taskDetail.CustomY;
 0137      if (taskDetail.CustomRotation != null)
 0138        waypoint.X = (double)taskDetail.CustomRotation;
 0139      return waypoint;
 140    }
 141    else
 0142    {
 0143      return new RobotClientsDof
 0144      {
 0145        X = taskDetail.CustomX != null ? (double)taskDetail.CustomX : 0,
 0146        Y = taskDetail.CustomY != null ? (double)taskDetail.CustomY : 0,
 0147        Rotation = taskDetail.CustomRotation != null ? (double)taskDetail.CustomRotation : 0
 0148      };
 149    }
 0150  }
 151
 152  public async Task<List<RobotClientsPath>> GeneratePath(AutoTask autoTask)
 0153  {
 0154    var realmId = autoTask.RealmId;
 0155    var hasWaypointsTrafficControl = _context.Realms.AsNoTracking()
 0156      .Where(r => r.Id == realmId)
 0157      .Select(r => r.HasWaypointsTrafficControl)
 0158      .FirstOrDefault();
 159
 0160    List<RobotClientsPath> paths = [];
 0161    List<AutoTaskDetail> taskDetails = [];
 0162    if (autoTask.CurrentProgressId == (int)ProgressState.PreMoving)
 0163    {
 0164      var firstTaskDetail = await _context.AutoTasksDetail.AsNoTracking()
 0165        .Where(t => t.AutoTaskId == autoTask.Id)
 0166        .Include(t => t.Waypoint)
 0167        .OrderBy(t => t.Order)
 0168        .FirstOrDefaultAsync();
 0169      if (firstTaskDetail != null)
 0170        taskDetails.Add(firstTaskDetail);
 0171    }
 0172    else if (autoTask.CurrentProgressId == (int)ProgressState.Moving)
 0173    {
 0174      taskDetails = await _context.AutoTasksDetail.AsNoTracking()
 0175        .Where(t => t.AutoTaskId == autoTask.Id)
 0176        .Include(t => t.Waypoint)
 0177        .OrderBy(t => t.Order)
 0178        .ToListAsync();
 0179    }
 0180    if (taskDetails.Count == 0)
 0181    {
 0182      return [];
 183    }
 184
 0185    if (hasWaypointsTrafficControl)
 0186    {
 187      // Has waypoints traffic control, return the waypoints by respecting the traffic control
 188      // Prepare waypoints traffic
 0189      var waypointsTraffic = await _mapEditorService.GetWaypointTrafficAsync(realmId);
 190
 191      // Find the nearest waypoint
 0192      var robotData = await _robotDataRepository.GetRobotDataAsync(realmId, (Guid)autoTask.AssignedRobotId!);
 0193      if (robotData == null)
 0194      {
 0195        RobotDataNotFoundForRobotId((Guid)autoTask.AssignedRobotId!);
 0196        throw new Exception();
 197      }
 0198      int selectedWaypointId = 0;
 0199      double selectedWaypointDistance = double.MaxValue;
 0200      foreach (var waypoint in waypointsTraffic.Waypoints)
 0201      {
 0202        var distance = EuclideanDistance(waypoint.Value.X, waypoint.Value.Y, robotData.Position.X, robotData.Position.Y)
 0203        if (distance < selectedWaypointDistance)
 0204        {
 0205          selectedWaypointId = waypoint.Key;
 0206          selectedWaypointDistance = distance;
 0207        }
 0208      }
 0209      taskDetails.Insert(0, new AutoTaskDetail
 0210      {
 0211        Id = 0,
 0212        Order = 0,
 0213        WaypointId = selectedWaypointId,
 0214        Waypoint = waypointsTraffic.Waypoints[selectedWaypointId],
 0215        AutoTaskId = autoTask.Id,
 0216        AutoTask = autoTask
 0217      });
 218
 219      // Path planning using A*
 0220      for (int i = 1; i < taskDetails.Count; i++)
 0221      {
 0222        var start = taskDetails[i - 1];
 0223        var end = taskDetails[i];
 0224        var planningPath = PathPlanning(start, end, waypointsTraffic);
 225
 226        // Use custom waypoint for end
 0227        var planningPathEnd = planningPath.Last();
 0228        if (end.CustomX != null)
 0229        {
 0230          planningPathEnd.X = (double)end.CustomX;
 0231        }
 0232        if (end.CustomY != null)
 0233        {
 0234          planningPathEnd.Y = (double)end.CustomY;
 0235        }
 0236        if (end.CustomRotation != null)
 0237        {
 0238          planningPathEnd.Rotation = (double)end.CustomRotation;
 0239        }
 0240        planningPath.RemoveAt(planningPath.Count - 1);
 0241        planningPath.Add(planningPathEnd);
 242
 0243        paths.Add(new RobotClientsPath
 0244        {
 0245          Waypoints = { planningPath }
 0246        });
 0247      }
 0248    }
 249    else
 0250    {
 251      // No waypoints traffic control, just return the waypoints
 0252      foreach (var t in taskDetails)
 0253      {
 0254        paths.Add(new RobotClientsPath
 0255        {
 0256          Waypoints = { GenerateWaypoint(t) }
 0257        });
 0258      }
 0259    }
 0260    return paths;
 0261  }
 262}