NavigationService.cs 4.63 KB
using Microsoft.Extensions.Logging;
using Rcs.Application.Services;
using Rcs.Application.Services.PathFind;
using Rcs.Application.Services.PathFind.Models;

namespace Rcs.Infrastructure.PathFinding.Services;

/// <summary>
/// 导航服务(统一锁模式)
/// @author zzy
/// </summary>
public class NavigationService
{
    private readonly IAgvPathService _agvPathService;
    private readonly IUnifiedTrafficControlService _unifiedTrafficControl;
    private readonly ILogger<NavigationService> _logger;
    private readonly AStarPathFinder _pathFinder;

    public NavigationService(
        IAgvPathService agvPathService,
        IUnifiedTrafficControlService unifiedTrafficControl,
        ILoggerFactory loggerFactory,
        ILogger<NavigationService> logger)
    {
        _agvPathService = agvPathService;
        _unifiedTrafficControl = unifiedTrafficControl;
        _logger = logger;
        _pathFinder = new AStarPathFinder(loggerFactory.CreateLogger<AStarPathFinder>());
    }

    /// <summary>
    /// 核心调度循环:每 100-200ms 调用一次
    /// </summary>
    /// <param name="robotId">机器人ID</param>
    /// <param name="mapId">地图Id</param>
    /// <param name="mapCode">地图编码(用于统一锁)</param>
    /// <param name="currentEdgeId">当前边ID</param>
    /// <param name="currentNodeId">当前节点ID</param>
    /// <param name="targetNodeId">目标节点ID</param>
    /// <param name="currentTheta">当前航向角</param>
    public async Task<NavigationResult> ExecuteNavigationCycleAsync(
        Guid robotId,
        Guid mapId,
        string mapCode,
        Guid currentEdgeId,
        Guid currentNodeId,
        Guid targetNodeId,
        double currentTheta,
        double currentRadius)
    {
        // 1. 构造寻路请求
        var request = new PathRequest
        {
            RobotId = robotId,
            StartNodeId = currentNodeId,
            EndNodeId = targetNodeId,
            CurrentTheta = currentTheta,
            VehicleOrientationAngles = new List<double> { 0, Math.PI },
            ForkRadOffsets = new List<double>()
        };
        var graph = await _agvPathService.GetOrBuildGraphAsync(mapId);
        var globalContext = _agvPathService.BuildGlobalPathContext(robotId);
        // 2. 调用 A* 算法规划路径 (软约束规划)
        var pathResult = _pathFinder.FindPath(request, graph, globalContext);

        if (!pathResult.Success || pathResult.Segments.Count == 0)
        {
            return NavigationResult.Wait("无可行路径");
        }

        // 3. 获取下一步要走的边 (Next Step)
        var nextSegment = pathResult.Segments[0];

        // 获取边的Code信息(用于统一锁)
        var edgeCode = graph.Edges.TryGetValue(nextSegment.EdgeId, out var edge) ? edge.EdgeCode : string.Empty;
        var toNodeCode = graph.Nodes.TryGetValue(nextSegment.ToNodeId, out var toNode) ? toNode.NodeCode : string.Empty;

        if (string.IsNullOrEmpty(edgeCode) || string.IsNullOrEmpty(toNodeCode))
        {
            _logger.LogWarning("边或节点缺少Code信息,EdgeId={EdgeId}, ToNodeId={ToNodeId}", nextSegment.EdgeId, nextSegment.ToNodeId);
            return NavigationResult.Wait("边或节点缺少Code信息");
        }

        // 4. 向统一锁服务申请硬锁(使用 MapCode:EdgeCode)
        bool edgeLockAcquired = await _unifiedTrafficControl.TryAcquireEdgeLockAsync(mapCode, edgeCode, robotId);
        bool nodeLockAcquired = await _unifiedTrafficControl.TryAcquireNodeLockAsync(mapCode, toNodeCode, robotId);

        if (edgeLockAcquired && nodeLockAcquired)
        {
            // 5. 成功锁定,返回指令给机器人
            return NavigationResult.Move(nextSegment);
        }
        else
        {
            // 锁定失败,释放已获取的锁
            if (edgeLockAcquired)
            {
                await _unifiedTrafficControl.ReleaseEdgeLockAsync(mapCode, edgeCode, robotId);
            }
            if (nodeLockAcquired)
            {
                await _unifiedTrafficControl.ReleaseNodeLockAsync(mapCode, toNodeCode, robotId);
            }

            // 6. 锁定失败 - 等待或触发重规划
            return NavigationResult.Wait("资源竞争/物理干涉,等待中...");
        }
    }
}

// 简单的结果封装
public class NavigationResult
{
    public bool ShouldMove { get; set; }
    public PathSegment? NextSegment { get; set; }
    public string Message { get; set; }

    public static NavigationResult Move(PathSegment segment) => new() { ShouldMove = true, NextSegment = segment };
    public static NavigationResult Wait(string msg) => new() { ShouldMove = false, Message = msg };
}