using DBHelper; using Microsoft.EntityFrameworkCore; using System; using System.Collections.Generic; using System.Linq; using WCS.Core; using WCS.Entity; using WCS.Entity.Protocol; using WCS.Entity.Protocol.SRM; using WCS.Service.Extensions; using WCS.Service.Handlers; using WCS.Service.Log; namespace WCS.Service.Works.SRM { [WorkTitle(typeof(SRMHandler), "堆垛机")] internal class SRMWork : DeviceWork { //月台发货需要的设备 private StationDevice[] DockDevs; public SRMWork() { //只取设备组 设备过多,先写程序后填写 DockDevs = new string[] { }.Select(v => Device.Find(v)).SelectMany(v => v.DEVICEGROUP).Select(v => v.MEMBER.Create()).ToArray(); } protected override void Do(SRMDevice obj) { obj.EX(obj => { var deviceCode = obj.Entity.CODE; //先检查堆垛机是否报警 if (obj.Data3.SCAlarm != 0) { if (obj.Entity.WakeupOn(5000)) WMS.DevInfo(obj.Entity.CODE, obj.Data3.SCAlarm.ToString()); InfoLog.INFO_SRMALARM($"{obj.Entity.CODE}-{obj.Data3.SCAlarm}"); return; } if (obj.Data.FinishedACK_1 == 1 || obj.Data.FinishedACK_2 == 1) throw new WarnException($"堆垛机完成任务WCS反馈信号未清除"); if (obj.Data2.VoucherNo_1 != obj.Data.VoucherNo_1 || obj.Data2.VoucherNo_2 != obj.Data.VoucherNo_2) throw new WarnException($"等待执行{obj.Data.TaskID_1}-{obj.Data.TaskID_2}"); //处理堆垛机已完成的任务 if (obj.Data2.FinishedTask_1 != 0 || obj.Data2.FinishedTask_2 != 0) { InfoLog.INFO_SRMINFO($"开始完成任务:[{obj.Entity.CODE}]-{obj.Data2.FinishedTask_1}-{obj.Data2.FinishedTask_2}"); obj.FinishedTaskHandle(); InfoLog.INFO_SRMINFO($"完成任务处理结束:[{obj.Entity.CODE}]-{obj.Data2.FinishedTask_1}-{obj.Data2.FinishedTask_2}"); return; } if (obj.Data2.Mode_1 != SCMode.远程) return; if (obj.Data2.Status_1 != SCRunStatus.空闲) return; var isTransfer = new List(); WCS_TASK enterPriority = new WCS_TASK(), outPriority = new WCS_TASK(); //再检查是否有等待执行的货物 DB.Do(db => { var task = db.Default.Set().Where(v => v.DEVICE == obj.Entity.CODE).Where(v => v.STATUS == TaskStatus.堆垛机执行).FirstOrDefault(); if (task != null) throw new WarnException($"[{deviceCode}]有正在执行的任务:[{task.ID}]"); //属于当前堆垛机未执行的移库任务 isTransfer = db.Default.Set().AsNoTracking().Where(v => v.DEVICE == obj.Entity.CODE && v.TYPE == TaskType.移库 && v.STATUS < TaskStatus.堆垛机执行).ToList(); //判断是否存在调整优先级任务,存在初始化isTransfer值 让本次执行优先任务 if (db.Default.Set().AsNoTracking().Any(v => v.DEVICE == obj.Entity.CODE && v.TYPE != TaskType.移库 && v.STATUS < TaskStatus.堆垛机执行 && v.Priority > 0)) isTransfer = new List(); enterPriority = db.Default.Set().Where(v => v.DEVICE == obj.Entity.CODE && v.TYPE == TaskType.入库 && v.STATUS < TaskStatus.堆垛机执行) .OrderByDescending(v => v.Priority).FirstOrDefault(); outPriority = db.Default.Set().Where(v => v.DEVICE == obj.Entity.CODE && v.TYPE == TaskType.出库 && v.STATUS < TaskStatus.堆垛机执行) .OrderByDescending(v => v.Priority).FirstOrDefault(); }); //最后一个是否是出库任务 var LastIsOut = obj.Entity.Get("LastIsOut"); obj.Entity.Set("LastIsOut", !LastIsOut); if (isTransfer.Count > 0) //防止因为无当前堆垛机移库任务导致无法执行其他类型任务 { #region 移库 DB.Do(db => { //获取当前堆垛机未执行的任务的组ID var taskGroupKey = db.Default.Set().Where(v => v.DEVICE == obj.Entity.CODE && v.TYPE == TaskType.移库 && v.STATUS < TaskStatus.堆垛机执行).OrderBy(p => p.CREATETIME).FirstOrDefault().TaskGroupKey; //通过任务的组ID找到本组的所有任务 var tasks = db.Default.Set().Where(v => v.TaskGroupKey == taskGroupKey); //双工位 货架列单数为一工位取放点 货架列双数为二工位取放点 foreach (var task in tasks) { var addrFrom = task.ADDRFROM.Split("-"); var addrTo = task.ADDRTO.Split("-"); var oldTaskSTATUS = task.STATUS; task.STARTTIME = DateTime.Now; task.UPDATETIME = DateTime.Now; task.STATUS = WCS.Entity.TaskStatus.堆垛机执行; task.DEVICE = obj.Entity.CODE; db.Default.SaveChanges(); Uploader.Upload(db); task.CreateStatusLog(db, $"状态由[{oldTaskSTATUS}]变更为[{task.STATUS}]", this.GetType()); if (addrFrom[2].ToShort().OddNumberOrEven()) { obj.Data.TaskID_1 = task.ID; obj.Data.SLine_1 = addrFrom[0].ToShort(); obj.Data.SCol_1 = addrFrom[1].ToShort(); obj.Data.SLayer_1 = addrFrom[2].ToShort(); obj.Data.ELine_1 = addrTo[0].ToShort(); obj.Data.ECol_1 = addrTo[1].ToShort(); obj.Data.ELayer_1 = addrTo[2].ToShort(); obj.Data.VoucherNo_1++; } else { obj.Data.TaskID_2 = task.ID; obj.Data.SLine_2 = addrFrom[0].ToShort(); obj.Data.SCol_2 = addrFrom[1].ToShort(); obj.Data.SLayer_2 = addrFrom[2].ToShort(); obj.Data.ELine_2 = addrTo[0].ToShort(); obj.Data.ECol_2 = addrTo[1].ToShort(); obj.Data.ELayer_2 = addrTo[2].ToShort(); obj.Data.VoucherNo_2++; } } }); #endregion 移库 } else if (LastIsOut) { #region 入库 if (enterPriority != null && outPriority != null && outPriority.Priority > enterPriority.Priority) return; //获取所有取货点 var arrIn = obj.GetPickPoint() .Where(v => Device.Where(d => d.IsConv()) .Select(d => d.Device()) .Where(d => d.Data.Goodsend == v.Entity.Code()) .Any()).ToList(); if (!arrIn.Any()) return; //当前堆垛机无入库任务 //入库口设备信息 找一个有任务有光电且不在运行状态位的取货点 如果找不到代表任务还在输送途中 var st = arrIn.OrderBy(v => v.Data2.Tasknum > 0 && v.Data2.Status.HasFlag(IstationStatus.光电状态) && !v.Data3.Status.HasFlag(StationStatus.运行状态位) ? 0 : 1) .ThenBy(v => v.UpdateTime) .FirstOrDefault() ?? throw new WarnException($"[{deviceCode}]等待入库任务输送到位"); //根据上述筛选条件筛选出来的入库任务 找到对应的设备组 var item = Device.Where(v => v.DEVICEGROUP.Any(p => p.MEMBER.CODE == st.Entity.CODE)).Single(); //对数据进行排序,根据CAD图纸规划,取货点两个设备中设备号较大的一个一定对应到堆垛机的一工位货叉 //因此按照降序进行排序,可以保证数组中第一个一定是放到堆垛机一工位的数据 var devs = item.DEVICEGROUP.Select(v => v.MEMBER) .Select(v => v.Create()) .OrderByDescending(v => v.Entity.CODE) .ToArray(); //取巷道 DB.Do(db => { var dev1 = devs[0]; var dev2 = devs[1]; var dev1IsThereATask = dev1.Data2.Tasknum > 0 && dev1.Data2.Status.HasFlag(IstationStatus.光电状态) && !dev1.Data3.Status.HasFlag(StationStatus.运行状态位); var dev2IsThereATask = dev2.Data2.Tasknum > 0 && dev2.Data2.Status.HasFlag(IstationStatus.光电状态) && !dev2.Data3.Status.HasFlag(StationStatus.运行状态位); //检测任务数量与可用任务数量是否有效 var task = db.Default.Set().FirstOrDefault(v => v.STATUS < TaskStatus.堆垛机执行 && v.ID == st.Data2.Tasknum); var taskCount = db.Default.Set().Count(v => v.TaskGroupKey == task.TaskGroupKey); return taskCount switch { 1 => dev1IsThereATask || dev2IsThereATask, 2 => dev1IsThereATask && dev2IsThereATask, _ => throw new WarnException($"同组任务最多为2,当前任务组有{taskCount}"), }; //处理一工位 if (dev1IsThereATask) { var tunnel = dev1.Entity.ROUTES.First().NEXT.CODE; var task1 = db.Default.Set().FirstOrDefault(v => v.STATUS < TaskStatus.堆垛机执行 && v.ID == dev1.Data2.Tasknum); if (task1 == null) throw new WarnException($"设备有光电有任务且不在运行状态,但WCS找不到任务{dev1.Data2.Tasknum}"); var loc = WMS.GetLocalIn(task1.WMSTASK, tunnel, dev1.Entity.CODE, Entity.WareCellForkNum.货叉1); var locno = string.Format("{0}-{1}-{2}", loc.Row, loc.Colomn, loc.Layer); var oldTask = task1.STATUS; task1.UPDATETIME = DateTime.Now; task1.STATUS = TaskStatus.堆垛机执行; task1.ADDRTO = locno; task1.DEVICE = obj.Entity.CODE; task1.TUNNEL = tunnel; db.Default.SaveChanges(); Uploader.Upload(db); task1.CreateStatusLog(db, $"状态由{oldTask}变更至{task1.STATUS}", this.GetType()); obj.Data.TaskID_1 = task1.ID; obj.Data.SLine_1 = dev1.Entity.CODE.ToShort(); obj.Data.SCol_1 = 0; obj.Data.SLayer_1 = 0; obj.Data.ELine_1 = loc.Row; obj.Data.ECol_1 = loc.Colomn; obj.Data.ELayer_1 = loc.Layer; } //处理二工位 if (dev2IsThereATask) { var tunnel = dev2.Entity.ROUTES.First().NEXT.CODE; var task2 = db.Default.Set().FirstOrDefault(v => v.STATUS < TaskStatus.堆垛机执行 && v.ID == dev2.Data2.Tasknum); if (task2 == null) throw new WarnException($"设备有光电有任务且不在运行状态,但WCS找不到任务{dev2.Data2.Tasknum}"); var loc = WMS.GetLocalIn(task2.WMSTASK, tunnel, dev2.Entity.CODE, Entity.WareCellForkNum.货叉2); var locno = string.Format("{0}-{1}-{2}", loc.Row, loc.Colomn, loc.Layer); var oldTask = task2.STATUS; task2.UPDATETIME = DateTime.Now; task2.STATUS = TaskStatus.堆垛机执行; task2.ADDRTO = locno; task2.DEVICE = obj.Entity.CODE; task2.TUNNEL = tunnel; db.Default.SaveChanges(); Uploader.Upload(db); task2.CreateStatusLog(db, $"状态由{oldTask}变更至{task2.STATUS}", this.GetType()); obj.Data.TaskID_2 = task2.ID; obj.Data.SLine_2 = dev2.Entity.CODE.ToShort(); obj.Data.SCol_2 = 0; obj.Data.SLayer_2 = 0; obj.Data.ELine_2 = loc.Row; obj.Data.ECol_2 = loc.Colomn; obj.Data.ELayer_2 = loc.Layer; } if (dev1IsThereATask) obj.Data.VoucherNo_1++; if (dev2IsThereATask) obj.Data.VoucherNo_2++; }); #endregion 入库 } else { #region 出库 var floor = obj.Entity.Get("LastFloor"); floor = floor % 2 + 1; obj.Entity.Set("LastFloor", floor); if (enterPriority != null && outPriority != null && enterPriority.Priority > outPriority.Priority) return; obj.EXOutStock(obj => { obj.CheckOutTask(); //获取当前堆垛机所有的放货点 var list = obj.GetDeliveryPoint(); list = list.Where(v => { //true:满足条件 false:不满足条件 //返回结果为无货的设备 默认无货 var res = true; //放货点是否有货 if (v.Data.VoucherNo != v.Data2.VoucherNo) res = false; else if (v.Data3.Status.HasFlag(StationStatus.运行状态位)) res = false; else if (v.Data2.Status.HasFlag(IstationStatus.光电状态)) res = false; else if (v.Data2.Request == IstationRequest.堆垛机放货完成请求目标地址) res = false; else if (v.Data2.Tasknum > 10000) res = false; return res; }).ToList(); //没有可用货位 if (list.Count == 0) return; //可用设备的编号组 var empties = list.Select(v => v.Entity.CODE).ToArray(); //月台所有设备当前有的任务号 var taskidList = DockDevs.Select(v => v.Data2.Tasknum).Where(v => v > 10000).ToList(); //堆垛机设备 var srm = obj.Entity.CODE; DB.Do(db => { //堆垛机当前是否有正在执行的任务 if (db.Default.Set().Any(d => d.DEVICE == srm && d.STATUS == TaskStatus.堆垛机执行)) throw new WarnException($"[{deviceCode}]有正在执行的出库任务"); //找出等待执行的出库任务 var waitTask = db.Default.Set().Where(v => v.STATUS == TaskStatus.新建) .Where(v => v.DEVICE == srm) .Where(v => v.TYPE == TaskType.出库) .Where(v => !db.Default.Set().Any(d => d.DEVICE == srm && d.STATUS == TaskStatus.堆垛机执行)).ToList(); //同一个目标地址同时只能有两组正在执行的任务 //同时对结果进行排序,分组后取第一组任务 var outDepot = waitTask.Where(v => { var max = v.ADDRTO == "G1340" ? 4 : 3; return db.Default.Set() .AsNoTracking() .Where(d => d.TYPE == TaskType.出库) .Where(d => d.STATUS > TaskStatus.新建) .Where(d => d.STATUS < TaskStatus.已完成 || taskidList.Contains(d.ID)) .Where(d => d.ADDRTO == v.ADDRTO && d.FLOOR == v.FLOOR) .GroupBy(d => d.TaskGroupKey).Count() < max; }) .OrderByDescending(v => v.Priority) .ThenBy(v => v.FLOOR == floor ? 0 : 1) .ThenBy(v => { return db.Default.Set() .AsNoTracking() .Where(d => d.TYPE == TaskType.出库) .Where(d => d.STATUS > TaskStatus.新建) .Where(d => d.STATUS < TaskStatus.已完成 || taskidList.Contains(d.ID)) .Where(d => d.ADDRTO == v.ADDRTO && d.FLOOR == v.FLOOR) .GroupBy(d => d.TaskGroupKey).Count(); }) .ThenBy(v => v.CREATETIME) .GroupBy(v => v.ADDRTO) .FirstOrDefault(); if (outDepot == null) { Ltc.Log($"{deviceCode}无出库任务可执行"); return; } //获取两个个可执行任务,此时这两个任务的目标地址是一致的 var tasks = outDepot.Select(v => v).ToList().GetOutTask(); //根据任务的目标地址获取对应的设备组 var devs = Device.Find($"{tasks.OrderBy(v => v.ADDRTO).FirstOrDefault().ADDRTO}").Create().Items.OrderByDescending(v => v.Entity.CODE).ToArray(); var dev1 = devs[0]; var dev2 = devs[1]; for (int i = 0; i < tasks.Length; i++) { var item = tasks[i]; var task = db.Default.Set().Find(item.ID); var oldTaskSTATUS = task.STATUS; task.STARTTIME = DateTime.Now; task.UPDATETIME = DateTime.Now; task.STATUS = WCS.Entity.TaskStatus.堆垛机执行; task.DEVICE = obj.Entity.CODE; switch (tasks.Length) { case 1: task.TaskGroupKey = $"{tasks[0].ID}_0"; break; case 2: task.TaskGroupKey = $"{tasks[0].ID}_{tasks[1].ID}"; break; } var fork = obj.GetFork(item, i); //获取站台及下一个地址 task.GetSrmStationAndaddNext(fork); db.Default.SaveChanges(); Uploader.Upload(db); var msg = ""; if (fork == SrmFork.货叉1) msg = $"状态由[{oldTaskSTATUS}]变更为[{task.STATUS}][{obj.Data.SLine_1}-{obj.Data.SCol_1}-{obj.Data.SLayer_1}][{obj.Data.ELine_1}][{obj.Data.VoucherNo_1}]"; else msg = $"状态由[{oldTaskSTATUS}]变更为[{task.STATUS}][{obj.Data.SLine_2}-{obj.Data.SCol_2}-{obj.Data.SLayer_2}][{obj.Data.ELine_2}][{obj.Data.VoucherNo_2}]"; task.CreateStatusLog(db, msg, this.GetType()); item.SRMSTATION = task.SRMSTATION; if (fork == SrmFork.货叉1) // 列数较小的放一工位 { obj.WriteTask1(item); } else if (fork == SrmFork.货叉2) //列数较大的放二工位 { obj.WriteTask2(item); } } }); }); #endregion 出库 } }); } protected override bool SelectDevice(WCS_DEVICE dev) { return dev.Is(DF.SRM); } } }