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.Helpers; 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) { 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 Exception(LogHelper.SpliceLogMessage($"堆垛机完成任务WCS反馈信号未清除", deviceCode, WCS_EXCEPTIONTYPE.设备异常, GetType())); if (obj.Data2.VoucherNo_1 != obj.Data.VoucherNo_1 || obj.Data2.VoucherNo_2 != obj.Data.VoucherNo_2) throw new Exception(LogHelper.SpliceLogMessage($"等待执行{obj.Data.TaskID_1}-{obj.Data.TaskID_2}", deviceCode, WCS_EXCEPTIONTYPE.设备异常, GetType())); //处理堆垛机已完成的任务 if (obj.Data2.FinishedTask_1 != 0 || obj.Data2.FinishedTask_2 != 0) { WCS_TASK task = new WCS_TASK(); DB.Do(db => { var taskIds = new List() { obj.Data2.FinishedTask_1, obj.Data2.FinishedTask_2 }.ToArray(); for (int i = 0; i < taskIds.Length; i++) { //判断当前工位是否有完成任务 if (taskIds[i] == 0) continue; //获取当前工位的目标地址 var ELine = i == 0 ? obj.Data.ELine_1.ToString() : obj.Data.ELine_2.ToString(); task = db.Default.Set().Single(v => taskIds[i] == v.ID); if (task.STATUS != TaskStatus.堆垛机执行) throw new Exception(LogHelper.SpliceLogMessage($"堆垛机已完成任务[{task.ID}]但WCS状态为[{task.STATUS}],应为[{TaskStatus.堆垛机执行}]", deviceCode, WCS_EXCEPTIONTYPE.逻辑异常, GetType())); if (task.TYPE == TaskType.入库) { task.ENDTIME = DateTime.Now; task.STATUS = TaskStatus.已完成; task.UPDATETIME = DateTime.Now; db.Default.SaveChanges(); } else if (task.TYPE == TaskType.出库) { task.STATUS = TaskStatus.堆垛机完成; task.UPDATETIME = DateTime.Now; db.Default.SaveChanges(); Uploader.Upload(db); var target = Device.Find(ELine).Create(); target.Data.Tasknum = task.ID; target.Data.Goodsstart = ELine.ToShort(); target.Data.Goodsend = task.ADDRNEXT.ToShort(); } else if (task.TYPE == TaskType.移库) { if (task.STATUS == TaskStatus.堆垛机执行) { task.STATUS = TaskStatus.已完成; task.UPDATETIME = DateTime.Now; db.Default.SaveChanges(); Uploader.Upload(db); } } else throw new Exception($"[{deviceCode}]任务类型错误"); if (i == 0) obj.Data.FinishedACK_1 = 1; else obj.Data.FinishedACK_2 = 1; task.CreateStatusLog(db, $"状态由[{TaskStatus.堆垛机执行}]变更为[{task.STATUS}]", this.GetType()); } }); return; } if (obj.Data2.Mode_1 != SCMode.远程) return; if (obj.Data2.Status_1 != SCRunStatus.空闲) return; var isTransfer = new List(); //再检查是否有等待执行的货物 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 Exception(LogHelper.SpliceLogMessage($"[{deviceCode}]有正在执行的任务:[{task.ID}]", obj.Entity.CODE, WCS_EXCEPTIONTYPE.逻辑异常, GetType())); //属于当前堆垛机未执行的移库任务 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(); }); 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("-"); 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++; } 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()); } }); #endregion 移库 } else if (LastIsOut) { #region 入库 //获取所有取货点 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 Exception($"[{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.运行状态位); //处理一工位 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 Exception(LogHelper.SpliceLogMessage($"设备有光电有任务且不在运行状态,但WCS找不到任务{dev1.Data2.Tasknum}", dev1.Entity.CODE, WCS_EXCEPTIONTYPE.设备异常, GetType())); 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 Exception(LogHelper.SpliceLogMessage($"设备有光电有任务且不在运行状态,但WCS找不到任务{dev2.Data2.Tasknum}", dev2.Entity.CODE, WCS_EXCEPTIONTYPE.设备异常, GetType())); 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); //获取当前堆垛机所有的放货点 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; //TODO:因放货设备过短且无动力,因此需要占用对应旋转台 后续考虑优化方式 if (res) { List moveDevs = null; if (v.Entity.CODE is "1473" or "1474" or "1475" or "1476") moveDevs = Device.Find("1471", "1472").Select(p => p.Create()).ToList(); else if (v.Entity.CODE is "1491" or "1492" or "1493" or "1494") moveDevs = Device.Find("1489", "1490").Select(p => p.Create()).ToList(); else if (v.Entity.CODE is "1520" or "1521" or "1522" or "1523") moveDevs = Device.Find("1518", "1519").Select(p => p.Create()).ToList(); else if (v.Entity.CODE is "1545" or "1546") moveDevs = Device.Find("1543", "1544").Select(p => p.Create()).ToList(); else if (v.Entity.CODE is "1555" or "1556") moveDevs = Device.Find("1551", "1553").Select(p => p.Create()).ToList(); //如果放货点有移动设备 旋转台的两个设备必须都以停止运行 并无任务及请求 if (moveDevs != null) { if (moveDevs.Any(p => p.Data.VoucherNo != p.Data2.VoucherNo)) res = false; else if (moveDevs.Any(p => p.Data3.Status.HasFlag(StationStatus.有货状态位))) res = false; else if (moveDevs.Any(p => p.Data2.Status.HasFlag(IstationStatus.光电状态))) res = false; else if (moveDevs.Any(p => p.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 sc = obj.Entity.CODE; DB.Do(db => { //堆垛机当前是否有正在执行的任务 if (db.Default.Set().Any(d => d.DEVICE == sc && d.STATUS == TaskStatus.堆垛机执行)) throw new Exception(LogHelper.SpliceLogMessage($"[{deviceCode}]有正在执行的出库任务", deviceCode, WCS_EXCEPTIONTYPE.逻辑异常, GetType())); //找出等待执行的出库任务 var q = db.Default.Set().Where(v => v.STATUS == TaskStatus.新建) .Where(v => v.DEVICE == sc) .Where(v => v.TYPE == TaskType.出库) .Where(v => !db.Default.Set().Any(d => d.DEVICE == sc && d.STATUS == TaskStatus.堆垛机执行)).ToList(); //同一个目标地址同时只能有4(双工位,每一节设备可以存放两个任务)个正在执行的任务 //考虑到有可能出现一次只发一个任务的情况,因此判断条件为3 //连续两次只发一个任务的情况暂时不考虑 //同时对结果进行排序,分组后取第一组任务 //TODO:暂时不确定排序后进行分组是否会完全打乱排序,先按照这种按时执行,后续有异常再更改 //TODO:暂时维考虑二楼环穿 var item = q.Where(v => db.Default.Set() .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) .Count() < 3) .OrderByDescending(v => v.Priority) .ThenBy(v => v.FLOOR == floor ? 0 : 1) .ThenBy(v => v.CREATETIME) .GroupBy(v => v.ADDRTO) .FirstOrDefault(); if (item == null) { Ltc.Log($"{deviceCode}无出库任务可执行"); return; } //获取两个个可执行任务,此时这两个任务的目标地址是一致的 var tasks = item.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]; if (obj.Data2.Mode_1 != SCMode.远程) return; if (obj.Data2.Status_1 != SCRunStatus.空闲) return; foreach (var task in tasks) { //判断当前任务为二工位还是一工位 var addrFrom = task.ADDRFROM.Split("-"); if (addrFrom[1].ToShort().OddNumberOrEven()) { InfoLog.INFO_SRMINFO($"写入堆垛机[{obj.Entity.CODE}]1工位-开始:[{obj.Data.TaskID_1}][{obj.Data.SLine_1}][{obj.Data.SCol_1}][{obj.Data.SLayer_1}][{obj.Data.ELine_1}][{obj.Data.VoucherNo_1}]"); 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 = task.SRMSTATION.ToShort(); obj.Data.ECol_1 = 0; obj.Data.ELayer_1 = 0; obj.Data.VoucherNo_1++; InfoLog.INFO_SRMINFO($"写入堆垛机[{obj.Entity.CODE}]1工位-结束:[{obj.Data.TaskID_1}][{obj.Data.SLine_1}][{obj.Data.SCol_1}][{obj.Data.SLayer_1}][{obj.Data.ELine_1}][{obj.Data.VoucherNo_1}]"); } else { InfoLog.INFO_SRMINFO($"写入堆垛机[{obj.Entity.CODE}]2工位-开始:[{obj.Data.TaskID_2}][{obj.Data.SLine_2}][{obj.Data.SCol_2}][{obj.Data.SLayer_2}][{obj.Data.ELine_2}][{obj.Data.VoucherNo_2}]"); 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 = task.SRMSTATION.ToShort(); obj.Data.ECol_2 = 0; obj.Data.ELayer_2 = 0; obj.Data.VoucherNo_2++; InfoLog.INFO_SRMINFO($"写入堆垛机[{obj.Entity.CODE}]2工位-结束:[{obj.Data.TaskID_2}][{obj.Data.SLine_2}][{obj.Data.SCol_2}][{obj.Data.SLayer_2}][{obj.Data.ELine_2}][{obj.Data.VoucherNo_2}]"); } var oldTaskSTATUS = task.STATUS; task.STARTTIME = DateTime.Now; task.UPDATETIME = DateTime.Now; task.STATUS = WCS.Entity.TaskStatus.堆垛机执行; task.DEVICE = obj.Entity.CODE; //获取到站台的下一个地址 if (Device.Find(task.SRMSTATION).ROUTES.Any(v => v.NEXT == dev1.Entity)) task.ADDRNEXT = dev1.Entity.CODE; else task.ADDRNEXT = dev2.Entity.CODE; db.Default.SaveChanges(); Uploader.Upload(db); if (addrFrom[1].ToShort().OddNumberOrEven()) { var msg = $"状态由[{oldTaskSTATUS}]变更为[{task.STATUS}][{obj.Data.SLine_1}-{obj.Data.SCol_1}-{obj.Data.SLayer_1}][{obj.Data.ELine_1}][{obj.Data.VoucherNo_1}]"; task.CreateStatusLog(db, msg, this.GetType()); } else { var 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()); } } }); #endregion 出库 } } protected override bool SelectDevice(WCS_DEVICE dev) { return dev.CODE.StartsWith("SRM"); } } }