using PlcSiemens.Core.Extension; using ServiceCenter.Extensions; using ServiceCenter.Logs; using ServiceCenter.SqlSugars; using SqlSugar; using System.ComponentModel; using WCS.Core; using WCS.Entity; using WCS.Entity.Protocol.BCR; using WCS.Entity.Protocol.RGV; using WCS.Entity.Protocol.Robot; using WCS.Entity.Protocol.Station; using WCS.Entity.Protocol.Truss; using WCS.WorkEngineering.Extensions; using WCS.WorkEngineering.Model.WMS; using WCS.WorkEngineering.Worlds; using DeviceFlags = WCS.WorkEngineering.Extensions.DeviceFlags; using TaskStatus = WCS.Entity.TaskStatus; using TaskType = WCS.Entity.TaskType; namespace WCS.WorkEngineering.Systems { /// /// RGV交互系统 /// [BelongTo(typeof(RgvWorld))] [Description("RGV交互系统")] public class RGVSystems : DeviceSystem> { protected override bool ParallelDo => true; /// /// 取货点设备集合 /// private readonly Dictionary>> _pickUpDevices = new(); public RGVSystems() { //获取所有的巷道集合 var rgvList = Device.All.Where(v => v.HasFlag(DeviceFlags.RGV)); foreach (var rgv in rgvList) { _pickUpDevices.Add(rgv.Code, rgv.Sources.Where(v => v.HasFlag(DeviceFlags.输送机)).Select(v => new Device(v, this.World)).ToList()); } } public override void Do(Device obj) { try { if (obj.Data.VoucherNo != obj.Data2.VoucherNo) { World.Log($"凭证号不一致,DB520:{obj.Data.VoucherNo}-DB521:{obj.Data2.VoucherNo}", LogLevelEnum.High); return; } if (obj.Data2.WorkMode != RGVWorkMode.Automatic) { World.Log(obj.Data2.WorkMode.GetDescription()); return; } //wcs任务完成确认信号未清除 if (obj.Data.RES1 == 1) { World.Log("wcs任务完成确认信号未清除"); return; } if (obj.Data2.Status.HasFlag(RGVStatus.Taskfinishi)) { switch (obj.Data2.CmdType) { case RGVCmdType.PickGoods: //单独取货任务完成,默认只有空托盘才会下发单独取货任务 World.Log($"任务处理:开始-取货完成-任务号[{obj.Data2.TaskNumber}]起始地址[{obj.Data2.StartPosition}]目标地址[{obj.Data2.DestPosition}]"); //开始申请读码信息 var bcrCode = obj.Data3.GetBCRCode(); if (bcrCode.IsNullOrWhiteSpace() || bcrCode.Contains(":")) { World.Log("扫码失败,内容为空", LogLevelEnum.Mid); return; } World.Log($"任务处理:扫码结果-{bcrCode}"); var taskNumber = 0; SqlSugarHelper.Do(_db => { var db = _db.Default; //检查库存表是否有残留库存信息 var inv = db.Queryable().OrderByDescending(x => x.AddTime).First(x => x.ContGrpBarCode == bcrCode); if (inv != null && inv.IsTorsChk) { switch (inv.InvStateCode) { case "InvEcecState_BuildUp": db.DeleteableRowLock(inv).ExecuteCommand(); //删除库存 var invInit = db.Queryable().Where(x => x.ContGrpBarCode == bcrCode).OrderByDescending(x => x.AddTime).First(); db.DeleteableRowLock(invInit).ExecuteCommand();//删除条码 var taskInfos = db.Queryable().Where(x => x.BarCode == bcrCode && x.Status == TaskStatus.NewBuild && x.BusType == "人工满托入库" || (x.BusType == "重绕满托入库" && x.Status == TaskStatus.WaitingToExecute)).ToList(); if (!taskInfos.Any()) { World.Log($"未找到托盘条码{bcrCode}对应任务,请联系智能制造组人员确认", LogLevelEnum.Mid); return; } foreach (var task in taskInfos) { //取消任务 task.Status = TaskStatus.Cancel; task.ManualRemarks = "托盘已使用,需在二楼组盘"; db.Updateable(task).ExecuteCommand(); task.AddWCS_TASK_DTL(db, obj.Entity.Code, "", "取消任务"); } break; case "InvEcecState_In": World.Log($"托盘条码{bcrCode}已有对应库存信息,请确认条码是否重复并更换!", LogLevelEnum.Mid); return; break; case "InvEcecState_OutGoing": World.Log($"【{obj.Entity.Code}】上的托盘 【{bcrCode}】存在出库中库存信息,请检查对应托盘条码是否存在未完成的出库任务!!!!!", LogLevelEnum.High); return; break; } #region 1 //删除库存 //var invNow = db.Queryable().Where(x => x.ContGrpBarCode == bcrCode).OrderByDescending(x => x.AddTime).First(); //if (invNow.IsTorsChk) //是一楼码垛入库 //{ // db.DeleteableRowLock(invNow).ExecuteCommand(); //删除库存 // var invInit = db.Queryable().Where(x => x.ContGrpBarCode == bcrCode).OrderByDescending(x => x.AddTime).First(); // db.DeleteableRowLock(invInit).ExecuteCommand();//删除条码 // var taskInfos = db.Queryable().Where(x => x.BarCode == bcrCode && x.Status == TaskStatus.NewBuild && x.BusType == "人工满托入库" // || (x.BusType == "重绕满托入库" && x.Status == TaskStatus.WaitingToExecute)).ToList(); // if (!taskInfos.Any()) // { // World.Log($"未找到托盘条码{bcrCode}对应任务,请联系智能制造组人员确认", LogLevelEnum.Mid); // return; // } // foreach (var task in taskInfos) // { // //取消任务 // task.Status = TaskStatus.Cancel; // task.ManualRemarks = "托盘已使用,需在二楼组盘"; // db.Updateable(task).ExecuteCommand(); // task.AddWCS_TASK_DTL(db, obj.Entity.Code, "", "取消任务"); // } //} //return; #endregion } else if(inv != null) { World.Log($"【{obj.Entity.Code}】上的托盘 【{bcrCode}】存在对应库存信息,请检查确认!!!", LogLevelEnum.High); return; } //桁架区域有该条码托盘 if (db.Queryable().NoLock().Any(x => x.PalleCode == bcrCode && x.Finish == false)) { World.Log($"桁架区域已有条码{bcrCode}托盘,请确认条码是否重复", LogLevelEnum.Mid); return; } if (db.Queryable().NoLock().Any(x => x.BarCode == bcrCode && x.Status < TaskStatus.Finish && x.Type == TaskType.Delivery)) { World.Log($"环形库区域已有条码{bcrCode}托盘搬运任务,请确认条码是否重复", LogLevelEnum.Mid); return; } var dev = Device.All.First(x => x.Code == obj.Data2.DestPosition.ToString()); if (dev.HasFlag(DeviceFlags.桁架码垛位)) { //开始绑定任务,并下发新的任务信息到小车 var palletizingInfo = db.Queryable().Single(x => x.Id == obj.Data2.TaskNumber); if (palletizingInfo == null) { World.Log($"未找到对应的码垛信息{obj.Data2.TaskNumber}", LogLevelEnum.Mid); return; } palletizingInfo.PalleCode = bcrCode; db.UpdateableRowLock(palletizingInfo).UpdateColumns(x => new { x.PalleCode }).ExecuteCommand(); taskNumber = palletizingInfo.Id; World.Log($"任务处理:当前任务为桁架区域补空托任务"); } else if (dev.HasFlag(DeviceFlags.环形库码垛工位)) { //开始处理对应的搬运任务信息 var task = db.Queryable().RowLock().First(x => x.Type == TaskType.Delivery && x.ID == obj.Data2.TaskNumber && x.AddrTo == obj.Data2.DestPosition.ToString()); if (task == null) { World.Log($"未找到对应的搬运任务{obj.Data2.TaskNumber}", LogLevelEnum.Mid); return; } task.BarCode = bcrCode; db.UpdateableRowLock(task).UpdateColumns(x => new { x.BarCode }).ExecuteCommand(); task.AddWCS_TASK_DTL(db, obj.Entity.Code, obj.Data2.DestPosition.ToString(), $"环形库码垛位{obj.Data2.DestPosition}搬运任务绑定条码信息{bcrCode}"); taskNumber = task.ID; World.Log($"任务处理:当前任务为环形库区域补空托任务"); } else //默认除了桁架码垛工位与环形库码垛工位外的任务就只有芯股用空托入库了 { //开始处理对应的入库任务信息 var task = db.Queryable().RowLock().First(x => x.Type == TaskType.EnterDepot && x.ID == obj.Data2.TaskNumber && x.AddrNext == obj.Data2.DestPosition.ToString()); if (task == null) { World.Log($"未找到对应的空托入库任务{obj.Data2.TaskNumber}", LogLevelEnum.Mid); return; } task.BarCode = bcrCode; db.UpdateableRowLock(task).UpdateColumns(x => new { x.BarCode }).ExecuteCommand(); task.AddWCS_TASK_DTL(db, obj.Entity.Code, obj.Data2.DestPosition.ToString(), $"空托入库任务绑定条码信息{bcrCode}"); taskNumber = task.ID; World.Log($"任务处理:当前任务为芯股用空托入库"); } }); if (taskNumber == 0) { World.Log($"取货完成处理失败", LogLevelEnum.Mid); return; } //清空目标点信息 var destDev = new Device(Device.All.FirstOrDefault(x => x.Code == obj.Data2.DestPosition.ToString())!, World); destDev.Data.TaskNumber = 0; destDev.Data.GoodsStart = 0; destDev.Data.GoodsEnd = 0; obj.Data2.TaskNumber = taskNumber; obj.Data.RES1 = 1; World.Log($"任务处理:结束-取货完成-任务号[{obj.Data2.TaskNumber}]起始地址[{obj.Data2.StartPosition}]目标地址[{obj.Data2.DestPosition}]"); break; case RGVCmdType.PutGoods: Device destPosition = null; bool isPalletizing = false; try { World.Log($"任务处理:开始-放货完成-任务号[{obj.Data2.TaskNumber}]起始地址[{obj.Data2.StartPosition}]目标地址[{obj.Data2.DestPosition}]"); WCS_TaskInfo finishiTask = null; var startPosition = Device.All.Where(x => x.Code == obj.Data2.StartPosition.ToString()) .Select(x => new Device(x, World)) .FirstOrDefault(); destPosition = Device.All.FirstOrDefault(x => x.Code == obj.Data2.DestPosition.ToString()); isPalletizing = destPosition!.HasFlag(DeviceFlags.桁架码垛位, DeviceFlags.环形库码垛工位); short countQty = 0; short shortCode = 0; SqlSugarHelper.Do(_db => { var db = _db.Default; if (isPalletizing) { if (destPosition.HasFlag(DeviceFlags.桁架码垛位)) { var palletizingInfo = db.Queryable() .First(x => x.Id == obj.Data.TaskNumber); countQty = palletizingInfo.CountQty.ToShort(); shortCode = palletizingInfo.ShortCode; World.Log($"任务处理:当前任务为桁架区域补空托任务"); } else if (destPosition.HasFlag(DeviceFlags.环形库码垛工位)) { var deliveryTask = db.Queryable().RowLock() .First(x => x.ID == obj.Data.TaskNumber); countQty = deliveryTask.FullQty; shortCode = deliveryTask.PalletType; deliveryTask.Status = TaskStatus.RgvCompleted; deliveryTask.EditTime = DateTime.Now; deliveryTask.LastInteractionPoint = obj.Entity.Code; db.Updateable(deliveryTask).UpdateColumns(x => new { x.Status, x.EditTime, x.LastInteractionPoint }).ExecuteCommand(); deliveryTask.AddWCS_TASK_DTL(db, deliveryTask.AddrTo, $"RGV任务执行结束"); World.Log($"任务处理:当前任务为环形库区域补空托任务"); } } else { var taskInfo = db.Queryable().RowLock().First(x => x.ID == obj.Data.TaskNumber); taskInfo.Status = TaskStatus.RgvCompleted; taskInfo.EditTime = DateTime.Now; taskInfo.LastInteractionPoint = obj.Entity.Code; db.Updateable(taskInfo).UpdateColumns(x => new { x.Status, x.EditTime, x.LastInteractionPoint }).ExecuteCommand(); taskInfo.AddWCS_TASK_DTL(db, taskInfo.AddrTo, $"RGV任务执行结束"); World.Log($"任务处理:当前任务为芯股用空托入库"); } }); if (startPosition.Data.TaskNumber == obj.Data.TaskNumber) //初始化起始点信息 { startPosition.Data.TaskNumber = 0; startPosition.Data.GoodsEnd = 0; World.Log($"任务处理:初始化取货点{startPosition.Entity.Code}任务及目标地址信息"); } //目标地址是码垛工位 if (isPalletizing) { if (destPosition.HasFlag(DeviceFlags.桁架码垛位)) { var dest = new Device(destPosition!, World); if (dest.Data2.MaxQuantity == 0) { dest.Data.MaxQuantity = countQty; dest.Data.Quantity = 0; dest.Data.Type = shortCode; dest.Data.VoucherNo++; World.Log($"任务处理:写入码垛信息-码垛位[{dest.Entity.Code}]最大码垛数量[{dest.Data.MaxQuantity}]已码数量[{dest.Data.Quantity}]垛形[{dest.Data.Type}]凭证号[{dest.Data.VoucherNo}]"); } else { World.Log($"桁架垛型信息未清除,无法写入新垛型信息请检查确认,最大码垛数量[{dest.Data.MaxQuantity}]已码数量[{dest.Data.Quantity}]垛形[{dest.Data.Type}]凭证号[{dest.Data.VoucherNo}]", LogLevelEnum.Mid); return; } } else if (destPosition.HasFlag(DeviceFlags.环形库码垛工位)) { var dest = new Device(destPosition!, World); dest.Data.MaxQuantity = countQty; dest.Data.Type = shortCode; //dest.Data.SetVoucherNoForRobot(); dest.Data.VoucherNo++; World.Log($"任务处理:写入码垛信息-码垛位[{dest.Entity.Code}]最大码垛数量[{dest.Data.MaxQuantity}]垛形[{dest.Data.Type}]凭证号[{dest.Data.VoucherNo}]"); } } } catch (Exception e) { World.Log($"处理小车放货完成是出现错误:{e.Message}-{e.StackTrace}"); return; } obj.Data.RES1 = 1; World.Log($"任务处理:结束-放货完成-任务号[{obj.Data2.TaskNumber}]起始地址[{obj.Data2.StartPosition}]目标地址[{obj.Data2.DestPosition}]"); break; case RGVCmdType.Move: World.Log($"任务处理:开始-移动完成-任务号[{obj.Data2.TaskNumber}]目标地址[{obj.Data2.DestPosition}]"); obj.Data.RES1 = 1; World.Log($"任务处理:结束-移动完成-任务号[{obj.Data2.TaskNumber}]目标地址[{obj.Data2.DestPosition}]"); break; case RGVCmdType.ChangePutGoods: break; case RGVCmdType.ChangePickGoods: break; case RGVCmdType.PickPutGoods: World.Log($"任务处理:开始-取放货完成-任务号[{obj.Data2.TaskNumber}]起始地址[{obj.Data2.StartPosition}]目标地址[{obj.Data2.DestPosition}]"); var statDev = Device.All.FirstOrDefault(x => x.Code == obj.Data.StartPosition.ToString()); var stDev = new Device(Device.All.FirstOrDefault(x => x.Code == obj.Data.StartPosition.ToString())!, World); stDev.Data.TaskNumber = 0; stDev.Data.GoodsStart = 0; stDev.Data.GoodsEnd = 0; World.Log($"任务处理:清除目标地址信息-目标货位{stDev.Entity.Code}"); obj.Data.RES1 = 1; World.Log($"任务处理:结束-取放货完成-任务号[{obj.Data2.TaskNumber}]起始地址[{obj.Data2.StartPosition}]目标地址[{obj.Data2.DestPosition}]"); break; default: throw new ArgumentOutOfRangeException(); } return; } if (obj.Data2.SystemStatus != RGVSystemStatus.空闲) { World.Log(obj.Data2.SystemStatus.GetDescription()); return; } if (obj.Data2.CmdType == RGVCmdType.PickGoods && !obj.Data2.Status.HasFlag(RGVStatus.Taskfinishi)) { if (obj.Data2.Status.HasFlag(RGVStatus.PH_Status)) { World.Log($"任务处理:开始-下发放货任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]起始地址[{obj.Data.StartPosition}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); obj.Data.TaskNumber = obj.Data.TaskNumber; obj.Data.CmdType = RGVCmdType.PutGoods; obj.Data.StartPosition = obj.Data2.StartPosition; obj.Data.DestPosition = obj.Data2.DestPosition; obj.Data.VoucherNo++; World.Log($"任务处理:结束-下发放货任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]起始地址[{obj.Data.StartPosition}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); return; } } var pickUpDevices = _pickUpDevices.FirstOrDefault(x => x.Key == obj.Entity.Code).Value; //有货且需要搬运货物的站台 var devs = pickUpDevices.Where(v => v.Data3.Status.HasFlag(StationStatus.PH_Status) && v.Data.TaskNumber != 0) .Where(v => v.Entity.Code.ToShort() != v.Data.GoodsEnd && v.Data.GoodsEnd != 0) .ToList(); //筛选出目标站台无货的站台 var putDev = obj.Entity.Targets.Where(x => x.HasFlag(DeviceFlags.输送机)) .Select(x => new Device(x, World)) .Where(x => !x.Data3.Status.HasFlag(StationStatus.PH_Status)) .Where(x => !x.Data3.Status.HasFlag(StationStatus.OT_Status)) .Select(x => x.Entity.Code.ToShort()); //var devList = devs.OrderBy(x => x.Entity.Code).Where(x => putDev.Contains(x.Data.GoodsEnd)); var devList = devs.OrderBy(x => x.Data.TaskNumber).Where(x => putDev.Contains(x.Data.GoodsEnd)); if (!devList.Any()) { World.Log($"无可用任务"); } foreach (var dev in devList) { //区分任务是拆盘机到码垛工位,还是码垛工位到拆盘机 if (dev.Entity.HasFlag(DeviceFlags.拆盘机)) { World.Log($"任务处理:开始-下发取货任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]RGV运行状态[{obj.Data2.WorkMode.GetDescription()}]起始地址[{obj.Data.StartPosition}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); obj.Data.TaskNumber = dev.Data.TaskNumber; obj.Data.CmdType = RGVCmdType.PickGoods; obj.Data.StartPosition = dev.Entity.Code.ToShort(); obj.Data.DestPosition = dev.Data.GoodsEnd; obj.Data.VoucherNo++; World.Log($"任务处理:结束-下发取货任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]起始地址[{obj.Data.StartPosition}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); return; } //if (dev.Entity.HasFlag(DeviceFlags.二次码垛RGV取货口)) //{ // obj.Data.TaskNumber = dev.Data.TaskNumber; // obj.Data.CmdType = RGVCmdType.PickPutGoods; // obj.Data.StartPosition = dev.Entity.Code.ToShort(); // obj.Data.DestPosition = dev.Data.GoodsEnd; // obj.Data.VoucherNo++; // return; //} World.Log($"任务处理:开始-下发满托入库任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]RGV运行状态[{obj.Data2.WorkMode.GetDescription()}]起始地址[{obj.Data.StartPosition}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); //非拆盘机起始任务 //站台中的任务号 WCS_TaskInfo task = null; SqlSugarHelper.Do(_db => { var db = _db.Default; var taskInfo = db.Queryable().First(p => p.ID == dev.Data.TaskNumber && p.Status == TaskStatus.ConveyorExecution); if (taskInfo == null) { World.Log($"任务处理:未找到对应的任务{dev.Entity.Code}--{dev.Data.TaskNumber}-1"); return; } taskInfo.Status = TaskStatus.RgvExecution; taskInfo.AddrNext = obj.Entity.Code; taskInfo.EditWho = "WCS"; taskInfo.EditTime = DateTime.Now; ; db.UpdateableRowLock(taskInfo).UpdateColumns(x => new { x.Status, x.AddrNext, x.EditWho, x.EditTime }).ExecuteCommand(); taskInfo.AddWCS_TASK_DTL(db, dev.Entity.Code, obj.Entity.Code, $"任务分配至{obj.Entity.Code}"); task = taskInfo; }); if (task == null) { World.Log($"任务处理:未找到对应的任务{dev.Entity.Code}--{dev.Data.TaskNumber}-2"); return; } World.Log($"任务处理:满托入库业务流程处理完成-任务[{task.ID}]状态已变更为[{task.Status}]开始准备写入PLC"); obj.Data.TaskNumber = task.ID; obj.Data.CmdType = RGVCmdType.PickPutGoods; obj.Data.StartPosition = dev.Entity.Code.ToShort(); obj.Data.DestPosition = dev.Data.GoodsEnd; obj.Data.VoucherNo++; World.Log($"任务处理:结束-下发满托入库任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]起始地址[{obj.Data.StartPosition}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); return; } if (obj.Data2.Status.HasFlag(RGVStatus.RES1)) //离开非安全区域 { World.Log($"任务处理:开始-下发移动任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); obj.Data.TaskNumber = obj.Data.TaskNumber; obj.Data.CmdType = RGVCmdType.Move; obj.Data.DestPosition = obj.Entity.Code switch { "RGV1" => 1668, "RGV2" => 1683, "RGV3" => 1698, "RGV4" => 1713, "RGV5" => 1728, "RGV6" => 1743, _ => throw new ArgumentOutOfRangeException() }; obj.Data.VoucherNo++; World.Log($"任务处理:结束-下发移动任务-任务号[{obj.Data.TaskNumber}]任务类型[{obj.Data.CmdType}]目标地址[{obj.Data.DestPosition}]凭证号[{obj.Data.VoucherNo}]"); return; } } catch (Exception ex) { if (ex.Message == "Destination array was not long enough. Check the destination index, length, and the array's lower bounds") { World.Log($"未知异常:{ex.StackTrace}"); } else if (ex.Message == "Number was less than the array's lower bound in the first dimension") { World.Log($"未知异常:{ex.StackTrace}"); } else throw new KnownException(ex.Message, LogLevelEnum.Mid); } } public override bool Select(Device dev) { return dev.Code is "RGV1" or "RGV2" or "RGV3" or "RGV4" or "RGV5" or "RGV6"; } } }