|
|
|
|
|
using System.Collections.Concurrent;
|
|
|
|
|
|
using Sln.Wcs.HikRoBotApi.Domain.Dto.GenAgvSchedulingTask;
|
|
|
|
|
|
using Sln.Wcs.HikRoBotApi.Enum;
|
|
|
|
|
|
using Sln.Wcs.HikRoBotApi.Service;
|
|
|
|
|
|
using Sln.Wcs.HoistApi.Domain.Dto.HoistTaskExecutor;
|
|
|
|
|
|
using Sln.Wcs.HoistApi.Domain.Enum;
|
|
|
|
|
|
using Sln.Wcs.HoistApi.Service;
|
|
|
|
|
|
using Sln.Wcs.Model.Domain;
|
|
|
|
|
|
using Sln.Wcs.Repository.service;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Sln.Wcs.Strategy;
|
|
|
|
|
|
|
|
|
|
|
|
public class TaskExecuteStrategy : ITaskExecuteStrategy
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly ILiveTaskQueueService _taskQueueService;
|
|
|
|
|
|
private readonly ILiveTaskDetailService _taskDetailService;
|
|
|
|
|
|
private readonly IHikRoBotService _hikRobotService;
|
|
|
|
|
|
private readonly IHoistApiService _hoistService;
|
|
|
|
|
|
|
|
|
|
|
|
// AGV 多车并发,默认最大 10 个并发
|
|
|
|
|
|
private readonly SemaphoreSlim _agvSemaphore = new(10, 10);
|
|
|
|
|
|
|
|
|
|
|
|
// 提升机按设备串行,每台提升机一次只能执行一个任务
|
|
|
|
|
|
private readonly ConcurrentDictionary<string, SemaphoreSlim> _hoistSemaphores = new();
|
|
|
|
|
|
|
|
|
|
|
|
public TaskExecuteStrategy(
|
|
|
|
|
|
ILiveTaskQueueService taskQueueService,
|
|
|
|
|
|
ILiveTaskDetailService taskDetailService,
|
|
|
|
|
|
IHikRoBotService hikRobotService,
|
|
|
|
|
|
IHoistApiService hoistService)
|
|
|
|
|
|
{
|
|
|
|
|
|
_taskQueueService = taskQueueService;
|
|
|
|
|
|
_taskDetailService = taskDetailService;
|
|
|
|
|
|
_hikRobotService = hikRobotService;
|
|
|
|
|
|
_hoistService = hoistService;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Task ExecuteAsync(CancellationToken cancellationToken = default)
|
|
|
|
|
|
{
|
|
|
|
|
|
return Task.WhenAll(
|
|
|
|
|
|
ProcessCategory(1, cancellationToken), // 包材
|
|
|
|
|
|
ProcessCategory(2, cancellationToken), // 成品
|
|
|
|
|
|
ProcessCategory(3, cancellationToken) // 托盘
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 按任务类别轮询待执行任务,同一类别内多个任务并行处理
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task ProcessCategory(int taskCategory, CancellationToken ct)
|
|
|
|
|
|
{
|
|
|
|
|
|
while (!ct.IsCancellationRequested)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var pendingTasks = _taskQueueService.Query(x =>
|
|
|
|
|
|
x.taskStatus == 1 && x.taskCategory == taskCategory);
|
|
|
|
|
|
|
|
|
|
|
|
if (pendingTasks.Count > 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
await Parallel.ForEachAsync(pendingTasks, ct, async (task, innerCt) =>
|
|
|
|
|
|
{
|
|
|
|
|
|
await ProcessTask(task, innerCt);
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"任务类别 {taskCategory} 调度异常: {ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
await Task.Delay(1000, ct);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 处理单个任务:按顺序遍历明细步骤,依次下发到对应设备
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task ProcessTask(LiveTaskQueue task, CancellationToken ct)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var details = _taskDetailService.Query(x =>
|
|
|
|
|
|
x.taskCode == task.taskCode && x.taskStatus == 1)
|
|
|
|
|
|
.OrderBy(x => x.objId)
|
|
|
|
|
|
.ToList();
|
|
|
|
|
|
|
|
|
|
|
|
foreach (var detail in details)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (ct.IsCancellationRequested) return;
|
|
|
|
|
|
|
|
|
|
|
|
var success = await DispatchDetail(detail);
|
|
|
|
|
|
if (!success) break;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否还有待执行的明细,若无则更新队列状态为执行中
|
|
|
|
|
|
var remainingPending = _taskDetailService.Query(x =>
|
|
|
|
|
|
x.taskCode == task.taskCode && x.taskStatus == 1);
|
|
|
|
|
|
|
|
|
|
|
|
if (remainingPending.Count == 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
task.taskStatus = 2;
|
|
|
|
|
|
_taskQueueService.Update(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
|
|
|
|
|
Console.WriteLine($"任务 {task.taskCode} 处理异常: {ex.Message}");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 根据设备类型分发明细到对应设备 API
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private Task<bool> DispatchDetail(LiveTaskDetail detail)
|
|
|
|
|
|
{
|
|
|
|
|
|
return detail.deviceType switch
|
|
|
|
|
|
{
|
|
|
|
|
|
1 => DispatchToAgv(detail),
|
|
|
|
|
|
2 => DispatchToHoist(detail),
|
|
|
|
|
|
_ => Task.FromResult(false) // ConveyorLine 暂不处理
|
|
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 下发 AGV 任务,通过 HikRoBotApi(AGV 多车可并发)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task<bool> DispatchToAgv(LiveTaskDetail detail)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _agvSemaphore.WaitAsync();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var dto = new GenAgvSchedulingTaskDto
|
|
|
|
|
|
{
|
|
|
|
|
|
reqCode = detail.taskCode,
|
|
|
|
|
|
reqTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
|
|
|
|
|
|
clientCode = "WCS",
|
|
|
|
|
|
taskCode = detail.taskCode,
|
|
|
|
|
|
taskTyp = detail.taskType switch { 1 => "1", 2 => "2", _ => "0" },
|
|
|
|
|
|
taskMode = detail.taskType switch { 1 => "2", 2 => "1", _ => "0" },
|
|
|
|
|
|
wbCode = detail.startPoint,
|
|
|
|
|
|
ctnrCode = detail.palletBarcode,
|
|
|
|
|
|
materialLot = detail.materialBarcode,
|
|
|
|
|
|
positionCodePath = new List<Position>
|
|
|
|
|
|
{
|
|
|
|
|
|
new() { positionCode = detail.startPoint, type = "00" },
|
|
|
|
|
|
new() { positionCode = detail.endPoint, type = "00" }
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var result = _hikRobotService.GetGenAgvSchedulingTask(dto);
|
|
|
|
|
|
if (result.code.Equals(HikRoBotStatusEnum.成功))
|
|
|
|
|
|
{
|
|
|
|
|
|
detail.taskStatus = 2;
|
|
|
|
|
|
_taskDetailService.Update(detail);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine($"AGV 任务 {detail.taskCode} 下发失败: [{result.code}] {result.msg}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
_agvSemaphore.Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// 下发提升机任务,通过 HoistApi(每台提升机串行)
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
private async Task<bool> DispatchToHoist(LiveTaskDetail detail)
|
|
|
|
|
|
{
|
|
|
|
|
|
var hoistCode = detail.startPoint ?? "default";
|
|
|
|
|
|
var semaphore = _hoistSemaphores.GetOrAdd(hoistCode, _ => new SemaphoreSlim(1, 1));
|
|
|
|
|
|
|
|
|
|
|
|
await semaphore.WaitAsync();
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
var dto = new HoistTaskExeDto
|
|
|
|
|
|
{
|
|
|
|
|
|
hoistCode = hoistCode,
|
|
|
|
|
|
taskCode = detail.taskCode,
|
|
|
|
|
|
startPoint = int.TryParse(detail.startPoint, out var s) ? s : 0,
|
|
|
|
|
|
endPoint = int.TryParse(detail.endPoint, out var e) ? e : 0
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
var result = _hoistService.HoistTaskExecutor(dto);
|
|
|
|
|
|
if (result.code.Equals(HoistStatusEnum.成功))
|
|
|
|
|
|
{
|
|
|
|
|
|
detail.taskStatus = 2;
|
|
|
|
|
|
_taskDetailService.Update(detail);
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Console.WriteLine($"提升机 {hoistCode} 任务 {detail.taskCode} 下发失败: [{result.code}] {result.msg}");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
semaphore.Release();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|