林豪 左 2 år sedan
förälder
incheckning
744ab2934c
29 ändrade filer med 2396 tillägg och 5 borttagningar
  1. 9 2
      WCS Pedestal.sln
  2. 1 0
      WCS.Service/PLCAccessors/SiemensS7PLC.cs
  3. 1 3
      WCS.Service/WCS.Service.csproj
  4. 1402 0
      WCS.WorkEngineering/Extensions/DeviceExtension.cs
  5. 173 0
      WCS.WorkEngineering/Extensions/TaskExtension.cs
  6. 38 0
      WCS.WorkEngineering/Extensions/WCS_TaskExtensions.cs
  7. 19 0
      WCS.WorkEngineering/Handlers/BOPPHandler.cs
  8. 19 0
      WCS.WorkEngineering/Handlers/CoatingHandler.cs
  9. 54 0
      WCS.WorkEngineering/Handlers/DataClearHandler.cs
  10. 34 0
      WCS.WorkEngineering/Handlers/DeviceWork.cs
  11. 21 0
      WCS.WorkEngineering/Handlers/Handler.cs
  12. 20 0
      WCS.WorkEngineering/Handlers/ProductHandler.cs
  13. 16 0
      WCS.WorkEngineering/Handlers/RGVHandler.cs
  14. 16 0
      WCS.WorkEngineering/Handlers/SRMHandler.cs
  15. 25 0
      WCS.WorkEngineering/Handlers/UploadHandler.cs
  16. 33 0
      WCS.WorkEngineering/Handlers/Work.cs
  17. 150 0
      WCS.WorkEngineering/Handlers/WorkHandler.cs
  18. 88 0
      WCS.WorkEngineering/Helpers/FinishTaskList.cs
  19. 55 0
      WCS.WorkEngineering/Helpers/LogHelper.cs
  20. 27 0
      WCS.WorkEngineering/Helpers/SystemConfigHelpers.cs
  21. 40 0
      WCS.WorkEngineering/Uploader.cs
  22. 14 0
      WCS.WorkEngineering/WCS.WorkEngineering.csproj
  23. 3 0
      WCS.WorkEngineering/Works/RGV/RGVWorks.cs
  24. 22 0
      WCS.WorkEngineering/Works/SRM/SRMWork.cs
  25. 3 0
      WCS.WorkEngineering/Works/Station/BOPP入库.cs
  26. 104 0
      WCS.WorkEngineering/Works/Station/一楼入库.cs
  27. 3 0
      WCS.WorkEngineering/Works/Station/一楼出库.cs
  28. 3 0
      WCS.WorkEngineering/Works/Station/涂布入库.cs
  29. 3 0
      WCS.WorkEngineering/Works/Station/涂布出库.cs

+ 9 - 2
WCS Pedestal.sln

@@ -23,9 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.Service", "WCS.Service\
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.Virtual_PLC", "WCS.Virtual_PLC\WCS.Virtual_PLC.csproj", "{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.WebApi", "WCS.WebApi\WCS.WebApi.csproj", "{6FE23C07-7FFC-4CF7-A889-9B62957E0BA5}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.WebApi", "WCS.WebApi\WCS.WebApi.csproj", "{6FE23C07-7FFC-4CF7-A889-9B62957E0BA5}"
 EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.BaseExtensions", "WCS.BaseExtensions\WCS.BaseExtensions.csproj", "{CDF091B2-2A9C-4A2A-BCB7-C1E7C1B62F79}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.BaseExtensions", "WCS.BaseExtensions\WCS.BaseExtensions.csproj", "{CDF091B2-2A9C-4A2A-BCB7-C1E7C1B62F79}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.WorkEngineering", "WCS.WorkEngineering\WCS.WorkEngineering.csproj", "{87B00619-DA36-4469-B166-E7AAA9BB6123}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -73,6 +75,10 @@ Global
 		{CDF091B2-2A9C-4A2A-BCB7-C1E7C1B62F79}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{CDF091B2-2A9C-4A2A-BCB7-C1E7C1B62F79}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{CDF091B2-2A9C-4A2A-BCB7-C1E7C1B62F79}.Release|Any CPU.Build.0 = Release|Any CPU
+		{87B00619-DA36-4469-B166-E7AAA9BB6123}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{87B00619-DA36-4469-B166-E7AAA9BB6123}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{87B00619-DA36-4469-B166-E7AAA9BB6123}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{87B00619-DA36-4469-B166-E7AAA9BB6123}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -88,6 +94,7 @@ Global
 		{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2} = {43005E7B-7FC3-407D-B098-E58D0B5B465F}
 		{6FE23C07-7FFC-4CF7-A889-9B62957E0BA5} = {9D01B749-E4C4-4AD4-82E1-5C3CA65295E5}
 		{CDF091B2-2A9C-4A2A-BCB7-C1E7C1B62F79} = {43005E7B-7FC3-407D-B098-E58D0B5B465F}
+		{87B00619-DA36-4469-B166-E7AAA9BB6123} = {9D01B749-E4C4-4AD4-82E1-5C3CA65295E5}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {BF8ACC41-E6AD-46E2-8A85-2BD2AE0990C0}

+ 1 - 0
WCS.Service/PLCAccessors/SiemensS7PLC.cs

@@ -1,4 +1,5 @@
 using HslCommunication.Profinet.Siemens;
+using WCS.BaseExtensions;
 using WCS.Core;
 using WCS.Core.DataTrans;
 using WCS.Virtual_PLC;

+ 1 - 3
WCS.Service/WCS.Service.csproj

@@ -25,9 +25,7 @@
     <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="6.0.0" />
     <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.8" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
-    <PackageReference Include="WCS.Core" Version="1.0.6" />
-    <PackageReference Include="WCS.Entity.Protocol" Version="1.0.1" />
     <PackageReference Include="WCS.Virtual_PLC" Version="1.0.0" />
-    <PackageReference Include="WCS.WebApi" Version="1.0.1" />
+    <PackageReference Include="WCS.WorkEngineering" Version="1.0.0" />
   </ItemGroup>
 </Project>

+ 1402 - 0
WCS.WorkEngineering/Extensions/DeviceExtension.cs

@@ -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,
+    }
+}

