|
|
|
|
@ -1,111 +1,134 @@
|
|
|
|
|
using Sln.Wcs.HikRoBotSdk;
|
|
|
|
|
using Sln.Wcs.HikRoBotSdk.Dto.GenAgvSchedulingTask;
|
|
|
|
|
using Sln.Wcs.HoistSdk;
|
|
|
|
|
using Sln.Wcs.HoistSdk.Dto.HoistTaskExecutor;
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 任务执行策略 - 轮询数据库中待执行的任务,根据设备类型调用对应 SDK 下发
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class TaskExecuteStrategy : ITaskExecuteStrategy
|
|
|
|
|
{
|
|
|
|
|
private readonly ILiveTaskQueueService _taskQueueService;
|
|
|
|
|
private readonly ILiveTaskDetailService _taskDetailService;
|
|
|
|
|
private readonly IHIKRoBotSdk _hikRobotSdk;
|
|
|
|
|
private readonly IHoistSdk _hoistSdk;
|
|
|
|
|
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,
|
|
|
|
|
IHIKRoBotSdk hikRobotSdk,
|
|
|
|
|
IHoistSdk hoistSdk)
|
|
|
|
|
IHikRoBotService hikRobotService,
|
|
|
|
|
IHoistApiService hoistService)
|
|
|
|
|
{
|
|
|
|
|
_taskQueueService = taskQueueService;
|
|
|
|
|
_taskDetailService = taskDetailService;
|
|
|
|
|
_hikRobotSdk = hikRobotSdk;
|
|
|
|
|
_hoistSdk = hoistSdk;
|
|
|
|
|
_hikRobotService = hikRobotService;
|
|
|
|
|
_hoistService = hoistService;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Task ExecuteAsync(CancellationToken cancellationToken = default)
|
|
|
|
|
{
|
|
|
|
|
return Task.WhenAll(
|
|
|
|
|
ProcessCategory(1, cancellationToken), // 包材
|
|
|
|
|
ProcessCategory(2, cancellationToken), // 成品
|
|
|
|
|
ProcessCategory(3, cancellationToken) // 托盘
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 执行一轮任务调度:查询所有待执行任务,按设备类型下发
|
|
|
|
|
/// 按任务类别轮询待执行任务,同一类别内多个任务并行处理
|
|
|
|
|
/// </summary>
|
|
|
|
|
public Task<int> ExecuteAsync()
|
|
|
|
|
private async Task ProcessCategory(int taskCategory, CancellationToken ct)
|
|
|
|
|
{
|
|
|
|
|
return Task.Run(() =>
|
|
|
|
|
{
|
|
|
|
|
// 1. 查询所有待执行的任务队列(task_status = 1)
|
|
|
|
|
var pendingTasks = _taskQueueService.Query(x => x.taskStatus == 1);
|
|
|
|
|
|
|
|
|
|
if (pendingTasks.Count == 0)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
int dispatchedCount = 0;
|
|
|
|
|
|
|
|
|
|
foreach (var task in pendingTasks)
|
|
|
|
|
while (!ct.IsCancellationRequested)
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
// 2. 查询任务明细
|
|
|
|
|
var details = _taskDetailService.Query(x => x.taskCode == task.taskCode);
|
|
|
|
|
var pendingTasks = _taskQueueService.Query(x =>
|
|
|
|
|
x.taskStatus == 1 && x.taskCategory == taskCategory);
|
|
|
|
|
|
|
|
|
|
if (details.Count == 0)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// 3. 取第一个未执行的明细步骤
|
|
|
|
|
var currentDetail = details
|
|
|
|
|
.Where(x => x.taskStatus == 1)
|
|
|
|
|
.OrderBy(x => x.objId)
|
|
|
|
|
.FirstOrDefault();
|
|
|
|
|
|
|
|
|
|
if (currentDetail == null)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
// 4. 根据设备类型下发任务
|
|
|
|
|
var success = DispatchByDeviceType(currentDetail);
|
|
|
|
|
|
|
|
|
|
if (success)
|
|
|
|
|
if (pendingTasks.Count > 0)
|
|
|
|
|
{
|
|
|
|
|
// 5. 更新明细状态为执行中
|
|
|
|
|
currentDetail.taskStatus = 2;
|
|
|
|
|
_taskDetailService.Update(currentDetail);
|
|
|
|
|
|
|
|
|
|
// 6. 更新队列状态为执行中
|
|
|
|
|
task.taskStatus = 2;
|
|
|
|
|
_taskQueueService.Update(task);
|
|
|
|
|
|
|
|
|
|
dispatchedCount++;
|
|
|
|
|
await Parallel.ForEachAsync(pendingTasks, ct, async (task, innerCt) =>
|
|
|
|
|
{
|
|
|
|
|
await ProcessTask(task, innerCt);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
{
|
|
|
|
|
Console.WriteLine($"任务 {task.taskCode} 下发失败: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
Console.WriteLine($"任务类别 {taskCategory} 调度异常: {ex.Message}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dispatchedCount;
|
|
|
|
|
});
|
|
|
|
|
await Task.Delay(1000, ct);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 根据设备类型分发到对应 SDK
|
|
|
|
|
/// 处理单个任务:按顺序遍历明细步骤,依次下发到对应设备
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool DispatchByDeviceType(LiveTaskDetail detail)
|
|
|
|
|
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),
|
|
|
|
|
_ => throw new NotSupportedException($"不支持的设备类型: {detail.deviceType}")
|
|
|
|
|
_ => Task.FromResult(false) // ConveyorLine 暂不处理
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 下发 AGV 任务(海康机器人 SDK)
|
|
|
|
|
/// 下发 AGV 任务,通过 HikRoBotApi(AGV 多车可并发)
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool DispatchToAgv(LiveTaskDetail detail)
|
|
|
|
|
private async Task<bool> DispatchToAgv(LiveTaskDetail detail)
|
|
|
|
|
{
|
|
|
|
|
await _agvSemaphore.WaitAsync();
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var dto = new GenAgvSchedulingTaskDto
|
|
|
|
|
{
|
|
|
|
|
@ -113,18 +136,8 @@ public class TaskExecuteStrategy : ITaskExecuteStrategy
|
|
|
|
|
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", // 入库 move
|
|
|
|
|
2 => "1", // 出库 move
|
|
|
|
|
_ => "0"
|
|
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
@ -135,35 +148,56 @@ public class TaskExecuteStrategy : ITaskExecuteStrategy
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var result = _hikRobotSdk.GenAgvSchedulingTask(dto);
|
|
|
|
|
return result.code == "0";
|
|
|
|
|
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>
|
|
|
|
|
/// 下发提升机任务(Hoist SDK)
|
|
|
|
|
/// 下发提升机任务,通过 HoistApi(每台提升机串行)
|
|
|
|
|
/// </summary>
|
|
|
|
|
private bool DispatchToHoist(LiveTaskDetail detail)
|
|
|
|
|
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 = detail.startPoint,
|
|
|
|
|
hoistCode = hoistCode,
|
|
|
|
|
taskCode = detail.taskCode,
|
|
|
|
|
startPoint = ParseIntPoint(detail.startPoint),
|
|
|
|
|
endPoint = ParseIntPoint(detail.endPoint)
|
|
|
|
|
startPoint = int.TryParse(detail.startPoint, out var s) ? s : 0,
|
|
|
|
|
endPoint = int.TryParse(detail.endPoint, out var e) ? e : 0
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
var result = _hoistSdk.HoistTaskExecutor(dto);
|
|
|
|
|
return result.code == "0";
|
|
|
|
|
var result = _hoistService.HoistTaskExecutor(dto);
|
|
|
|
|
if (result.code.Equals(HoistStatusEnum.成功))
|
|
|
|
|
{
|
|
|
|
|
detail.taskStatus = 2;
|
|
|
|
|
_taskDetailService.Update(detail);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// 将位置字符串转为整数楼层/层号
|
|
|
|
|
/// </summary>
|
|
|
|
|
private static int ParseIntPoint(string? point)
|
|
|
|
|
Console.WriteLine($"提升机 {hoistCode} 任务 {detail.taskCode} 下发失败: [{result.code}] {result.msg}");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
|
|
|
|
if (string.IsNullOrEmpty(point))
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
return int.TryParse(point, out var val) ? val : 0;
|
|
|
|
|
semaphore.Release();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|