|
@@ -0,0 +1,1402 @@
|
|
|
+using DbHelper;
|
|
|
+using Log;
|
|
|
+using System;
|
|
|
+using System.Collections.Concurrent;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using WCS.BaseExtensions;
|
|
|
+using WCS.Core;
|
|
|
+using WCS.Entity;
|
|
|
+using WCS.Entity.Protocol;
|
|
|
+using WCS.Entity.Protocol.RGV;
|
|
|
+using WCS.Redis;
|
|
|
+using WCS.Service.Helpers;
|
|
|
+using LogHelper = WCS.Service.Helpers.LogHelper;
|
|
|
+using TaskStatus = WCS.Entity.TaskStatus;
|
|
|
+
|
|
|
+namespace WCS.Service.Extensions
|
|
|
+{
|
|
|
+ public static class WCS_DEVICEExtension
|
|
|
+ {
|
|
|
+ private static ConcurrentDictionary<string, object> DeviceValues = new ConcurrentDictionary<string, object>();
|
|
|
+
|
|
|
+ public static void AddFlag(this WCS_DEVICE source, DF flag)
|
|
|
+ {
|
|
|
+ var df = source.Get<DF>("DeviceFlag");
|
|
|
+ df = df | flag;
|
|
|
+ source.Set("DeviceFlag", df);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool Is(this WCS_DEVICE source, DF flag)
|
|
|
+ {
|
|
|
+ var df = source.Get<DF>("DeviceFlag");
|
|
|
+ return (df & flag) == flag;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void Set<T>(this WCS_DEVICE source, string key, T value)
|
|
|
+ {
|
|
|
+ DeviceValues[source.CODE + key] = value;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static T Get<T>(this WCS_DEVICE source, string key)
|
|
|
+ {
|
|
|
+ if (!DeviceValues.ContainsKey(source.CODE + key))
|
|
|
+ return default(T);
|
|
|
+ return (T)DeviceValues[source.CODE + key];
|
|
|
+ }
|
|
|
+
|
|
|
+ public static short Code(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return short.Parse(source.CODE);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static string Tunnel(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.Get<string>("Tunnel");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static int TunnelNum(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return int.Parse(source.Tunnel().Last().ToString());
|
|
|
+ }
|
|
|
+
|
|
|
+ public static int Floor(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.Get<int>("Floor");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static WCS_DEVICE SC(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.Get<WCS_DEVICE>("SC");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static WCS_DEVICE RGV(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.Get<WCS_DEVICE>("RGV");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static LocInfo LocInfo(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.Get<LocInfo>("LocInfo");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool WakeupOn(this WCS_DEVICE source, int sec, string key)
|
|
|
+ {
|
|
|
+ var str = "WakeupOn" + key;
|
|
|
+ var last = source.Get<DateTime>(str);
|
|
|
+ if ((DateTime.Now - last).TotalMilliseconds > sec)
|
|
|
+ {
|
|
|
+ source.Set(str, DateTime.Now);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Ltc.Log("OnSleep");
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool WakeupOn(this WCS_DEVICE source, int sec)
|
|
|
+ {
|
|
|
+ return source.WakeupOn(sec, "");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static IBCR80 BCR(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ var dev = Device.Find("BCR" + source.CODE);
|
|
|
+ return dev.DEVICEPROTOCOLS.First().Data<IBCR80>();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static short PalletType(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return (short)source.Get<int>("PalletType");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static short ProductLine(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return (short)source.Get<int>("ProductLine");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static short WorkShop(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return (short)source.Get<int>("WorkShop");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取从source到endaddr的路径中的所有位置
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="source">起始位置</param>
|
|
|
+ /// <param name="endAddr">目标位置</param>
|
|
|
+ /// <param name="condition">路径筛选条件</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static List<WCS_DEVICE> GetPath(this WCS_DEVICE source, string endAddr, Func<List<WCS_DEVICE>, bool> condition = null)
|
|
|
+ {
|
|
|
+ var q = source.PATHS.Where(v => v.START == source & v.END.CODE.Contains(endAddr.ToUpper()))
|
|
|
+ .Select(v => v.PATH.Split('-').Select(v => Device.Find(v)).ToList());
|
|
|
+
|
|
|
+ if (condition != null)
|
|
|
+ q = q.Where(condition);
|
|
|
+
|
|
|
+ return q.FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取从source到endaddr的路径中的第一个位置
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="source">起始位置</param>
|
|
|
+ /// <param name="endAddr">目标位置</param>
|
|
|
+ /// <param name="condition">路径筛选条件</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static WCS_DEVICE GetPath(this WCS_DEVICE source, string endAddr)
|
|
|
+ {
|
|
|
+ var q = source.PATHS.Where(v => v.START == source && v.END.CODE.Contains(endAddr.ToUpper()))
|
|
|
+ .Select(v => v.PATH.Split('-').Select(v => Device.Find(v)).ToList());
|
|
|
+
|
|
|
+ return q.FirstOrDefault().FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ public static WCS_DEVICE GetNext(this WCS_DEVICE source, string endAddr)
|
|
|
+ {
|
|
|
+ return source.GetNext(endAddr, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取从source到endaddr的路径中的下一个位置
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="source">起始位置</param>
|
|
|
+ /// <param name="endAddr">目标位置</param>
|
|
|
+ /// <param name="condition">路径筛选条件</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static WCS_DEVICE GetNext(this WCS_DEVICE source, string endAddr, Func<List<WCS_DEVICE>, bool> condition)
|
|
|
+ {
|
|
|
+ var path = source.GetPath(endAddr, condition);
|
|
|
+ if (Ltc.Do(path, v => v == null || v.Count == 0))
|
|
|
+ return null;
|
|
|
+
|
|
|
+ if (source.IsConv())
|
|
|
+ {
|
|
|
+ var obj = path.FirstOrDefault(v => !v.IsConv());
|
|
|
+ if (obj == null)
|
|
|
+ {
|
|
|
+ return path.Last();
|
|
|
+ }
|
|
|
+ else if (obj.IsRGV())
|
|
|
+ {
|
|
|
+ var target = path.Skip(path.IndexOf(obj) + 1).FirstOrDefault();
|
|
|
+ return target;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ return path.First();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsDevGroup(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.CODE.Contains("G");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsRGV(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.DEVICEPROTOCOLS.Any(v => v.DB.PROTOCOL == typeof(IRGV520).AssemblyQualifiedName);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsConv(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.DEVICEPROTOCOLS.Any(v => v.DB.PROTOCOL == typeof(IStation521).AssemblyQualifiedName);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 是否堆垛机
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="source"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static bool IsSC(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.DEVICEPROTOCOLS.Any(v => v.DB.PROTOCOL == typeof(ISRM520).AssemblyQualifiedName);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static bool IsTunnel(this WCS_DEVICE source)
|
|
|
+ {
|
|
|
+ return source.DEVICEPROTOCOLS.Count == 0 && source.CODE.StartsWith("T");
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void AddFlag(DF flag, params string[] devices)
|
|
|
+ {
|
|
|
+ var arr = LogicHandler.AllObjects.OfType<WCS_DEVICE>().Where(v => devices.Contains(v.CODE)).ToArray();
|
|
|
+ Parallel.ForEach(arr, v =>
|
|
|
+ {
|
|
|
+ v.AddFlag(flag);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void AddFlag(DF flag, Action<WCS_DEVICE> callbck, params string[] devices)
|
|
|
+ {
|
|
|
+ var arr = LogicHandler.AllObjects.OfType<WCS_DEVICE>().Where(v => devices.Contains(v.CODE)).ToArray();
|
|
|
+ Parallel.ForEach(arr, v =>
|
|
|
+ {
|
|
|
+ v.AddFlag(flag);
|
|
|
+ callbck?.Invoke(v);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void Set<T>(string key, T value, params string[] devices)
|
|
|
+ {
|
|
|
+ var arr = LogicHandler.AllObjects.OfType<WCS_DEVICE>().Where(v => devices.Contains(v.CODE)).ToArray();
|
|
|
+ Parallel.ForEach(arr, v =>
|
|
|
+ {
|
|
|
+ v.Set(key, value);
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void Set<T>(string key, T value, Func<WCS_DEVICE, bool> func)
|
|
|
+ {
|
|
|
+ var arr = LogicHandler.AllObjects.OfType<WCS_DEVICE>().Where(func).ToArray();
|
|
|
+ Parallel.ForEach(arr, v =>
|
|
|
+ {
|
|
|
+ v.Set(key, value);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 输送机设备组
|
|
|
+ /// </summary>
|
|
|
+ public class StationDeviceGroup : DeviceGroup<IStation520, IStation521, IStation523>
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// 当前设备可用的RGV
|
|
|
+ /// </summary>
|
|
|
+ private static List<RGVDevice> AllRGVList;
|
|
|
+
|
|
|
+ static StationDeviceGroup()
|
|
|
+ {
|
|
|
+ AllRGVList = Device.Where(v => v.IsRGV() && v.CODE != "RGV8").Select(v => v.Create<RGVDevice>()).ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ public StationDeviceGroup(WCS_DEVICE entity) : base(entity)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 执行输送机设备组任务 单例锁
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="act"></param>
|
|
|
+ public void EX(Action<StationDeviceGroup> act)
|
|
|
+ {
|
|
|
+ var key = $"WCS:Lock:{Entity.CODE}";
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"[{Entity.CODE}]--触发并发管控");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ act(this);
|
|
|
+ }
|
|
|
+ catch (DoException ex)
|
|
|
+ {
|
|
|
+ ex.DoExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (WarnException ex)
|
|
|
+ {
|
|
|
+ ex.WarnExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ ex.ExceptionEx(Entity);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ RedisHelper.Default().Del(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当前设备可用的RGV
|
|
|
+ /// </summary>
|
|
|
+ public List<RGVDevice> RgvList
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return AllRGVList.Where(v => v.LocationList.Any(p => p.Entity == Entity) && v.Data2.WorkMode != 0).ToList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当前设备环穿组
|
|
|
+ /// </summary>
|
|
|
+ private List<StationLocation> LoncationList
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ var dev = StationLocation.ALLlocations.FirstOrDefault(v => v.Station == Entity.CODE);
|
|
|
+ return StationLocation.ALLlocations.Where(v => v.PLC == dev.PLC).ToList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设备组自身的位置
|
|
|
+ /// </summary>
|
|
|
+ public float Position
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return StationLocation.ALLlocations.FirstOrDefault(v => v.Station == Entity.CODE).Location;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设备组所在环穿的总长度
|
|
|
+ /// </summary>
|
|
|
+ public float Length
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return StationLocation.ALLlocations.FirstOrDefault(v => v.Station == Entity.CODE).Length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设备组是否满足任务执行条件
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="type">给当前设备组下发任务时需要的请求</param>
|
|
|
+ /// <returns>true:不满足执行条件需要进行停止执行 false:表示满足条件不需要停止执行 </returns>
|
|
|
+ /// <exception cref="Exception"></exception>
|
|
|
+ public void WhetherToExecute(IstationRequest type = IstationRequest.无)
|
|
|
+ {
|
|
|
+ foreach (var item in Items)
|
|
|
+ {
|
|
|
+ if (item.Data.VoucherNo != item.Data2.VoucherNo) throw new WarnException($"等待{item.Entity.CODE}执行任务{item.Data.Tasknum},凭证号不一致");
|
|
|
+ if (item.Data3.Status.HasFlag(StationStatus.运行状态位)) throw new DoException($"{item.Entity.CODE}运行中");
|
|
|
+ if (!item.Data2.Status.HasFlag(IstationStatus.光电状态)) throw new DoException($"[{item.Entity.CODE}]无光电");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取设备组中需要取货的设备
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public List<Device<IStation520, IStation521, IStation523>> RGVGetTaskedDevice()
|
|
|
+ {
|
|
|
+ var a = Items.Where(v => v.Data2.Status.HasFlag(IstationStatus.光电状态) && v.Data2.Tasknum > 10000)
|
|
|
+ .Where(v => v.Entity.CODE.ToShort() != v.Data2.Goodsend && v.Data2.Goodsend != 0)
|
|
|
+ .ToList();
|
|
|
+ return a.Count == 0 ? null : a;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 是否可以取货
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public bool IsPickUp(RGVDevice rgvDevice)
|
|
|
+ {
|
|
|
+ if (Entity.CODE is "G2" or "G3") return true;
|
|
|
+ var dCount = Device.Where(v => v.CODE is "G1" or "G1340" or "G1337").Select(v => v.Create<StationDeviceGroup>()).Count(v =>
|
|
|
+ {
|
|
|
+ var count = v.Items.Count(v => !v.Data3.Status.HasFlag(StationStatus.运行状态位) && !v.Data2.Status.HasFlag(IstationStatus.光电状态) && v.Data2.Tasknum < 10000
|
|
|
+ && v.Data3.Status.HasFlag(StationStatus.自动));
|
|
|
+ return count == 2 ? true : false;
|
|
|
+ });
|
|
|
+ var rCount = rgvDevice.RGVList.Count(v => v.Data.DestPosition_1 == 1);
|
|
|
+ return rCount < dCount;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 入库位置获取需要生产任务的设备及条码信息
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public List<FinishTaskList<string>> GetBcrValid()
|
|
|
+ {
|
|
|
+ var list = new List<FinishTaskList<string>>();
|
|
|
+
|
|
|
+ //获取需要执行的设备信息
|
|
|
+ foreach (var dev in Items)
|
|
|
+ {
|
|
|
+ if (!dev.Data2.Status.HasFlag(IstationStatus.光电状态))
|
|
|
+ {
|
|
|
+ InfoLog.INFO_INFO($"{dev.Entity.CODE}--没有光电");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (dev.Data2.Request != IstationRequest.扫码入库)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb($"{dev.Entity.CODE}--有光电没有扫码入库请求--1", Entity.CODE);
|
|
|
+ continue;
|
|
|
+ };
|
|
|
+ if (dev.Data2.Tasknum > 10000)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb($"{dev.Entity.CODE}--有光电有请求,但已有任务号", Entity.CODE);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ var bcr = dev.Entity.BCR();
|
|
|
+ var barcode = bcr.Content.Trim('\r');
|
|
|
+ if (barcode == "")
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb($"{dev.Entity.CODE}--扫码失败,内容为空", Entity.CODE);
|
|
|
+ continue;
|
|
|
+ };
|
|
|
+
|
|
|
+ list.Add(new FinishTaskList<string>(barcode, dev.Entity.Create<StationDevice>()));
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取下一个地址的有效设备
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public List<FinishTaskList<string>> GetAddressValid()
|
|
|
+ {
|
|
|
+ var list = new List<FinishTaskList<string>>();
|
|
|
+
|
|
|
+ //获取需要执行的设备信息
|
|
|
+ foreach (var dev in Items)
|
|
|
+ {
|
|
|
+ if (!dev.Data2.Status.HasFlag(IstationStatus.光电状态))
|
|
|
+ {
|
|
|
+ InfoLog.INFO_INFO($"{dev.Entity.CODE}--没有光电");
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (dev.Data2.Request != IstationRequest.请求分配目标地址)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb($"{dev.Entity.CODE}--有光电没有分配目标地址请求--2", Entity.CODE);
|
|
|
+ continue;
|
|
|
+ };
|
|
|
+ if (dev.Data2.Tasknum < 10000)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb($"{dev.Entity.CODE}--有光电有请求没有任务号", Entity.CODE);
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ list.Add(new FinishTaskList<string>(dev.Entity.CODE, dev.Entity.Create<StationDevice>()));
|
|
|
+ }
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 最近的RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public RGVDevice RecentRGV()
|
|
|
+ {
|
|
|
+ return RgvList.OrderBy(v => v.Distance(this)).FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算目标RGV与站台自身的距离
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="rgv"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public float Distance(RGVDevice rgv)
|
|
|
+ {
|
|
|
+ return DevEX.Distance(Position, rgv.Position, Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算两个站台之间的距离
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="rgv"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public float Distance(StationDeviceGroup dev)
|
|
|
+ {
|
|
|
+ return DevEX.Distance(Position, dev.Position, Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当前RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public RGVDevice CurrentRGV()
|
|
|
+ {
|
|
|
+ //RGV与站台距离误差为 正负50
|
|
|
+ var max = Position + 500;
|
|
|
+ var min = Position - 500;
|
|
|
+ return RgvList?.FirstOrDefault(v => v.Data2.Position < max && v.Data2.Position > min);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 是否需要RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <returns>true:需要RGV false:不需要RGV</returns>
|
|
|
+ public bool NeedRgv()
|
|
|
+ {
|
|
|
+ var rgvs = Device.Where(v => v.IsRGV()).Select(v => v.Device<IRGV521>());
|
|
|
+ var code = Entity.CODE.Replace("G", "").ToShort();
|
|
|
+ if (rgvs.Any(v => v.Data.DestPosition_1 == code && v.Data.SystemStatus != RGVRunStatus.空闲))
|
|
|
+ throw new WarnException("已有RGV执行中");
|
|
|
+ foreach (var item in Items)
|
|
|
+ {
|
|
|
+ if (item.Data3.Status.HasFlag(StationStatus.运行状态位)) return false;
|
|
|
+ if (!item.Data2.Status.HasFlag(IstationStatus.光电状态)) return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// BCR 站点是否被禁止
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public void BcrStationIsForbid()
|
|
|
+ {
|
|
|
+ var config = RedisHelper.Default().Get("ForbidTubuEnter").Split(",");
|
|
|
+ if (config.Contains(Entity.CODE)) throw new WarnException("当前入库口已被禁用,请联系机修人员了解具体情况");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 输送机设备
|
|
|
+ /// </summary>
|
|
|
+ public class StationDevice : Device<IStation520, IStation521, IStation523>
|
|
|
+ {
|
|
|
+ public StationDevice(WCS_DEVICE entity) : base(entity)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设备组是否满足任务执行条件
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="type">给当前设备组下发任务时需要的请求</param>
|
|
|
+ /// <returns>true:不满足执行条件需要进行停止执行 false:表示满足条件不需要停止执行 </returns>
|
|
|
+ /// <exception cref="Exception"></exception>
|
|
|
+ public void WhetherToExecute(IstationRequest type = IstationRequest.无)
|
|
|
+ {
|
|
|
+ //正在运行
|
|
|
+ if (Data3.Status.HasFlag(StationStatus.运行状态位)) throw new DoException("运行中");
|
|
|
+ //上一次的任务还未执行
|
|
|
+ if (Data.VoucherNo != Data2.VoucherNo)
|
|
|
+ throw new WarnException($"等待任务[{Data2.Tasknum}]执行");
|
|
|
+ //没有光电
|
|
|
+ if (!Data2.Status.HasFlag(IstationStatus.光电状态)) throw new DoException("无光电"); ;
|
|
|
+ //没有任务号
|
|
|
+ switch (type)
|
|
|
+ {
|
|
|
+ case IstationRequest.无:
|
|
|
+ if (Data2.Tasknum < 10000 && Data.Tasknum < 10000)
|
|
|
+ throw new WarnException($"设备无任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case IstationRequest.扫码入库:
|
|
|
+ if (Data2.Tasknum > 10000 && Data.Tasknum > 10000)
|
|
|
+ throw new WarnException($"设备已有任务任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case IstationRequest.堆垛机放货完成请求目标地址:
|
|
|
+ if (Data2.Tasknum < 10000 && Data.Tasknum < 10000)
|
|
|
+ throw new WarnException($"设备无任务信息");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case IstationRequest.请求分配目标地址:
|
|
|
+ if (Data2.Tasknum < 10000 && Data.Tasknum < 10000)
|
|
|
+ throw new WarnException($"设备无任务信息");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ //没有请求
|
|
|
+ if (type != IstationRequest.无 && Data2.Request != type)
|
|
|
+ throw new WarnException($"有光电无{type}请求");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 执行输送机任务 单例锁
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="act"></param>
|
|
|
+ public void EX(Action<StationDevice> act)
|
|
|
+ {
|
|
|
+ var key = $"WCS:Lock:{Entity.CODE}";
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"[{Entity.CODE}]--触发并发管控");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ act(this);
|
|
|
+ }
|
|
|
+ catch (DoException ex)
|
|
|
+ {
|
|
|
+ ex.DoExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (WarnException ex)
|
|
|
+ {
|
|
|
+ ex.WarnExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ ex.ExceptionEx(Entity);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ RedisHelper.Default().Del(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// RGV设备
|
|
|
+ /// </summary>
|
|
|
+ public class RGVDevice : Device<IRGV520, IRGV521, IRGV523>
|
|
|
+ {
|
|
|
+ static RGVDevice()
|
|
|
+ {
|
|
|
+ AllRGVList = Device.Where(v => v.IsRGV() && v.CODE != "RGV8").Select(v => v.Create<RGVDevice>()).ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ public RGVDevice(WCS_DEVICE entity) : base(entity)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 所有环穿RGV
|
|
|
+ /// </summary>
|
|
|
+ private static List<RGVDevice> AllRGVList { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 与当前RGV处于同一环穿的RGV
|
|
|
+ /// </summary>
|
|
|
+ public List<RGVDevice> RGVList
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ //利用WorkMode来排除的环穿维护设备
|
|
|
+ return AllRGVList.Where(v => v.Entity.DEVICEPROTOCOLS.Any(d => Entity.DEVICEPROTOCOLS.Any(e => e.DB.PLC.IP == d.DB.PLC.IP)))
|
|
|
+ .Where(v => v.Data2.WorkMode != 0)
|
|
|
+ .Where(v => v.Entity.CODE != Entity.CODE).ToList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// RGV当前位置
|
|
|
+ /// </summary>
|
|
|
+ public float Position
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return Data2.Position;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 与当前RGV处于同一环穿的站台
|
|
|
+ /// </summary>
|
|
|
+ public List<StationDeviceGroup> LocationList
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return StationLocation.ALLlocations.Where(v => Entity.DEVICEPROTOCOLS.Any(p => p.DB.PLC.CODE == v.PLC))
|
|
|
+ .Select(v => Device.Find(v.Station).Create<StationDeviceGroup>()).ToList();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 总长度
|
|
|
+ /// </summary>
|
|
|
+ public float Length
|
|
|
+ {
|
|
|
+ get
|
|
|
+ {
|
|
|
+ return LocationList.FirstOrDefault().Length;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 执行RGV任务 单例锁
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="act"></param>
|
|
|
+ public void EX(Action<RGVDevice> act)
|
|
|
+ {
|
|
|
+ var key = $"WCS:Lock:{Entity.CODE}";
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"[{Entity.CODE}]--触发并发管控");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ act(this);
|
|
|
+ }
|
|
|
+ catch (DoException ex)
|
|
|
+ {
|
|
|
+ ex.DoExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (WarnException ex)
|
|
|
+ {
|
|
|
+ ex.WarnExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ ex.ExceptionEx(Entity);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ RedisHelper.Default().Del(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取前一个取货点
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public StationDeviceGroup BeforeStation()
|
|
|
+ {
|
|
|
+ var a = LocationList.Where(v => v.Entity.Is(DF.涂布RGV取货设备组) || v.Entity.Is(DF.BOPPRGV取货设备组));
|
|
|
+ return LocationList.Where(v => v.Entity.Is(DF.涂布RGV取货设备组) || v.Entity.Is(DF.BOPPRGV取货设备组) && v.Entity.CODE != this.CurrentStation().Entity.CODE).OrderBy(v => Distance(v)).FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 前一个RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public RGVDevice Before()
|
|
|
+ {
|
|
|
+ //按照位置排序
|
|
|
+ var arr = RGVList.OrderBy(v => v.Position);
|
|
|
+ var rgv = arr.FirstOrDefault(v => v.Position > Position);
|
|
|
+ if (rgv == null)
|
|
|
+ rgv = arr.LastOrDefault(v => v.Position < Position);
|
|
|
+ return rgv;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 后一个RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public RGVDevice After()
|
|
|
+ {
|
|
|
+ //到当前RGV最近的一个RGV
|
|
|
+ return RGVList.OrderBy(v => v.Distance(this)).FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取当前所在的取货站台
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public StationDeviceGroup CurrentStation()
|
|
|
+ {
|
|
|
+ return LocationList.Where(v => v.Entity.Is(DF.涂布RGV取货设备组) || v.Entity.Is(DF.涂布RGV放货设备组) || v.Entity.Is(DF.BOPPRGV取货设备组) || v.Entity.Is(DF.BOPPRGV放货设备组)).Where(v =>
|
|
|
+ {
|
|
|
+ //RGV与站台距离误差为 正负50500
|
|
|
+ var max = v.Position + 500;
|
|
|
+ var min = v.Position - 500;
|
|
|
+ return Data2.Position < max && Data2.Position > min;
|
|
|
+ }).FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算当前RGV与指定RGV之间的距离
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="rgv"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public float Distance(RGVDevice rgv)
|
|
|
+ {
|
|
|
+ //return Math.Abs((Position - rgv.Position + Length) % Length);
|
|
|
+ return DevEX.Distance(Position, rgv.Position, Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算当前RGV与指定站台之间的距离
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="after"></param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public float Distance(StationDeviceGroup after)
|
|
|
+ {
|
|
|
+ if (after == null) throw new WarnException($"不是一个有效的StationDeviceGroup,{Entity.CODE}");
|
|
|
+ return DevEX.Distance(Position, after.Position, Length);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 是否需要执行放货任务
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public bool IsPut()
|
|
|
+ {
|
|
|
+ if (Data2.TaskType_1 != RGVTaskType.取货) return false;
|
|
|
+ if (!Data2.Status_1.HasFlag(WCS.Entity.Protocol.RGVStatus.RGV到站)) return false;
|
|
|
+ if (!Data2.Status_1.HasFlag(WCS.Entity.Protocol.RGVStatus.任务完成)) return false;
|
|
|
+ if (!Data2.Status_1.HasFlag(WCS.Entity.Protocol.RGVStatus.光电)) return false;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 写入移动任务
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="addr">目标地址</param>
|
|
|
+ public void Move(StationDeviceGroup addr)
|
|
|
+ {
|
|
|
+ if (Data.TaskType_1 == RGVTaskType.取货) throw new WarnException($"当前有{Data.TaskType_1}任务,无法执行移动任务");
|
|
|
+ if (Data2.WorkMode != RGVMode.自动) throw new WarnException($"RGV状态{Data2.WorkMode},无法执行移动任务");
|
|
|
+ if (Data2.SystemStatus != RGVRunStatus.空闲) throw new WarnException($"rgv状态为{Data2.SystemStatus},无法执行移动任务");
|
|
|
+ if (Data2.Status_1.HasFlag(WCS.Entity.Protocol.RGVStatus.光电)) throw new WarnException("RGV有光电,无法执行移动任务");
|
|
|
+ if (Data2.TaskID_1 == addr.Entity.CODE.GetShortCode() && Data.TaskType_1 == RGVTaskType.移动)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_RGVINFO($"{Entity.CODE}]--已有目标地址相同的移动任务");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ InfoLog.INFO_RGVINFO($"[{Entity.CODE}]--写入RGV移动任务-开始:{Data.TaskID_1},{Data.TaskType_1},{Data.DestPosition_1},{Data.Trigger_1}");
|
|
|
+
|
|
|
+ Data.TaskID_1 = addr.Entity.CODE.GetShortCode();
|
|
|
+ Data.TaskType_1 = RGVTaskType.移动;
|
|
|
+ Data.DestPosition_1 = addr.Entity.CODE.GetShortCode();
|
|
|
+ Data.Trigger_1++;
|
|
|
+ InfoLog.INFO_RGVINFO($"[{Entity.CODE}]--写入RGV移动任务-结束:{Data.TaskID_1},{Data.TaskType_1},{Data.DestPosition_1},{Data.Trigger_1}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 写入取货任务
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="addr">目标地址</param>
|
|
|
+ public void Pick(StationDeviceGroup addr, int task1 = 0, int task2 = 0)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_RGVINFO($"[{Entity.CODE}]--写入RGV取货任务-开始:{Data.TaskID_1},{Data.TaskID_2},{Data.TaskType_1},{Data.DestPosition_1},{Data.Trigger_1}");
|
|
|
+ Data.TaskType_1 = RGVTaskType.取货;
|
|
|
+ Data.DestPosition_1 = addr.Entity.CODE.GetShortCode();
|
|
|
+ if (task1 != 0) Data.TaskID_1 = task1;
|
|
|
+ if (task2 != 0) Data.TaskID_2 = task2;
|
|
|
+ Data.Trigger_1++;
|
|
|
+ InfoLog.INFO_RGVINFO($"[{Entity.CODE}]--写入RGV取货任务-结束:{Data.TaskID_1},{Data.TaskID_2},{Data.TaskType_1},{Data.DestPosition_1},{Data.Trigger_1}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 写入放货任务
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="addr">目标地址</param>
|
|
|
+ public void Put(StationDeviceGroup addr, int task1 = 0, int task2 = 0)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_RGVINFO($"[{Entity.CODE}]--写入RGV放货任务-开始:{Data.TaskID_1},{Data.TaskID_2},{Data.TaskType_1},{Data.DestPosition_1},{Data.Trigger_1}");
|
|
|
+ Data.TaskType_1 = RGVTaskType.放货;
|
|
|
+ Data.DestPosition_1 = addr.Entity.CODE.GetShortCode();
|
|
|
+ if (task1 != 0) Data.TaskID_1 = task1;
|
|
|
+ if (task2 != 0) Data.TaskID_2 = task2;
|
|
|
+ Data.Trigger_1++;
|
|
|
+ InfoLog.INFO_RGVINFO($"[{Entity.CODE}]--写入RGV放货任务-结束:{Data.TaskID_1},{Data.TaskID_2},{Data.TaskType_1},{Data.DestPosition_1},{Data.Trigger_1}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 筛选出所有与当前RGV距离小于指定长度的RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="distance">指定长度</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public RGVDevice[] RgvAfter(float distance)
|
|
|
+ {
|
|
|
+ return RGVList.Where(v => Distance(v) < distance).ToArray();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 当前RGV是否有拦住指定RGV
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="rgv">RGV</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public bool StopedByMe(RGVDevice rgv)
|
|
|
+ {
|
|
|
+ //目标站台
|
|
|
+ var target = rgv.Data2.DestPosition_1;
|
|
|
+ //获取目标站台的设备组信息
|
|
|
+ var station = Device.Find($"G{target}").Create<StationDeviceGroup>();
|
|
|
+
|
|
|
+ if (station.Distance(rgv) < 5000) return false;
|
|
|
+
|
|
|
+ //当前RGV与目标站台的距离小于传入RGV到达目标站台的距离
|
|
|
+ return (this.Distance(station) < rgv.Distance(station)) || station.CurrentRGV()?.Entity.CODE == this.Entity.CODE;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取当前RGV的下一个站台,即距离最近的一个站台
|
|
|
+ /// </summary>
|
|
|
+ /// <returns></returns>
|
|
|
+ public StationDeviceGroup NextStation()
|
|
|
+ {
|
|
|
+ //先取当前RGV与所有站台的距离
|
|
|
+ var dev = LocationList.OrderBy(v => v.Distance(this)).FirstOrDefault();
|
|
|
+ return dev;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 堆垛机设备
|
|
|
+ /// </summary>
|
|
|
+ public class SRMDevice : Device<ISRM520, ISRM521, ISRM537>
|
|
|
+ {
|
|
|
+ public SRMDevice(WCS_DEVICE entity) : base(entity)
|
|
|
+ {
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取放货点
|
|
|
+ /// </summary>
|
|
|
+ public List<StationDevice> GetDeliveryPoint()
|
|
|
+ {
|
|
|
+ return Entity.ROUTES.Select(v => v.NEXT) //巷道
|
|
|
+ .SelectMany(v => v.ROUTES.Select(d => d.NEXT)) //放货点
|
|
|
+ .Where(v => v.IsConv()) //必须是输送线
|
|
|
+ .Select(v => v.Create<StationDevice>()).ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取取货点
|
|
|
+ /// </summary>
|
|
|
+ public List<StationDevice> GetPickPoint()
|
|
|
+ {
|
|
|
+ return Device.Where(v => v.Is(DF.SRM二级品取货) || v.Is(DF.SRM涂布取货) || v.Is(DF.SRMBOPP取货))
|
|
|
+ .Where(v => v.ROUTES.Any(p => p.NEXT.ROUTES.Any(d => d.NEXT == Entity)))
|
|
|
+ .Select(v => v.Create<StationDevice>())
|
|
|
+ .ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 处理完成任务
|
|
|
+ /// </summary>
|
|
|
+ public void FinishedTaskHandle()
|
|
|
+ {
|
|
|
+ WCS_TASK task = new WCS_TASK();
|
|
|
+
|
|
|
+ Db.Do(db =>
|
|
|
+ {
|
|
|
+ var taskIds = new List<int>() { Data2.FinishedTask_1, Data2.FinishedTask_2 }.ToArray();
|
|
|
+
|
|
|
+ for (int i = 0; i < taskIds.Length; i++)
|
|
|
+ {
|
|
|
+ //判断当前工位是否有完成任务
|
|
|
+ if (taskIds[i] == 0) continue;
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Single(v => taskIds[i] == v.ID);
|
|
|
+ if (task.STATUS != TaskStatus.堆垛机执行 && task.STATUS != TaskStatus.堆垛机完成)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb($"任务{task.ID},状态位{task.STATUS}", Entity.CODE);
|
|
|
+ continue;
|
|
|
+ };
|
|
|
+
|
|
|
+ if (task.STATUS == TaskStatus.堆垛机完成)
|
|
|
+ {
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ Data.FinishedACK_1 = 1;
|
|
|
+ Data.TaskID_1 = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Data.FinishedACK_2 = 1;
|
|
|
+ Data.TaskID_2 = 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ throw new DoException("二次处理堆垛机完成任务");
|
|
|
+ }
|
|
|
+
|
|
|
+ switch (task.TYPE)
|
|
|
+ {
|
|
|
+ case TaskType.入库:
|
|
|
+ task.ENDTIME = DateTime.Now;
|
|
|
+ task.STATUS = WCS.Entity.TaskStatus.已完成;
|
|
|
+ task.UPDATETIME = DateTime.Now;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TaskType.出库:
|
|
|
+ task.STATUS = TaskStatus.堆垛机完成;
|
|
|
+ task.UPDATETIME = DateTime.Now;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case TaskType.移库:
|
|
|
+ {
|
|
|
+ if (task.STATUS == TaskStatus.堆垛机执行)
|
|
|
+ {
|
|
|
+ task.STATUS = TaskStatus.已完成;
|
|
|
+ task.UPDATETIME = DateTime.Now;
|
|
|
+ }
|
|
|
+
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ default:
|
|
|
+ throw new Exception($"[{Entity.CODE}]任务类型错误,{task.ID}");
|
|
|
+ }
|
|
|
+
|
|
|
+ task.CreateStatusLog(db, $"状态由[{TaskStatus.堆垛机执行}]变更为[{task.STATUS}]", this.GetType());
|
|
|
+ db.Default.Updateable(task).AddQueue();
|
|
|
+ }
|
|
|
+ db.Default.SaveQueues();
|
|
|
+ });
|
|
|
+
|
|
|
+ Db.Do(db =>
|
|
|
+ {
|
|
|
+ var taskIds = new List<int>() { Data2.FinishedTask_1, Data2.FinishedTask_2 }.ToArray();
|
|
|
+
|
|
|
+ for (int i = 0; i < taskIds.Length; i++)
|
|
|
+ {
|
|
|
+ //判断当前工位是否有完成任务
|
|
|
+ if (taskIds[i] == 0) continue;
|
|
|
+ //获取当前工位的目标地址
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Single(v => taskIds[i] == v.ID);
|
|
|
+
|
|
|
+ if (i == 0)
|
|
|
+ {
|
|
|
+ Data.FinishedACK_1 = 1;
|
|
|
+ Data.TaskID_1 = 0;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Data.FinishedACK_2 = 1;
|
|
|
+ Data.TaskID_2 = 0;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 执行堆垛机任务 单例锁
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="act"></param>
|
|
|
+ public void EX(Action<SRMDevice> act)
|
|
|
+ {
|
|
|
+ var key = $"WCS:Lock:{Entity.CODE}";
|
|
|
+
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"[{Entity.CODE}]--触发并发管控");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ act(this);
|
|
|
+ }
|
|
|
+ catch (DoException ex)
|
|
|
+ {
|
|
|
+ ex.DoExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (WarnException ex)
|
|
|
+ {
|
|
|
+ ex.WarnExceptionEX(Entity);
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ ex.ExceptionEx(Entity);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ RedisHelper.Default().Del(key);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 执行出库任务 出库单例锁
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="act"></param>
|
|
|
+ public void EXOutStock(Action<SRMDevice> act)
|
|
|
+ {
|
|
|
+ var key = "WCS:Lock:";
|
|
|
+ try
|
|
|
+ {
|
|
|
+ if (Entity.CODE == "SRM3" || Entity.CODE == "SRM4")
|
|
|
+ {
|
|
|
+ key += "SRM3-SRM4-Out";
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"触发出库并发管控--[{Entity.CODE}]");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ }
|
|
|
+ if (Entity.CODE == "SRM5" || Entity.CODE == "SRM6")
|
|
|
+ {
|
|
|
+ key += "SRM5-SRM6-Out";
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"触发出库并发管控--[{Entity.CODE}]");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (Entity.CODE == "SRM7" || Entity.CODE == "SRM8")
|
|
|
+ {
|
|
|
+ key += "SRM7-SRM8-Out";
|
|
|
+ if (RedisHelper.Default().Get(key) != null) throw new WarnException($"触发出库并发管控--[{Entity.CODE}]");
|
|
|
+ RedisHelper.Default().Set(key, Entity.CODE);
|
|
|
+ }
|
|
|
+
|
|
|
+ act(this);
|
|
|
+ }
|
|
|
+ finally
|
|
|
+ {
|
|
|
+ if (Entity.CODE == "SRM3" || Entity.CODE == "SRM4") RedisHelper.Default().Del($"{key}SRM3-SRM4-Out");
|
|
|
+ if (Entity.CODE == "SRM5" || Entity.CODE == "SRM6") RedisHelper.Default().Del($"{key}SRM5-SRM6-Out");
|
|
|
+ if (Entity.CODE == "SRM7" || Entity.CODE == "SRM8") RedisHelper.Default().Del($"{key}SRM7-SRM8-Out");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 一工位写任务
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="task"></param>
|
|
|
+ /// <param name="goodsnum">货物数量</param>
|
|
|
+ public void WriteTask1(Task task, short goodsnum)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_SRMINFO($"出库--写入堆垛机[{Entity.CODE}]1工位-开始:[{Data.TaskID_1}][{Data.SLine_1}][{Data.SCol_1}][{Data.SLayer_1}][{Data.ELine_1}][{Data.VoucherNo_1}]--[{Data.RES1_1}]");
|
|
|
+ Data.TaskID_1 = task.ID;
|
|
|
+ Data.SLine_1 = task.Line;
|
|
|
+ Data.SCol_1 = task.Col;
|
|
|
+ Data.SLayer_1 = task.Layer;
|
|
|
+ Data.ELine_1 = task.SRMSTATION.ToShort();
|
|
|
+ Data.ECol_1 = 0;
|
|
|
+ Data.ELayer_1 = 0;
|
|
|
+ Data.RES1_1 = goodsnum;
|
|
|
+ Data.VoucherNo_1++;
|
|
|
+ InfoLog.INFO_SRMINFO($"出库--写入堆垛机[{Entity.CODE}]1工位-结束:[{Data.TaskID_1}][{Data.SLine_1}][{Data.SCol_1}][{Data.SLayer_1}][{Data.ELine_1}][{Data.VoucherNo_1}]--[{Data.RES1_1}]");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 二工位写任务
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="task"></param>
|
|
|
+ /// <param name="goodsnum">货物数量</param>
|
|
|
+ public void WriteTask2(Task task, short goodsnum)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_SRMINFO($"出库--写入堆垛机[{Entity.CODE}]2工位-开始:[{Data.TaskID_2}][{Data.SLine_2}][{Data.SCol_2}][{Data.SLayer_2}][{Data.ELine_2}][{Data.VoucherNo_2}]--[{Data.RES1_2}]");
|
|
|
+ Data.TaskID_2 = task.ID;
|
|
|
+ Data.SLine_2 = task.Line;
|
|
|
+ Data.SCol_2 = task.Col;
|
|
|
+ Data.SLayer_2 = task.Layer;
|
|
|
+ Data.ELine_2 = task.SRMSTATION.ToShort();
|
|
|
+ Data.ECol_2 = 0;
|
|
|
+ Data.ELayer_2 = 0;
|
|
|
+ Data.RES1_2 = goodsnum;
|
|
|
+ Data.VoucherNo_2++;
|
|
|
+ InfoLog.INFO_SRMINFO($"出库--写入堆垛机[{Entity.CODE}]2工位-结束:[{Data.TaskID_2}][{Data.SLine_2}][{Data.SCol_2}][{Data.SLayer_2}][{Data.ELine_2}][{Data.VoucherNo_2}]--[{Data.RES1_2}]");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 获取任务对应的货叉
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="task">任务信息</param>
|
|
|
+ /// <param name="index">任务在下发任务集合中的索引</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public SrmFork GetFork(Task task, int index)
|
|
|
+ {
|
|
|
+ return index switch
|
|
|
+ {
|
|
|
+ > 1 => throw new WarnException("一次最多下发两个任务"),
|
|
|
+ //如果索引是1,直接返回货叉2
|
|
|
+ 1 => SrmFork.货叉2,
|
|
|
+ _ => task.Col switch
|
|
|
+ {
|
|
|
+ 102 => Entity.CODE switch
|
|
|
+ {
|
|
|
+ "SRM1" => SrmFork.货叉1,
|
|
|
+ _ => SrmFork.货叉2,
|
|
|
+ },
|
|
|
+ 112 => SrmFork.货叉2,
|
|
|
+ _ => SrmFork.货叉1,
|
|
|
+ }
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 检查同组堆垛机是否有出库任务正在执行
|
|
|
+ /// </summary>
|
|
|
+ public void CheckOutTask()
|
|
|
+ {
|
|
|
+ //检查同组堆垛机是否有正在执行出库任务的
|
|
|
+ Db.Do(db =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var srm = Device.Find("SRM4").Create<SRMDevice>();
|
|
|
+ var task = db.Default.Queryable<WCS_TASK>().Any(v => v.STATUS == TaskStatus.堆垛机执行 && v.DEVICE == "SRM4" && v.TYPE == TaskType.出库);
|
|
|
+ switch (Entity.CODE)
|
|
|
+ {
|
|
|
+ case "SRM3":
|
|
|
+ if (srm.Data3.SCAlarm == 0 && srm.Data2.SRMMode == WCS.Entity.Protocol.SRM.SCMode.远程 && task)
|
|
|
+ throw new DoException("SRM4正在执行出库任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "SRM4":
|
|
|
+ srm = Device.Find("SRM3").Create<SRMDevice>();
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Any(v => v.STATUS == TaskStatus.堆垛机执行 && v.DEVICE == "SRM3" && v.TYPE == TaskType.出库);
|
|
|
+ if (srm.Data3.SCAlarm == 0 && srm.Data2.SRMMode == WCS.Entity.Protocol.SRM.SCMode.远程 && task)
|
|
|
+ throw new DoException("SRM3正在执行出库任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "SRM5":
|
|
|
+ srm = Device.Find("SRM6").Create<SRMDevice>();
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Any(v => v.STATUS == TaskStatus.堆垛机执行 && v.DEVICE == "SRM6" && v.TYPE == TaskType.出库);
|
|
|
+ if (srm.Data3.SCAlarm == 0 && srm.Data2.SRMMode == WCS.Entity.Protocol.SRM.SCMode.远程 && task)
|
|
|
+ throw new DoException("SRM6正在执行出库任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "SRM6":
|
|
|
+ srm = Device.Find("SRM5").Create<SRMDevice>();
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Any(v => v.STATUS == TaskStatus.堆垛机执行 && v.DEVICE == "SRM5" && v.TYPE == TaskType.出库);
|
|
|
+ if (srm.Data3.SCAlarm == 0 && srm.Data2.SRMMode == WCS.Entity.Protocol.SRM.SCMode.远程 && task)
|
|
|
+ throw new DoException("SRM5正在执行出库任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "SRM7":
|
|
|
+ srm = Device.Find("SRM8").Create<SRMDevice>();
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Any(v => v.STATUS == TaskStatus.堆垛机执行 && v.DEVICE == "SRM8" && v.TYPE == TaskType.出库);
|
|
|
+ if (srm.Data3.SCAlarm == 0 && srm.Data2.SRMMode == WCS.Entity.Protocol.SRM.SCMode.远程 && task)
|
|
|
+ throw new DoException("SRM8正在执行出库任务");
|
|
|
+ break;
|
|
|
+
|
|
|
+ case "SRM8":
|
|
|
+ srm = Device.Find("SRM7").Create<SRMDevice>();
|
|
|
+ task = db.Default.Queryable<WCS_TASK>().Any(v => v.STATUS == TaskStatus.堆垛机执行 && v.DEVICE == "SRM7" && v.TYPE == TaskType.出库);
|
|
|
+ if (srm.Data3.SCAlarm == 0 && srm.Data2.SRMMode == WCS.Entity.Protocol.SRM.SCMode.远程 && task)
|
|
|
+ throw new DoException("SRM7正在执行出库任务");
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception e)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WarnDb(e.Message, Entity.CODE);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 异常处理
|
|
|
+ /// </summary>
|
|
|
+ public static class DevEX
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// 计算两点距离
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="start">起始点</param>
|
|
|
+ /// <param name="end">结束点</param>
|
|
|
+ /// <param name="total">总长</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ public static float Distance(float start, float end, float total)
|
|
|
+ {
|
|
|
+ float distance = 0;
|
|
|
+ if (start > end) distance = (total - start) + end;
|
|
|
+ else distance = end - start;
|
|
|
+ return distance;
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void DoExceptionEX(this DoException ex, WCS_DEVICE Entity)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_INFO($"[{Entity.CODE}]--{ex.Message}");
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 警报执行记录
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="ex">警报信息</param>
|
|
|
+ /// <param name="Entity">发生设备</param>
|
|
|
+ /// <param name="reportMonitor">是否上报监控</param>
|
|
|
+ /// <exception cref="Exception"></exception>
|
|
|
+ public static void WarnExceptionEX(this WarnException ex, WCS_DEVICE Entity, bool reportMonitor = true)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_WARN($"[{Entity.CODE}]--{ex.Message}");
|
|
|
+ if (ex.Message.Contains("The database operation was expected")) return;
|
|
|
+
|
|
|
+ LogHelper.AddWCS_EXCEPTION(ex.Message, Entity.CODE, WCS_EXCEPTIONTYPE.无.ToString());
|
|
|
+ //排除部分频繁触发的异常上报
|
|
|
+ if (ex.Message.Contains("触发并发管控")) return;
|
|
|
+
|
|
|
+ if (reportMonitor)
|
|
|
+ {
|
|
|
+ Ltc.Log(ex.GetBaseException().Message);
|
|
|
+ throw new Exception($"[{Entity.CODE}]--{ex.Message}");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static void ExceptionEx(this Exception ex, WCS_DEVICE Entity)
|
|
|
+ {
|
|
|
+ InfoLog.INFO_ERROR($"[{Entity.CODE}]--{ex.Message}--{ex.StackTrace}");
|
|
|
+ //排除部分频繁触发的异常上报
|
|
|
+ if (ex.Message.Contains("Collection was modified; enumeration operation may not execute.")) return;
|
|
|
+ Ltc.Log(ex.GetBaseException().Message);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 堆垛机货叉/工位
|
|
|
+ /// </summary>
|
|
|
+ public enum SrmFork
|
|
|
+ {
|
|
|
+ 货叉1 = 0,
|
|
|
+ 货叉2 = 1,
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 站台位置信息
|
|
|
+ /// </summary>
|
|
|
+ public class StationLocation
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// 所有环穿站台的信息
|
|
|
+ /// </summary>
|
|
|
+ public static List<StationLocation> ALLlocations { get; set; } = new List<StationLocation>();
|
|
|
+
|
|
|
+ static StationLocation()
|
|
|
+ {
|
|
|
+ ALLlocations.AddRange(new List<StationLocation>() {
|
|
|
+ new StationLocation("G1",486326,"RGV3",1567770),
|
|
|
+ new StationLocation("G2",693631,"RGV3",1567770),
|
|
|
+ new StationLocation("G3",789931,"RGV3",1567770),
|
|
|
+ new StationLocation("G4",961595,"RGV3",1567770),
|
|
|
+ new StationLocation("G5",1013350,"RGV3",1567770),
|
|
|
+ new StationLocation("G6",1069938,"RGV3",1567770),
|
|
|
+ new StationLocation("G7",1126338,"RGV3",1567770),
|
|
|
+ new StationLocation("G8",1178355,"RGV3",1567770),
|
|
|
+ new StationLocation("G9",1256875,"RGV3",1567770),
|
|
|
+ new StationLocation("G10",1313239,"RGV3",1567770),
|
|
|
+ new StationLocation("G11",1369970,"RGV3",1567770),
|
|
|
+ new StationLocation("G12",636770,"RGV1",3719290),
|
|
|
+ new StationLocation("G13",749520,"RGV1",3719290),
|
|
|
+ new StationLocation("G14",879930,"RGV1",3719290),
|
|
|
+ new StationLocation("G15",936310,"RGV1",3719290),
|
|
|
+ new StationLocation("G16",988000,"RGV1",3719290),
|
|
|
+ new StationLocation("G19",1785000,"RGV1",3719290),
|
|
|
+ new StationLocation("G23",2714350,"RGV1",3719290),
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ public StationLocation(string station, int location, string plc, int length)
|
|
|
+ {
|
|
|
+ Station = station;
|
|
|
+ Location = location;
|
|
|
+ PLC = plc;
|
|
|
+ Length = length;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 输送机设备组编号
|
|
|
+ /// </summary>
|
|
|
+ public string Station { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 输送机在环轨中的位置
|
|
|
+ /// </summary>
|
|
|
+ public int Location { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 所属RGV组 PLC名称
|
|
|
+ /// </summary>
|
|
|
+ public string PLC { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 所属环穿轨道的长度
|
|
|
+ /// </summary>
|
|
|
+ public int Length { get; set; }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 巷道信息
|
|
|
+ /// </summary>
|
|
|
+ public class TunnelInfo
|
|
|
+ {
|
|
|
+ public WCS_DEVICE Tunnel;
|
|
|
+ public WCS_DEVICE taskIN;
|
|
|
+ public Device<ISRM520, ISRM521, ISRM537> SRM;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 设备配置
|
|
|
+ /// </summary>
|
|
|
+ [Flags]
|
|
|
+ public enum DF
|
|
|
+ {
|
|
|
+ 无 = 0,
|
|
|
+ SRM = 1 << 0,
|
|
|
+ SRM二级品取货 = 1 << 1,
|
|
|
+ SRM涂布取货 = 1 << 2,
|
|
|
+ SRM月台放货 = 1 << 3,
|
|
|
+ 一楼RGV放货 = 1 << 4,
|
|
|
+ 月台 = 1 << 5,
|
|
|
+ 涂布RGV = 1 << 6,
|
|
|
+ BOPPRGV = 1 << 7,
|
|
|
+ 涂布RGV取货设备组 = 1 << 8,
|
|
|
+ 涂布RGV放货设备组 = 1 << 9,
|
|
|
+ 涂布出库RGV取货站台 = 1 << 10,
|
|
|
+ 涂布入库RGV取货站台 = 1 << 11,
|
|
|
+ SRM涂布放货 = 1 << 12,
|
|
|
+ 涂布RGV取货站台 = 1 << 13,
|
|
|
+ BOPPRGV取货设备组 = 1 << 14,
|
|
|
+ BOPPRGV放货设备组 = 1 << 15,
|
|
|
+ SRMBOPP取货 = 1 << 16,
|
|
|
+ }
|
|
|
+}
|