+ 173 - 0
WCS.WorkEngineering/Extensions/TaskExtension.cs

@@ -0,0 +1,173 @@
+using DbHelper;
+using Log;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using WCS.BaseExtensions;
+using WCS.Core;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+using WCS.Service.Helpers;
+
+namespace WCS.Service.Extensions
+{
+    /// <summary>
+    /// 任务扩展
+    /// </summary>
+    public static class TaskExtension
+    {
+        public static T Create<T>(this WCS_TASK source)
+        {
+            return (T)Activator.CreateInstance(typeof(T), source);
+        }
+
+        /// <summary>
+        /// 获取可用的出库任务
+        /// </summary>
+        /// <param name="tasks"></param>
+        /// <returns></returns>
+        public static Task[] GetOutTask(this List<WCS_TASK> tasks)
+        {
+            var tasklist = tasks.Select(v => v.Create<Task>());
+            var task = tasklist.FirstOrDefault() ?? throw new WarnException("无可用出库任务--GetOutTask");
+            //AGV任务ID不为零表示为车间叫料任务
+            if (task.AGVTASKID != 0)
+            {
+                //按照AGV任务ID分一次组
+                tasklist = tasklist.OrderByDescending(v => v.Priority)
+                                   .ThenBy(v => v.CREATETIME)
+                                   .GroupBy(v => v.AGVTASKID)
+                                   .FirstOrDefault() ?? throw new WarnException("无可用叫料任务--GetOutTask");
+                //无论这个AGV任务绑定的货物相隔多远都必须要一起出出去
+                return tasklist.OrderBy(v => v.Col).ToArray();
+            }
+            return tasklist.OrderByDescending(v => v.Priority)
+                           .GroupBy(v => v.MaterialCode)
+                           .OrderBy(v => v.Key).FirstOrDefault()
+                           .OrderByDescending(v => v.Priority)
+                           .ThenBy(v => v.Line)
+                           .ThenBy(v => v.Layer)
+                           .ThenBy(v => v.Col)
+                           .Take(2)
+                           .DistinctBy(v => v.Col)
+                           .OrderBy(v => v.Col)
+                           .ToArray();
+        }
+
+        /// <summary>
+        /// 获取出库任务的站台号及下一个地址
+        /// </summary>
+        /// <param name="task">任务</param>
+        /// <param name="srmFork">货叉</param>
+        public static void GetSrmStationAndaddNext(this WCS_TASK task, SrmFork srmFork)
+        {
+            //取任务巷道到达目标地址的下一个地址,即任务堆垛机的站台对应的设备组
+            var stations = Device.Where(v => v.DEVICEGROUP.Any(p => p.MEMBER == Device.Find(task.TUNNEL).GetPath(task.ADDRTO.Replace("G", ""))))
+                                 .Select(v => v.Create<StationDeviceGroup>())
+                                 .FirstOrDefault()?.Items
+                                 .OrderByDescending(v => v.Entity.CODE)
+                                 .ToArray();
+            //一工位放较大的站台号
+            switch (srmFork)
+            {
+                case SrmFork.货叉1:
+                    task.SRMSTATION = stations?[0].Entity.CODE;
+                    task.ADDRNEXT = stations?[0].Entity.GetPath(task.ADDRTO).CODE;
+                    break;
+
+                case SrmFork.货叉2:
+                    task.SRMSTATION = stations?[1].Entity.CODE;
+                    task.ADDRNEXT = stations?[1].Entity.GetPath(task.ADDRTO).CODE;
+                    break;
+            }
+        }
+
+        /// <summary>
+        /// 有效任务数是否符合任务组任务数
+        /// </summary>
+        /// <param name="tasks"></param>
+        /// <param name="executable"></param>
+        /// <param name="db"></param>
+        public static void ValidTaskCheck(this List<WCS_TASK> tasks, int executable, Db db)
+        {
+            var task = tasks.FirstOrDefault();
+            var taskCount = db.Default.Queryable<WCS_TASK>().Count(v => v.TaskGroupKey == task.TaskGroupKey && v.TYPE == TaskType.入库);
+            //开始检查任务数是否匹配
+            if (executable != taskCount) throw new WarnException($"可执行数{executable},任务组任务数{taskCount},数量不匹配,{task.ID}-{task.TaskGroupKey}");
+        }
+
+        /// <summary>
+        /// 有效任务数是否符合任务组任务数  临时
+        /// </summary>
+        /// <param name="tasks"></param>
+        /// <param name="executable"></param>
+        /// <param name="db"></param>
+        public static List<WCS_TASK> ValidTaskCheck(this List<FinishTaskList<string>> devs, Db db)
+        {
+            var taskIds = devs.Select(v => v.Station.Data2.Tasknum).ToList();
+            var taskList = db.Default.Queryable<WCS_TASK>().Where(v => taskIds.Contains(v.ID)).ToList();
+            var task = taskList.FirstOrDefault() ?? throw new WarnException($"ValidTaskCheck 无任务"); ;
+            var taskCount = db.Default.Queryable<WCS_TASK>().Count(v => v.TaskGroupKey == task.TaskGroupKey && v.TYPE == TaskType.入库);
+            //开始检查任务数是否匹配
+            if (devs.Count != taskCount) throw new WarnException($"可执行数{devs.Count},任务组任务数{taskCount},数量不匹配,{task.ID}-{task.TaskGroupKey}");
+            return taskList;
+        }
+
+        /// <summary>
+        /// 用于任务创建时获取放货站台
+        /// </summary>
+        /// <param name="task"></param>
+        public static void TaskGetSrmStation(this WCS_TASK task)
+        {
+            task.GetSrmStationAndaddNext(SrmFork.货叉1);
+            task.SRMSTATION = Device.Where(v => v.IsDevGroup()).FirstOrDefault(v => v.DEVICEGROUP.Any(b => b.MEMBER.CODE == task.SRMSTATION))?.CODE;
+            task.ADDRNEXT = string.Empty;
+        }
+
+        public static void AGVStatusChange(this WCS_AGVTask task, AGVTaskStatus status, string type = "同步")
+        {
+            InfoLog.INFO_AGV($"AGV状态更新Status:{task.Status},AGVStatus:{status},{type}");
+        }
+    }
+
+    public enum SrmIndex
+    {
+        工位一 = 0,
+        工位二 = 1,
+    }
+
+    public class Task : WCS_TASK
+    {
+        /// <summary>
+        /// 行
+        /// </summary>
+        public short Line { get; set; }
+
+        /// <summary>
+        /// 列
+        /// </summary>
+        public short Col { get; set; }
+
+        /// <summary>
+        /// 层
+        /// </summary>
+        public short Layer { get; set; }
+
+        public Task(WCS_TASK task)
+        {
+            var addrFrom = task.ADDRFROM.Split("-");
+            ADDRTO = task.ADDRTO;
+            TUNNEL = task.TUNNEL;
+            DEVICE = task.DEVICE;
+            Priority = task.Priority;
+            AGVTASKID = task.AGVTASKID;
+            CREATETIME = task.CREATETIME;
+            MaterialCode = task.MaterialCode;
+            SRMSTATION = task.SRMSTATION;
+            ID = task.ID;
+            Line = addrFrom[0].ToShort();
+            Col = addrFrom[1].ToShort();
+            Layer = addrFrom[2].ToShort();
+        }
+    }
+}

+ 38 - 0
WCS.WorkEngineering/Extensions/WCS_TaskExtensions.cs

@@ -0,0 +1,38 @@
+using DbHelper;
+using System;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+
+namespace WCS.Service.Extensions
+{
+    public static class WCS_TaskExtensions
+    {
+        /// <summary>
+        /// 任务发生状态变更时,为任务新增状态变更记录
+        /// </summary>
+        /// <param name="task">对应任务</param>
+        /// <param name="db">db链接, 没有从新构造一个db的原因是为了保证状态记录的增加与任务状态的变更是同步的</param>
+        /// <param name="msg">需要记录的消息</param>
+        public static void CreateStatusLog(this WCS_TASK task, Db db, string msg, Type cl)
+        {
+            try
+            {
+                WCS_StatusLog statusLog = new WCS_StatusLog()
+                {
+                    WCS_TASKID = task.ID,
+                    NewStatus = task.STATUS,
+                    UPDATETIME = DateTime.Now,
+                    upStatus = task.UPLOADED,
+                    Node = cl.FullName,
+                    UPDATEUSER = "WCS",
+                    msg = msg,
+                };
+                db.Default.Insertable(statusLog).ExecuteCommand();
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine(e.Message);
+            }
+        }
+    }
+}

+ 19 - 0
WCS.WorkEngineering/Handlers/BOPPHandler.cs

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+using WCS.Core;
+
+namespace WCS.Service.Handlers
+{
+    /// <summary>
+    /// BOPP处理中心
+    /// </summary>
+    [Description("BOPP处理中心")]
+    internal class BOPPHandler : WorkHandler
+    {
+        public override bool ParallelRun => false;
+
+        protected override void DoWork(WorkInfo work)
+        {
+            base.DoWork(work);
+        }
+    }
+}

+ 19 - 0
WCS.WorkEngineering/Handlers/CoatingHandler.cs

@@ -0,0 +1,19 @@
+using System.ComponentModel;
+using WCS.Core;
+
+namespace WCS.Service.Handlers
+{
+    /// <summary>
+    /// 涂布处理中心
+    /// </summary>
+    [Description("涂布处理中心")]
+    internal class CoatingHandler : WorkHandler
+    {
+        public override bool ParallelRun => false;
+
+        protected override void DoWork(WorkInfo work)
+        {
+            base.DoWork(work);
+        }
+    }
+}

+ 54 - 0
WCS.WorkEngineering/Handlers/DataClearHandler.cs

@@ -0,0 +1,54 @@
+using DbHelper;
+using SqlSugar;
+using System;
+using System.ComponentModel;
+using WCS.BaseExtensions;
+using WCS.Core;
+using WCS.Entity;
+
+namespace WCS.Service
+{
+    [Description("数据清理")]
+    internal class DataClearHandler : LogicHandler
+    {
+        public override void Start()
+        {
+        }
+
+        private DateTime last = DateTime.MinValue;
+
+        public override void Update(double milliseconds)
+        {
+            if ((DateTime.Now - last).TotalMilliseconds < 1000 * 60 * 60 * 24)
+                return;
+            last = DateTime.Now;
+            Db.Do(db =>
+            {
+                db.Default.Deleteable<WCS_EXCEPTION>().Where(v => SqlFunc.DateDiff(DateType.Day, v.UPDATETIME, SqlFunc.GetDate()) > 7).AddQueue();
+                //db.Default.Ado.UseStoredProcedure().ExecuteCommand("DELETE FROM wcs_exception WHERE TIMESTAMPDIFF(DAY,UPDATETIME,NOW()) > 7");
+
+                var ps = db.Default.GetType().GetProperties();
+                foreach (var p in ps)
+                {
+                    if (!p.PropertyType.IsGenericType)
+                        continue;
+                    var tType = p.PropertyType.GenericTypeArguments[0];
+                    if (!tType.IsSubclassOf(typeof(WCS_PROTOCOLDATA)))
+                        continue;
+                    var sSql = $"Delete FROM {tType.Name} where TIMESTAMPDIFF(DAY,Frame,NOW())>7";
+                    db.Default.Ado.UseStoredProcedure().ExecuteCommand(sSql);
+                    DbHelper.DbLog.DB_CLEAN(sSql);
+                }
+                var taskList = db.Default.Queryable<WCS_TASK>().Where(v => SqlFunc.DateDiff(DateType.Day, v.UPDATETIME, SqlFunc.GetDate()) > 3 && v.STATUS >= TaskStatus.已完成).ToList();
+
+                foreach (var task in taskList)
+                {
+                    var taskOld = TypeExtension.Mapper<WCS_TASK_OLD, WCS_TASK>(task);
+                    db.Default.Insertable(taskOld).AddQueue();
+                    db.Default.Deleteable(task).AddQueue();
+                }
+                db.Default.SaveQueues();
+            });
+        }
+    }
+}

+ 34 - 0
WCS.WorkEngineering/Handlers/DeviceWork.cs

@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using WCS.Core;
+using WCS.Entity;
+
+namespace WCS.Service.Handlers
+{
+    /// <summary>
+    /// 设备工作器
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public abstract class DeviceWork<T> : Work<T> where T : EntityEx<WCS_DEVICE>
+    {
+        private readonly string[] _typenames;
+
+        protected DeviceWork()
+        {
+            _typenames = typeof(T).GenericTypeArguments.Select(v => v.AssemblyQualifiedName).ToArray();
+        }
+
+        protected abstract override void Do(T obj);
+
+        protected override sealed IEnumerable<T> InitObjects()
+        {
+            var arr = Device.Where(v => v.ENABLED && v.DEVICEPROTOCOLS.All(d => d.ENABLED && d.DB.ENABLED && d.DB.PLC.ENABLED))
+                .Where(v => _typenames.All(d => v.DEVICEPROTOCOLS.Any(e => e.DB.PROTOCOL == d)))
+                .Where(SelectDevice).ToArray();
+            var res = arr.Select(v => Activator.CreateInstance(typeof(T), v) as T);
+
+            return res;
+        }
+    }
+}

+ 21 - 0
WCS.WorkEngineering/Handlers/Handler.cs

@@ -0,0 +1,21 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WCS.Core;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+
+namespace WCS.Service.Handlers
+{
+    public abstract class Handler<T> : LogicHandler<T> where T : EntityEx<WCS_DEVICE>
+    {
+        protected virtual bool Parallel { get { return false; } }
+        protected abstract bool Selecte(WCS_DEVICE obj);
+        public override void Start()
+        {
+            AddWork(v => Selecte(v.Entity), Execute, Parallel);
+        }
+    }
+}

+ 20 - 0
WCS.WorkEngineering/Handlers/ProductHandler.cs

@@ -0,0 +1,20 @@
+using System.ComponentModel;
+using WCS.Core;
+using WCS.Service.Handlers;
+
+namespace WCS.Service
+{
+    /// <summary>
+    /// 二级品及月台发货
+    /// </summary>
+    [Description("一楼出入库处理中心")]
+    internal class ProductHandler : WorkHandler
+    {
+        public override bool ParallelRun => false;
+
+        protected override void DoWork(WorkInfo work)
+        {
+            base.DoWork(work);
+        }
+    }
+}

+ 16 - 0
WCS.WorkEngineering/Handlers/RGVHandler.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WCS.Core;
+
+namespace WCS.Service.Handlers
+{
+    [Description("穿梭车")]
+    class RGVHandler : WorkHandler
+    {
+        public override bool ParallelRun => true;
+    }
+}

+ 16 - 0
WCS.WorkEngineering/Handlers/SRMHandler.cs

@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WCS.Core;
+
+namespace WCS.Service.Handlers
+{
+    [Description("堆垛机")]
+    class SRMHandler : WorkHandler
+    {
+        public override bool ParallelRun =>true;
+    }
+}

+ 25 - 0
WCS.WorkEngineering/Handlers/UploadHandler.cs

@@ -0,0 +1,25 @@
+using DbHelper;
+using System;
+using System.ComponentModel;
+using WCS.Core;
+
+namespace WCS.Service.Handlers
+{
+    [Description("任务上传")]
+    internal class UploadHandler : LogicHandler
+    {
+        public override void Start()
+        {
+        }
+
+        private DateTime last = DateTime.MinValue;
+
+        public override void Update(double milliseconds)
+        {
+            if ((DateTime.Now - last).TotalMilliseconds < 10000)
+                return;
+            last = DateTime.Now;
+            Db.Do(Uploader.Upload);
+        }
+    }
+}

+ 33 - 0
WCS.WorkEngineering/Handlers/Work.cs

@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using WCS.Core;
+using WCS.Entity;
+
+namespace WCS.Service.Handlers
+{
+    public abstract class Work<T> : Work
+    {
+        public override sealed void Execute(object obj)
+        {
+            Do((T)obj);
+        }
+
+        public override sealed IEnumerable<object> GetObjs()
+        {
+            return InitObjects().OfType<object>().ToArray();
+        }
+
+        protected abstract void Do(T obj);
+
+        protected abstract bool SelectDevice(WCS_DEVICE dev);
+
+        protected virtual IEnumerable<T> InitObjects()
+        {
+            var arr = Device.Where(v => v.ENABLED)
+                .Where(SelectDevice).ToArray();
+            var res = arr.Select(v => (T)Activator.CreateInstance(typeof(T), v));
+            return res;
+        }
+    }
+}

+ 150 - 0
WCS.WorkEngineering/Handlers/WorkHandler.cs

@@ -0,0 +1,150 @@
+using Log;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Threading.Tasks;
+using WCS.BaseExtensions;
+using WCS.Core;
+using WCS.Entity;
+
+namespace WCS.Service.Handlers
+{
+    /// <summary>
+    /// 工作处理器
+    /// </summary>
+    public abstract class WorkHandler : LogicHandler
+    {
+        /// <summary>
+        /// 所有被申明的工作处理器
+        /// </summary>
+        protected List<WorkInfo> Works = new List<WorkInfo>();
+
+        protected WorkHandler()
+        {
+            //所有被声明的处理器
+            var arr = Assembly.GetEntryAssembly()
+                ?.GetTypes().Where(v => v.IsSubclassOf(typeof(Work))).Where(v =>
+                {
+                    var attr = v.GetCustomAttribute<WorkTitleAttribute>();
+                    if (attr == null)
+                        return false;
+                    return attr.Handler == this.GetType();
+                });
+            //TODO:加一层关于仓库的管控,通过
+            var works = arr.Select(v => Activator.CreateInstance(v) as Work).Select(v =>
+            {
+                var attr = v.GetType().GetCustomAttribute<WorkTitleAttribute>();
+                return new WorkInfo { Params = v.GetObjs(), Work = v.Execute, Title = attr.Title, Parallel = attr.Parallel };
+            }).ToArray();
+            Works.AddRange(works);
+        }
+
+        public override sealed void Start()
+        {
+        }
+
+        /// <summary>
+        /// 执行处理中心--中心级
+        /// </summary>
+        /// <param name="milliseconds"></param>
+        public override void Update(double milliseconds)
+        {
+            if (ParallelRun)
+            {
+                Parallel.ForEach(Works, DoWork);
+            }
+            else
+            {
+                foreach (var w in Works)
+                {
+                    DoWork(w);
+                }
+            }
+        }
+
+        /// <summary>
+        /// 执行级
+        /// </summary>
+        /// <param name="work"></param>
+        protected virtual void DoWork(WorkInfo work)
+        {
+            if (work.Parallel)
+            {
+                Parallel.ForEach(work.Params, p =>
+                {
+                    Do(work, p);
+                });
+            }
+            else
+            {
+                foreach (var p in work.Params)
+                {
+                    Do(work, p);
+                }
+            }
+        }
+
+        protected virtual void Do(WorkInfo wi, object p)
+        {
+            var dt = DateTime.Now;
+            var channel = Description + "." + wi.Title + "." + p;
+            var code = "";
+            if (p is IProtocol protocol)
+            {
+                code = protocol.PROTOCOL().DEVICE.CODE;
+            }
+            try
+            {
+                Ltc.SetChannel(channel);
+                Ltc.Log("开始---------------------------------------");
+                wi.Work(p);
+            }
+            //下述日志处理方案可根据项目情况自定义
+            //DoException为基础条件未满足,仅作记录文本日志
+            catch (DoException ex)
+            {
+                InfoLog.INFO_INFO($"[{code}]--{ex.Message}");
+            }
+            //WarnException进阶条件未满足,添加数据库,记录文本日志、数据库,上抛WCS,上抛WMS
+            catch (WarnException ex)
+            {
+                if (ex.RECORDTXT)
+                {
+                    InfoLog.INFO_WARN($"[{code}]--{ex.Message}");
+                }
+                if (ex.RECORDDB)
+                {
+                    Helpers.LogHelper.AddWCS_EXCEPTION(ex.Message, code, WCS_EXCEPTIONTYPE.无.ToString());
+                }
+                if (ex.REPORTWCS)
+                {
+                    Ltc.Log(ex.GetBaseException().Message);
+                }
+                if (ex.REPORTWMS)
+                {
+                    Configs.UploadException?.Invoke(p.ToString(), ex.GetBaseException().Message);
+                }
+            }
+            //未知异常,仅记录文本日志,需定期排查该文件,检查系统是否有未知异常,并处理
+            catch (Exception ex)
+            {
+                InfoLog.INFO_ERROR($"[{code}]--{ex.Message}--{ex.StackTrace}");
+            }
+            finally
+            {
+                var dd = (DateTime.Now - dt).TotalMilliseconds;
+                if (dd > 500)
+                {
+                    Console.ForegroundColor = ConsoleColor.Red;
+                    Console.WriteLine(channel + "耗时" + dd);
+                    Console.ResetColor();
+                }
+
+                if (dd > 10000)
+                    Configs.UploadException?.Invoke(p.ToString(), wi.Title + "执行耗时" + Math.Floor(dd / 1000) + "秒");
+                Ltc.Log("结束\n");
+            }
+        }
+    }
+}

+ 88 - 0
WCS.WorkEngineering/Helpers/FinishTaskList.cs

@@ -0,0 +1,88 @@
+using System.Collections.Generic;
+using System.Linq;
+using WCS.BaseExtensions;
+using WCS.Service.Extensions;
+using WCS.WebApi.WMS;
+using WCS.WebApi.WMS.Response;
+
+namespace WCS.Service.Helpers
+{
+    /// <summary>
+    /// 处理完成任务记录集合
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class FinishTaskList<T>
+    {
+        public FinishTaskList(T finishCode, StationDevice station)
+        {
+            FinishCode = finishCode;
+            Station = station;
+        }
+
+        /// <summary>
+        /// 完成
+        /// </summary>
+        public T FinishCode { get; set; }
+
+        /// <summary>
+        /// 对应设备信息
+        /// </summary>
+        public StationDevice Station { get; set; }
+    }
+
+    /// <summary>
+    /// 处理完成任务记录集合
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class FinishTaskList<T, T1>
+    {
+        public FinishTaskList(T finishCode, T1 station)
+        {
+            FinishCode = finishCode;
+            Station = station;
+        }
+
+        /// <summary>
+        /// 完成
+        /// </summary>
+        public T FinishCode { get; set; }
+
+        /// <summary>
+        /// 对应设备信息
+        /// </summary>
+        public T1 Station { get; set; }
+    }
+
+    public static class FinishTaskListExtensions
+    {
+        /// <summary>
+        /// 入库可用任务数是否有效
+        /// </summary>
+        /// <param name="finishes"></param>
+        /// <exception cref="WarnException"></exception>
+        public static void Valid(this List<FinishTaskList<string>> finishes)
+        {
+            var maxGoodsnum = finishes.Select(v => v.Station.Data2.Goodsnum).OrderByDescending(v => v).FirstOrDefault();
+            if (finishes.Count != maxGoodsnum) throw new WarnException($"可用货物数{finishes.Count},实际货物数{maxGoodsnum}");
+            if (!finishes.Any()) throw new DoException("没有任务");
+        }
+
+        /// <summary>
+        /// 入库可用任务数是否有效
+        /// </summary>
+        /// <param name="finishes"></param>
+        /// <exception cref="WarnException"></exception>
+        public static List<I_WCS_GetInTaskResponseItem> GetWMSInTask(this List<FinishTaskList<string>> finishes)
+        {
+            if (!finishes.Any()) throw new DoException("没有任务");
+            var items = finishes.ToArray();
+            var infos = items.Length switch
+            {
+                1 => WMS.I_WCS_GetInTask(items[0].FinishCode, items[0].Station.Entity.CODE),
+                2 => WMS.I_WCS_GetInTask(items[0].FinishCode, items[0].Station.Entity.CODE, items[1].FinishCode, items[1].Station.Entity.CODE),
+                _ => throw new WarnException($"一组任务数量最大为2,当前{items.Length}"),
+            };
+            return infos;
+        }
+    }
+}

+ 55 - 0
WCS.WorkEngineering/Helpers/LogHelper.cs

@@ -0,0 +1,55 @@
+using DbHelper;
+using Log;
+using SqlSugar;
+using System;
+using WCS.Entity;
+
+namespace WCS.Service.Helpers
+{
+    public class LogHelper
+    {
+        /// <summary>
+        /// 添加异常记录
+        /// </summary>
+        /// <param name="msg">异常信息</param>
+        /// <param name="device">异常关联设备</param>
+        /// <param name="type">异常类型 关联WCS_EXCEPTIONTYPE枚举</param>
+        public static void AddWCS_EXCEPTION(string msg, string device, string type)
+        {
+            Db.Do(db =>
+            {
+                var exp = db.Default.Queryable<WCS_EXCEPTION>()
+                    .Where(v => SqlFunc.DateDiff(DateType.Second, v.UPDATETIME, DateTime.Now) < 5)
+                    .Where(v => v.MSG == msg)
+                    .OrderByDescending(v => v.ID)
+                    .First() ?? new WCS_EXCEPTION
+                    {
+                        MSG = msg,
+                        DEVICECODE = device,
+                        EXCEPTIONTYPE = type,
+                        STARTTIME = DateTime.Now,
+                        TIMES = 0,
+                        UPDATETIME = DateTime.Now,
+                        UPDATEUSER = "WCS"
+                    };
+
+                exp.TIMES++;
+                exp.UPDATETIME = DateTime.Now;
+                db.Default.Storageable(exp).ExecuteCommand();
+            });
+        }
+
+        /// <summary>
+        /// 拼接日志消息 ,避免返回异常时写错导致异常
+        /// </summary>
+        /// <param name="msg">异常信息</param>
+        /// <param name="device">异常关联设备</param>
+        /// <param name="type">异常类型</param>
+        /// <returns></returns>
+        public static string SpliceLogMessage(string msg, string device, WCS_EXCEPTIONTYPE type, Type type1)
+        {
+            InfoLog.INFO_ERROR($"{type1.FullName}--{msg}--{device}--{type}");
+            return $"{msg}|{device}|{type}";
+        }
+    }
+}

+ 27 - 0
WCS.WorkEngineering/Helpers/SystemConfigHelpers.cs

@@ -0,0 +1,27 @@
+using DbHelper;
+using WCS.Entity.Protocol;
+
+namespace WCS.Service.Helpers
+{
+    /// <summary>
+    /// 系统设置
+    /// </summary>
+    public class SystemConfigHelpers
+    {
+        /// <summary>
+        /// 获取指定设备配置信息 true表示设备启用,false标识设备未启用
+        /// </summary>
+        /// <param name="DEVICECODE">设备编号</param>
+        /// <returns></returns>
+        public static bool GetDeviceConfig(string DEVICECODE)
+        {
+            bool result = true;
+            Db.Do(db =>
+            {
+                var systemConfig = db.Default.Queryable<WCS_SystemConfig>().Single(p => p.DEVICECODE == DEVICECODE);
+                if (systemConfig != null) result = systemConfig.ENABLED;
+            });
+            return result;
+        }
+    }
+}

+ 40 - 0
WCS.WorkEngineering/Uploader.cs

@@ -0,0 +1,40 @@
+using DbHelper;
+using Log;
+using System;
+using WCS.Entity;
+using WCS.WebApi.WMS;
+
+namespace WCS.Service
+{
+    public class Uploader
+    {
+        public static void Upload(Db db)
+        {
+            try
+            {
+                var tasks = db.Default.Queryable<WCS_TASK>().Where(v => v.WMSTASK > 0)
+                    .Where(v => v.STATUS != v.UPLOADED).ToArray();
+                foreach (var task in tasks)
+                {
+                    try
+                    {
+                        WMS.UpdateTask(task.ADDRNEXT, task.WMSTASK, (int)task.STATUS, task.HEIGHT);
+                        var st = task.UPLOADED;
+                        task.UPLOADED = task.STATUS;
+                        InfoLog.INFO_SYTASKSTATUS($"[{task.ID}]---old:[{st}]-new:[{task.UPLOADED}]---{task.HEIGHT}");
+                    }
+                    catch (Exception ex)
+                    {
+                        Console.WriteLine($"上传任务状态失败:WCS任务号{task.ID},{ex.Message}");
+                    }
+                }
+
+                db.Default.Updateable(tasks).ExecuteCommand();
+            }
+            catch
+            {
+                // ignored
+            }
+        }
+    }
+}

+ 14 - 0
WCS.WorkEngineering/WCS.WorkEngineering.csproj

@@ -0,0 +1,14 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>.net6</TargetFramework>
+    <Nullable>enable</Nullable>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+    <Description>调度程序的业务逻辑</Description>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="WCS.WebApi" Version="1.0.1" />
+  </ItemGroup>
+
+</Project>

+ 3 - 0
WCS.WorkEngineering/Works/RGV/RGVWorks.cs

@@ -0,0 +1,3 @@
+namespace WCS.Service.Works.RGV
+{
+}

+ 22 - 0
WCS.WorkEngineering/Works/SRM/SRMWork.cs

@@ -0,0 +1,22 @@
+using WCS.Core;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+using WCS.Service.Extensions;
+using WCS.Service.Handlers;
+
+namespace WCS.Service.Works.SRM
+{
+    [WorkTitle(typeof(SRMHandler), "堆垛机")]
+    internal class SRMWork : DeviceWork<Device<ISRM520, ISRM521, ISRM537>>
+    {
+        protected override void Do(Device<ISRM520, ISRM521, ISRM537> obj)
+        {
+            //obj.Data.ECol_1 = (short)22l;
+        }
+
+        protected override bool SelectDevice(WCS_DEVICE dev)
+        {
+            return dev.Is(DF.SRM);
+        }
+    }
+}

+ 3 - 0
WCS.WorkEngineering/Works/Station/BOPP入库.cs

@@ -0,0 +1,3 @@
+namespace WCS.Service.Works.Station
+{
+}

+ 104 - 0
WCS.WorkEngineering/Works/Station/一楼入库.cs

@@ -0,0 +1,104 @@
+using DbHelper;
+using Log;
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using WCS.BaseExtensions;
+using WCS.Core;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+using WCS.Service.Extensions;
+using WCS.Service.Handlers;
+using WCS.Service.Helpers;
+
+namespace WCS.Service.Works.Station
+{
+    [WorkTitle(typeof(ProductHandler), "扫码入库")]
+    internal class 扫码入库 : Work<StationDeviceGroup>
+    {
+        protected override void Do(StationDeviceGroup obj)
+        {
+            var timer = new Stopwatch();
+            timer.Start();
+            obj.EX(obj =>
+            {
+                obj.BcrStationIsForbid();
+                //设备组无论单卷还是双卷都必须满足的条件
+                if (obj.Items.Any(v => v.Data.VoucherNo != v.Data2.VoucherNo)) throw new WarnException($"等待任务执行--凭证号不一致");
+                if (obj.Items.Any(v => v.Data3.Status.HasFlag(StationStatus.运行状态位))) throw new WarnException($"设备运行中");
+
+                //成功创建的任务
+                var finishTaskList = new List<FinishTaskList<int>>();
+
+                //创建对应的任务
+                Db.Do(db =>
+                {
+                    var devs = obj.GetBcrValid();
+                    devs.Valid();
+                    var infos = devs.GetWMSInTask();
+
+                    foreach (var item in devs)
+                    {
+                        var dev = item.Station;
+                        var next = dev.Entity.GetPath("SRM").CODE;
+                        var info = infos.FirstOrDefault(v => item.FinishCode.Contains(v.ContainerCode + "}"));
+                        if (db.Default.Queryable<WCS_TASK>().Any(v => v.BARCODE == info.ContainerCode && v.STATUS < TaskStatus.已完成 && v.TYPE == TaskType.入库))
+                            throw new WarnException($"生产条码{info.ContainerCode}存在未完成任务,请检查是否为标签卡重复使用");
+
+                        var task = new WCS_TASK
+                        {
+                            BARCODE = info.ContainerCode,
+                            TYPE = TaskType.入库,
+                            STATUS = TaskStatus.执行中,
+                            ADDRFROM = dev.Entity.CODE,
+                            ADDRTO = info.EndPostion,
+                            STARTTIME = DateTime.Now,
+                            UPDATEUSER = "WCS",
+                            UPDATETIME = DateTime.Now,
+                            WMSTASK = int.Parse(info.WMSTaskNum),
+                            TaskGroupKey = info.TaskGroupKey,
+                            ADDRNEXT = next,
+                            HEIGHT = dev.Data2.GoodsSize,
+                            FLOOR = 1
+                        };
+
+                        db.Default.Updateable(task).AddQueue();
+                        finishTaskList.Add(new FinishTaskList<int>(task.WMSTASK, item.Station));
+
+                        var msg = $"下达从{dev.Entity.CODE}移动至{next}的PLC指令。";
+                        msg += $"[{dev.Data.Tasknum}][{dev.Data.Goodsstart}][{dev.Data.Goodsend}][{dev.Data.VoucherNo}[{dev.Data2.VoucherNo}]";
+                        task.CreateStatusLog(db, msg, this.GetType());
+                    }
+                    //两个任务一起创建
+                    db.Default.SaveQueues();
+                });
+
+                //检查对应的任务是否已创建成功
+                Db.Do(db =>
+                {
+                    foreach (var finishTask in finishTaskList)
+                    {
+                        var task = db.Default.Queryable<WCS_TASK>().First(v => v.WMSTASK == finishTask.FinishCode);
+                        if (task == null) continue;
+
+                        finishTask.Station.Data.Tasknum = task.ID;
+                        finishTask.Station.Data.Goodsstart = task.ADDRFROM.ToShort();
+                        finishTask.Station.Data.Goodsend = task.ADDRNEXT.ToShort();
+                        finishTask.Station.Data.Goodsnum = finishTaskList.Count.ToShort();
+                        finishTask.Station.Data.CmdType = IstationCmdType.扫码入库;
+                        finishTask.Station.Data.VoucherNo++;
+                    }
+                });
+
+                timer.Stop();
+                InfoLog.INFO_TIMING($"{obj.Entity.CODE}--扫码入库,耗时{timer.ElapsedMilliseconds}");
+            });
+        }
+
+        protected override bool SelectDevice(WCS_DEVICE dev)
+        {
+            return dev.CODE == "G1028";
+        }
+    }
+}

+ 3 - 0
WCS.WorkEngineering/Works/Station/一楼出库.cs

@@ -0,0 +1,3 @@
+namespace WCS.Service.Works.Station
+{
+}

+ 3 - 0
WCS.WorkEngineering/Works/Station/涂布入库.cs

@@ -0,0 +1,3 @@
+namespace WCS.Service.Works.Station
+{
+}

+ 3 - 0
WCS.WorkEngineering/Works/Station/涂布出库.cs

@@ -0,0 +1,3 @@
+namespace WCS.Service.Works.Station
+{
+}