Explorar el Código

添加项目文件。

林豪 左 hace 1 año
padre
commit
6d05bc638f
Se han modificado 76 ficheros con 6730 adiciones y 0 borrados
  1. 21 0
      WCS.Service/AppSettings.cs
  2. 29 0
      WCS.Service/PLCAccessors/PLCAccessorsCreater.cs
  3. 52 0
      WCS.Service/PLCAccessors/SiemensS7PLC.cs
  4. 70 0
      WCS.Service/Program.cs
  5. 11 0
      WCS.Service/Properties/launchSettings.json
  6. 35 0
      WCS.Service/WCS.Service.csproj
  7. 173 0
      WCS.Service/Worker.cs
  8. 29 0
      WCS.Service/Worlds/initWorld.cs
  9. 8 0
      WCS.Service/appsettings.Development.json
  10. 13 0
      WCS.Service/appsettings.json
  11. 91 0
      WCS.WorkEngineering/Extensions/BCRExtension.cs
  12. 582 0
      WCS.WorkEngineering/Extensions/DeviceExtension.cs
  13. 43 0
      WCS.WorkEngineering/Extensions/FlagExtension.cs
  14. 12 0
      WCS.WorkEngineering/Extensions/RGVExtension.cs
  15. 26 0
      WCS.WorkEngineering/Extensions/RedisExtenshin.cs
  16. 60 0
      WCS.WorkEngineering/Extensions/SRMExtension.cs
  17. 615 0
      WCS.WorkEngineering/Extensions/TaskExtension.cs
  18. 12 0
      WCS.WorkEngineering/Extensions/TrussExtebsion.cs
  19. 95 0
      WCS.WorkEngineering/Extensions/WorldExtension.cs
  20. 9 0
      WCS.WorkEngineering/LockHub.cs
  21. 239 0
      WCS.WorkEngineering/ProtocolProxy.cs
  22. 1027 0
      WCS.WorkEngineering/Systems/DataCollectionSysyem.cs
  23. 44 0
      WCS.WorkEngineering/WCS.WorkEngineering.csproj
  24. 716 0
      WCS.WorkEngineering/WebApi/Controllers/WcsController.cs
  25. 28 0
      WCS.WorkEngineering/WebApi/Models/AGV/AgvResponseCode.cs
  26. 10 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/AddWcsMoveTaskRequest.cs
  27. 115 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/AgvCallbackRequest.cs
  28. 18 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/ApplyEmptySpoolRequest.cs
  29. 50 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/CancelTaskRequest.cs
  30. 68 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/ContinueTaskRequest.cs
  31. 70 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/CopperLineAgvTaskStockInToIWmsRequest.cs
  32. 95 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/CopperLineAgvTaskStockOutToIWmsRequest.cs
  33. 200 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/GenAgvSchedulingTaskRequest.cs
  34. 103 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/zhongTianIntoStockRequest.cs
  35. 78 0
      WCS.WorkEngineering/WebApi/Models/AGV/Request/zhongTianOutStockRequest.cs
  36. 29 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/AddWcsMoveTaskResponse.cs
  37. 35 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/ApplyEmptySpoolResponse.cs
  38. 23 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/CancelTaskResponse.cs
  39. 23 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/ContinueTaskResponse.cs
  40. 25 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/GenAgvSchedulingTaskResponse.cs
  41. 20 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/agvCallbackResponse.cs
  42. 28 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/zhongTianIntoStockResponse.cs
  43. 25 0
      WCS.WorkEngineering/WebApi/Models/AGV/Response/zhongTianOutStockResponse.cs
  44. 58 0
      WCS.WorkEngineering/WebApi/Models/WCS/Request/DevRunInfoRequest.cs
  45. 46 0
      WCS.WorkEngineering/WebApi/Models/WCS/Request/HandleTaskRequest.cs
  46. 57 0
      WCS.WorkEngineering/WebApi/Models/WCS/Request/PaginationRequest.cs
  47. 23 0
      WCS.WorkEngineering/WebApi/Models/WCS/Response/HandleTaskResponse.cs
  48. 56 0
      WCS.WorkEngineering/WebApi/Models/WCS/Response/PagedInfoResponse.cs
  49. 149 0
      WCS.WorkEngineering/WebApi/Models/WCS/Response/RunInfoViewMode.cs
  50. 32 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/ApplyStockInLocRequest.cs
  51. 20 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/ApplyStockOutTaskRequest.cs
  52. 18 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/CancelTaskVerifyRequest.cs
  53. 47 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/CompleteTaskRequest.cs
  54. 15 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/CompleteTaskVerifyRequest.cs
  55. 13 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/EnteMainLineRequest.cs
  56. 3 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/FJBingPalletRequest.cs
  57. 56 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/FJBuildEmptyPalletsStockRequest.cs
  58. 18 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/FJEnteMainLineRequest.cs
  59. 9 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/GetTunnelEmptyConCountRequest.cs
  60. 23 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/GetTunnelPriorityListRequest.cs
  61. 54 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/I_WCS_GetInTaskRequest.cs
  62. 60 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/OneFloorWorkerBuildEmptyPalletsStockRequest.cs
  63. 36 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/PalletizingCreateseErrorTasksRequest.cs
  64. 36 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/RingApplyPalletizingStockOutRequest.cs
  65. 13 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/SrmPickOutCompletedRequest.cs
  66. 23 0
      WCS.WorkEngineering/WebApi/Models/WMS/Request/WcsUploadInfoRequest.cs
  67. 33 0
      WCS.WorkEngineering/WebApi/Models/WMS/Response/ApplyStockInLocResponse.cs
  68. 18 0
      WCS.WorkEngineering/WebApi/Models/WMS/Response/GetTunnelEmptyConResponse.cs
  69. 46 0
      WCS.WorkEngineering/WebApi/Models/WMS/Response/I_WCS_GetInTaskResponse.cs
  70. 73 0
      WCS.WorkEngineering/WebApi/Models/WMS/Response/SRes.cs
  71. 11 0
      WCS.WorkEngineering/WebApi/Models/WMS/Response/WcsContractApiResponse.cs
  72. 65 0
      WCS.WorkEngineering/WebApi/Startup.cs
  73. 128 0
      WCS.WorkEngineering/WorkStart.cs
  74. 133 0
      WCS.WorkEngineering/Worlds/MainWorld.cs
  75. 252 0
      WCS.WorkEngineering/数据采集.cs
  76. 51 0
      时效库数据处理.sln

+ 21 - 0
WCS.Service/AppSettings.cs

@@ -0,0 +1,21 @@
+namespace WCS.Service
+{
+    /// <summary>
+    /// 程序设置
+    /// </summary>
+    public class AppSettings
+    {
+        /// <summary>
+        /// 配置信息
+        /// </summary>
+        public static IConfiguration Config { get; private set; }
+
+        /// <summary>
+        /// 静态构造函数
+        /// </summary>
+        static AppSettings()
+        {
+            Config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build();
+        }
+    }
+}

+ 29 - 0
WCS.Service/PLCAccessors/PLCAccessorsCreater.cs

@@ -0,0 +1,29 @@
+using WCS.Core;
+using PLCType = WCS.Core.PLCType;
+
+namespace WCS.Service.PLCAccessors
+{
+    /// <summary>
+    ///  PLC访问器创建者
+    /// </summary>
+    public class PLCAccessorsCreater : IPLCAccessorCreater
+    {
+        /// <summary>
+        ///  创建PLC访问器
+        /// </summary>
+        /// <param name="data">PLC信息</param>
+        /// <returns></returns>
+        /// <exception cref="Exception"> </exception>
+        public IPLCAccessor Create(PLCInfo data)
+        {
+            switch (data.Type)
+            {
+                case PLCType.Siemens:
+                    return new SiemensS7PLC(data.IP, data.Port, data.Rack, data.Slot);
+
+                default:
+                    return new VitrualRedisPLC(data, "127.0.0.1,database=1,prefix=Sorting:");
+            }
+        }
+    }
+}

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

@@ -0,0 +1,52 @@
+
+using PlcSiemens.O;
+using PlcSiemens.Protocol.Common;
+using ServiceCenter;
+using ServiceCenter.Virtual_PLC;
+using WCS.Core;
+
+namespace WCS.Service.PLCAccessors
+{
+    public class SiemensS7PLC : IPLCAccessor
+    {
+        private SimenssPlc plc;
+
+        public SiemensS7PLC(string ip, int port, int rack, int slot)
+        {
+            plc = new SimenssPlc(ip, rack, slot);
+            plc.Connect();
+        }
+
+        public byte[] ReadBytes(ushort db, ushort address, ushort length)
+        {
+            if (ServiceHub.Any(SystemMode.虚拟plc))
+            {
+                return PlcData.Read(new PLCData { IP = plc.IP, DB = db, Length = length, DataLength = length });
+            }
+
+            if (!plc.Connected)
+                plc.Connect();
+
+            var res = plc.ReadArea(AreaType.DB, db, address, length, DataType.Byte);
+            if (res == null)
+                throw new Exception("读取DB块数据失败");
+            return res.Data;
+        }
+
+        public void WriteBytes(ushort db, ushort address, byte[] data)
+        {
+            if (ServiceHub.Any(SystemMode.虚拟plc))
+            {
+                PlcData.Write(new PLCData { IP = plc.IP, DB = db }, address, data);
+            }
+            else
+            {
+                if (!plc.Connected)
+                    plc.Connect();
+                var res = plc.WriteArea(AreaType.DB, db, address, (ushort)data.Length, DataType.Byte, data);
+                if (!res) throw new Exception("写入DB块数据失败");
+
+            }
+        }
+    }
+}

+ 70 - 0
WCS.Service/Program.cs

@@ -0,0 +1,70 @@
+using Microsoft.AspNetCore.Hosting;
+using System.Runtime.InteropServices;
+using ServiceCenter.Extensions;
+using ServiceCenter.Redis;
+using ServiceCenter.WebApi;
+
+namespace WCS.Service
+{
+    public class Program
+    {
+        public static void Main(string[] args)
+        {
+            #region 接入Redis
+
+            RedisHub.CreateContext(AppSettings.Config.GetConnectionString("Redis"), "default", true);
+
+            #endregion 接入Redis
+
+            //互斥锁检测
+            var mutexName = RedisHub.Default.Check("Mutex") ?? throw new Exception("请在Redis中配置互斥量值");
+
+            //using var mt = new Mutex(true, mutexName);
+            //if (mt.WaitOne())
+            //{
+                CreateHostBuilder(args).Build().Run(); /*mt.ReleaseMutex();*/
+            //}
+            //else
+            //{
+            //    Console.WriteLine("请勿重复运行");
+            //    //InfoLog.INFO_INIT("请勿重复运行");
+            //    Task.Delay(2000).Wait();
+            //}
+        }
+
+        /// <summary>
+        /// 创建一个主机构建器
+        /// </summary>
+        /// <param name="args"></param>
+        /// <returns></returns>
+        public static IHostBuilder CreateHostBuilder(string[] args)
+        {
+            //是否是win平台
+            var isWin = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
+            Console.WriteLine($"win:{isWin}");
+            if (isWin)
+            {
+                var useUrls = RedisHub.Default.Check("UseUrls") ?? throw new Exception("请在Redis中配置网络访问端口");
+                //"http://*:8080"
+                return Host.CreateDefaultBuilder(args)
+                    .UseWindowsService()//win
+                    .ConfigureWebHostDefaults(web => //网络访问配置
+                    {
+                        web.UseUrls(useUrls); //设备访问端口
+                        web.UseStartup<Startup>(); //调用启动服务
+                    })
+                    .ConfigureServices((_, services) =>
+                    {
+                        services.AddHostedService<Worker>();
+                        services.AddHttpClient();
+                    });
+            }
+            return Host.CreateDefaultBuilder(args)
+                .UseSystemd()//linux
+                .ConfigureServices((_, services) =>
+                {
+                    services.AddHostedService<Worker>();
+                });
+        }
+    }
+}

+ 11 - 0
WCS.Service/Properties/launchSettings.json

@@ -0,0 +1,11 @@
+{
+  "profiles": {
+    "WCS.Service": {
+      "commandName": "Project",
+      "dotnetRunMessages": true,
+      "environmentVariables": {
+        "DOTNET_ENVIRONMENT": "Development"
+      }
+    }
+  }
+}

+ 35 - 0
WCS.Service/WCS.Service.csproj

@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.NET.Sdk.Worker">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>dotnet-WCS.Service-ee485c84-3250-46d0-8109-1d37d3b27230</UserSecretsId>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591</NoWarn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Compile Remove="Logs\**" />
+    <Content Remove="Logs\**" />
+    <EmbeddedResource Remove="Logs\**" />
+    <None Remove="Logs\**" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="MessagePack" Version="2.5.108" />
+    <PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.Systemd" Version="7.0.0" />
+    <PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.0" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\WCS.WorkEngineering\WCS.WorkEngineering.csproj" />
+  </ItemGroup>
+</Project>

+ 173 - 0
WCS.Service/Worker.cs

@@ -0,0 +1,173 @@
+using MessagePack;
+using Newtonsoft.Json;
+using ServiceCenter;
+using ServiceCenter.Redis;
+using System.Text;
+using ServiceCenter.Extensions;
+using ServiceCenter.Logs;
+using ServiceCenter.SqlSugars;
+using SqlSugar;
+using WCS.Core;
+using WCS.WorkEngineering;
+
+namespace WCS.Service
+{
+    /// <summary>
+    /// 工作服务
+    /// </summary>
+    public class Worker : BackgroundService
+    {
+        /// <summary>
+        /// 记录器
+        /// </summary>
+        private readonly ILogger<Worker> _logger;
+
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        /// <param name="logger">记录器</param>
+        public Worker(ILogger<Worker> logger)
+        {
+            _logger = logger;
+        }
+
+        public static readonly string WcsDlog = "WCSDlog";
+        public static readonly string Wcsdb = "WCSDB";
+
+        /// <summary>
+        ///  执行
+        /// </summary>
+        /// <param name="stoppingToken">停止令牌</param>
+        /// <returns></returns>
+        protected override async Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            if (stoppingToken.IsCancellationRequested)
+                return;
+
+            #region 初始化Redis连接
+
+            var redisConnectionStrings = RedisHub.Default.Check("RedisConnectionStrings") ?? throw new Exception("请在Redis中配置RedisConnectionStrings");
+            var configs = JsonConvert.DeserializeObject<List<DataBaseConnectionString>>(redisConnectionStrings);
+            if (configs != null)
+            {
+                if (configs.All(v => v.Key != "Monitor")) throw new Exception("请在RedisConnectionStrings中配置监控RedisDB库连接字符串");
+            }
+
+            foreach (var redisConnection in configs!)
+            {
+                RedisHub.CreateContext(redisConnection.ConnectionString, redisConnection.Key);
+                switch (redisConnection.Key)
+                {
+                    case "Monitor":
+                        RedisHub.SetMonitorContextType(redisConnection.Key);
+                        RedisHub.Monitor.Serialize = obj =>
+                        {
+                            var bytes = MessagePackSerializer.Serialize(obj);
+                            return bytes;
+                        };
+                        RedisHub.Monitor.DeserializeRaw = (bytes, type) =>
+                        {
+                            var obj = MessagePackSerializer.Deserialize(type, bytes);
+                            return obj;
+                        };
+                        break;
+
+                    case "DebugRedisUrl":
+                        RedisHub.SetDebugContextType(redisConnection.Key);
+                        Configs.DebugRedisUrl = redisConnection.ConnectionString;
+                        break;
+
+                    case "WMS":
+                        RedisHub.SetWMSContextType(redisConnection.Key);
+                        break;
+                }
+            }
+
+            #endregion 初始化Redis连接
+
+            #region 启用日志
+
+            //var logConfigText = RedisHub.Default.Check("LogConfigText") ?? throw new Exception("请在Redis中配置log4net相关内容");
+            //var logConfig = JsonConvert.DeserializeObject<LogConfig>(logConfigText);
+            //LogHub.SetConfigInfo(logConfig!);
+
+            #endregion 启用日志
+
+            _logger.LogInformation("WCS开始启动");
+            Configs.ProtocolProxyBaseType = typeof(ProtocolProxy);
+            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+            Configs.StringEncoding = Encoding.UTF8;
+            //var warehouseName = RedisHub.Default.Check("WarehouseName") ?? throw new Exception("请在Redis中配置仓库名称");
+            //if (string.IsNullOrEmpty(warehouseName)) throw new Exception("请在Redis中配置仓库名称");
+            //ServiceHub.SetWarehouseName(warehouseName);
+
+            #region 初始化数据库连接
+
+            InstanceFactory.CustomDbName = "TDengine";
+            InstanceFactory.CustomDllName = "SqlSugar.BzTDengineCore";
+            InstanceFactory.CustomNamespace = "SqlSugar.BzTDengineCore";
+
+            var dbConnectionStrings = RedisHub.Default.Check("DbConnectionStrings") ?? throw new Exception("请在Redis中配置数据库连接相关内容");
+            ServiceHub.DbConnectionStrings = JsonConvert.DeserializeObject<List<DataBaseConnectionString>>(dbConnectionStrings);
+            if (ServiceHub.DbConnectionStrings != null)
+            {
+                //if (ServiceHub.DbConnectionStrings.All(v => v.Key != Wcsdb)) throw new Exception("请在DbConnectionStrings中配置WCS基础数据库连接字符串");
+                //if (ServiceHub.DbConnectionStrings.All(v => v.Key == Wcsdb && !v.IsDefault)) throw new Exception("请在DbConnectionStrings中配置WCS基础数据库为默认数据库");
+                // if (ServiceHub.DbConnectionStrings.All(v => v.Key != WcsDlog)) throw new Exception("请在DbConnectionStrings中配置WCS日志数据库连接字符串");
+            }
+
+            //设置连接信息
+            List<ConnectionConfig> connectionConfigs = new List<ConnectionConfig>();
+            foreach (var connectionString in ServiceHub.DbConnectionStrings!)
+            {
+                connectionConfigs.Add(new ConnectionConfig()
+                {
+                    ConfigId = connectionString.Key,
+                    ConnectionString = connectionString.ConnectionString,//连接符字串
+                    DbType = connectionString.DbType,//数据库类型
+                    IsAutoCloseConnection = true,//不设成true要手动close
+                    LanguageType = LanguageType.Chinese,
+                    MoreSettings = new ConnMoreSettings()
+                    {
+                        IsNoReadXmlDescription = true
+                    }
+                });
+            };
+            SqlSugarHelper.SetDb(new SqlSugarScope(connectionConfigs));
+
+            ServiceHub.DbConnectionStrings.InitDB();
+
+            #endregion 初始化数据库连接
+
+            #region 初始化设备信息
+
+            WorkStart.InitializeDeviceInfo();
+
+            #endregion 初始化设备信息
+
+            #region 初始化PLC访问器及PLC读取协议
+
+            //创建PLC访问器
+            Configs.PLCAccessorCreater = new PLCAccessors.PLCAccessorsCreater();
+
+            try
+            {
+                #region 唤醒所有的世界
+
+                World.StartAll();
+
+                #endregion 唤醒所有的世界
+
+                _logger.LogInformation("WCS启动成功");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError("WCS启动失败{0}", ex.Message);
+            }
+
+            #endregion 初始化PLC访问器及PLC读取协议
+
+            LogHub.init();
+        }
+    }
+}

+ 29 - 0
WCS.Service/Worlds/initWorld.cs

@@ -0,0 +1,29 @@
+using WCS.Core;
+
+namespace WCS.Service.Worlds
+{
+    public class initWorld : World
+    {
+        protected override int Interval => 300;
+
+        protected override IEnumerable<string> GetChannelMsg(Channel channel)
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override void OnError(Channel channel, Exception exception)
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override void OnInternalLog(Channel channel, string msg)
+        {
+            throw new NotImplementedException();
+        }
+
+        protected override void OnLog(Channel channel, object logObj)
+        {
+            throw new NotImplementedException();
+        }
+    }
+}

+ 8 - 0
WCS.Service/appsettings.Development.json

@@ -0,0 +1,8 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  }
+}

+ 13 - 0
WCS.Service/appsettings.json

@@ -0,0 +1,13 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  },
+  "ConnectionStrings": {
+    "Redis": "10.30.37.2:6379,database=0,prefix=SX:"
+    //"Redis": "127.0.0.1,database=0,prefix=Sorting:"
+  }
+}

+ 91 - 0
WCS.WorkEngineering/Extensions/BCRExtension.cs

@@ -0,0 +1,91 @@
+using PlcSiemens.Core.Extension;
+using ServiceCenter.Logs;
+using WCS.Core;
+using WCS.Entity.Protocol.BCR;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    /// <summary>
+    ///  BCR扩展
+    /// </summary>
+    public class BCR : Device<IBCR81>
+    {
+        /// <summary>
+        ///   BCR扩展
+        /// </summary>
+        /// <param name="device"></param>
+        /// <param name="world"></param>
+        public BCR(Device device, World world) : base(device, world)
+        {
+        }
+
+        /// <summary>
+        ///  获取BCR码
+        /// </summary>
+        /// <returns></returns>
+        public string GetBCRCode()
+        {
+            var barcode = Data.Content.RemoveEscapeCharacters();
+
+            if (barcode.IsNullOrWhiteSpace()) throw new KnownException($"{Entity.Code}--扫码失败,内容为空", LogLevelEnum.High);
+
+            return barcode;
+        }
+    }
+
+    ///// <summary>
+    /////  BCR83
+    ///// </summary>
+    //public class BCR83 : Device<IBCR83>
+    //{
+    //    /// <summary>
+    //    ///   BCR扩展
+    //    /// </summary>
+    //    /// <param name="device"></param>
+    //    /// <param name="world"></param>
+    //    public BCR83(Device device, World world) : base(device, world)
+    //    {
+    //    }
+
+    //    /// <summary>
+    //    ///  获取BCR码
+    //    /// </summary>
+    //    /// <returns></returns>
+    //    public string GetBCRCode()
+    //    {
+    //        var barcode = Data.BcrCode.RemoveEscapeCharacters();
+
+    //        if (barcode.IsNullOrWhiteSpace()) throw new KnownException($"{Entity.Code}--扫码失败,内容为空", LogLevelEnum.High);
+
+    //        return barcode;
+    //    }
+    //}
+
+    /// <summary>
+    ///  BCR扩展
+    /// </summary>
+    public static class BCRExtension
+    {
+        /// <summary>
+        ///  获取BCR码
+        /// </summary>
+        /// <param name="bs"></param>
+        /// <param name="code">BCR对应站点设备号</param>
+        /// <returns></returns>
+        public static string GetBCRCode(this List<BCR> bc, string code)
+        {
+            code = "BCR" + code;
+            var bcr = bc.FirstOrDefault(p => p.Entity.Code == code) ?? throw new KnownException($"未找到扫码器{code}", LogLevelEnum.High);
+            return bcr.GetBCRCode();
+        }
+
+        ///// <summary>
+        /////  获取BCR83码集合
+        ///// </summary>
+        ///// <param name="bc"></param>
+        ///// <returns></returns>
+        //public static string GetBCRCodeList(this BCR83 bc)
+        //{
+        //}
+    }
+}

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

@@ -0,0 +1,582 @@
+using PlcSiemens.Core.Extension;
+using ServiceCenter.Extensions;
+using ServiceCenter.Logs;
+using ServiceCenter.Redis;
+using WCS.Core;
+using WCS.Entity.Protocol.BCR;
+using WCS.Entity.Protocol.Robot;
+using WCS.Entity.Protocol.Station;
+using WCS.Entity.Protocol.Truss;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    /// <summary>
+    ///  设备扩展
+    /// </summary>
+    public static class DeviceExtension
+    {
+        #region 设备类型
+
+        public static List<ProtocolInfo> ProtocolInfos = new List<ProtocolInfo>();
+
+        /// <summary>
+        ///  是否是巷道
+        /// </summary>
+        /// <param name="source">设备信息</param>
+        /// <returns></returns>
+        public static bool IsTunnel(this Device source)
+        {
+            return source.HasFlag(DeviceFlags.巷道);
+        }
+
+        #endregion 设备类型
+
+        #region 标签判断
+
+        public static bool HasFlag(this Device device, params DeviceFlags[] flag)
+        {
+            return flag.Any(device.HasFlag);
+        }
+
+        #endregion 标签判断
+
+        #region 协议操作扩展
+
+        public static void AddProtocol<T>(this Device device, int position, ushort db, string ip)
+        {
+            var info = new ProtocolInfo
+            {
+                Position = position,
+                DBInfo = new DBInfo
+                {
+                    No = db,
+                    PLCInfo = new PLCInfo
+                    {
+                        IP = ip,
+                        Port = 102,
+                        Rack = 0,
+                        Slot = 1,
+                        Type = PLCType.Siemens
+                    }
+                }
+            };
+            ProtocolInfos.Add(info);
+            device.AddProtocol<T>(info);
+        }
+
+        #endregion 协议操作扩展
+
+        #region 设备扩展方法
+
+        /// <summary>
+        ///  入库站点是否被禁止
+        /// </summary>
+        /// <returns></returns>
+        public static void 入库站点是否被禁止(this Device<IStation520, IStation521, IStation523> device)
+        {
+            var config = RedisHub.Default.Check("ForbidTubuEnter") ?? throw new Exception("请在Redis中配置入库口禁用");
+            var configs = config.Split(",");
+            if (configs.Contains(device.Entity.Code)) throw new KnownException("当前入库口已被禁用,请联系运维人员了解具体情况", LogLevelEnum.High);
+        }
+
+        /// <summary>
+        ///  入库站点是否被禁止
+        /// </summary>
+        /// <returns></returns>
+        public static void 入库站点是否被禁止(this Device<IStation520, IStation521, IStation523, IBCR81> device)
+        {
+            var config = RedisHub.Default.Check("ForbidTubuEnter") ?? throw new Exception("请在Redis中配置入库口禁用");
+            var configs = config.Split(",");
+            if (configs.Contains(device.Entity.Code)) throw new KnownException("当前入库口已被禁用,请联系运维人员了解具体情况", LogLevelEnum.High);
+        }
+
+        /// <summary>
+        ///  入库站点是否被禁止
+        /// </summary>
+        /// <returns></returns>
+        public static void 入库站点是否被禁止(this Device<IStation520, IStation521, IStation523, IStation91> device)
+        {
+            var config = RedisHub.Default.Check("ForbidTubuEnter") ?? throw new Exception("请在Redis中配置入库口禁用");
+            var configs = config.Split(",");
+            if (configs.Contains(device.Entity.Code)) throw new KnownException("当前入库口已被禁用,请联系运维人员了解具体情况", LogLevelEnum.High);
+        }
+
+        /// <summary>
+        ///  入库站点是否被禁止
+        /// </summary>
+        /// <returns></returns>
+        public static void 入库站点是否被禁止(this Device<IStation520, IStation521, IStation523, IStation91, IBCR81> device)
+        {
+            var config = RedisHub.Default.Check("ForbidTubuEnter") ?? throw new Exception("请在Redis中配置入库口禁用");
+            var configs = config.Split(",");
+            if (configs.Contains(device.Entity.Code)) throw new KnownException("当前入库口已被禁用,请联系运维人员了解具体情况", LogLevelEnum.High);
+        }
+
+        /// <summary>
+        ///  入库站点是否满足执行条件
+        /// </summary>
+        /// <returns></returns>
+        public static void 入库站点是否满足执行条件(this Device<IStation520, IStation521, IStation523> device)
+        {
+        }
+
+        ///// <summary>
+        /////  入库站点是否满足执行条件
+        ///// </summary>
+        ///// <returns></returns>
+        //public static void 入库站点是否满足执行条件(this Device<IStation520, IStation521, IStation523, IBCR81> device)
+        //{
+        //    if (device.Data.VoucherNo != device.Data2.VoucherNo) throw new KnownException($"凭证号不一致,DB520:{device.Data.VoucherNo}-DB521:{device.Data2.VoucherNo}", LogLevelEnum.High);
+        //    if (device.Data3.Status.HasFlag(StationStatus.Run)) throw new KnownException("设备运行中", LogLevelEnum.Low);
+        //    if (device.Data3.Status.HasFlag(StationStatus.PH_Status) && device.Data2.Request == 0) throw new KnownException("有光电无请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.PH_Status) && device.Data2.Request == 1) throw new KnownException("无光电有请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.OT_Status)) throw new KnownException("站台货物信息与实际占用不一致", LogLevelEnum.Low);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.PH_Status)) throw new KnownException("无光电", LogLevelEnum.Mid);
+        //    if (device.Data2.Request != 1) throw new KnownException("无请求", LogLevelEnum.Mid);
+        //}
+
+        ///// <summary>
+        /////  入库站点是否满足执行条件
+        ///// </summary>
+        ///// <returns></returns>
+        //public static void 入库站点是否满足执行条件(this Device<IStation520, IStation521, IStation523, IStation91> device)
+        //{
+        //    if (device.Data.VoucherNo != device.Data2.VoucherNo) throw new KnownException($"凭证号不一致,DB520:{device.Data.VoucherNo}-DB521:{device.Data2.VoucherNo}", LogLevelEnum.High);
+        //    if (device.Data3.Status.HasFlag(StationStatus.Run)) throw new KnownException("设备运行中", LogLevelEnum.Low);
+        //    if (device.Data3.Status.HasFlag(StationStatus.PH_Status) && device.Data2.Request == 0) throw new KnownException("有光电无请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.PH_Status) && device.Data2.Request == 1) throw new KnownException("无光电有请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.OT_Status)) throw new KnownException("站台货物信息与实际占用不一致", LogLevelEnum.Low);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.PH_Status)) throw new KnownException("无光电", LogLevelEnum.Mid);
+        //    if (device.Data2.Request != 1) throw new KnownException("无请求", LogLevelEnum.Mid);
+        //}
+
+        ///// <summary>
+        /////  入库站点是否满足执行条件
+        ///// </summary>
+        ///// <returns></returns>
+        //public static void 入库站点是否满足执行条件(this Device<IStation520, IStation521, IStation523, IStation91, IBCR81> device)
+        //{
+        //    if (device.Data.VoucherNo != device.Data2.VoucherNo) throw new KnownException($"凭证号不一致,DB520:{device.Data.VoucherNo}-DB521:{device.Data2.VoucherNo}", LogLevelEnum.High);
+        //    if (device.Data3.Status.HasFlag(StationStatus.Run)) throw new KnownException("设备运行中", LogLevelEnum.Low);
+        //    if (device.Data3.Status.HasFlag(StationStatus.PH_Status) && device.Data2.Request == 0) throw new KnownException("有光电无请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.PH_Status) && device.Data2.Request == 1) throw new KnownException("无光电有请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.OT_Status)) throw new KnownException("站台货物信息与实际占用不一致", LogLevelEnum.Low);
+        //    if (device.Data2.Request != 1.ToShort()) throw new KnownException("无请求", LogLevelEnum.Mid);
+        //    if (!device.Data3.Status.HasFlag(StationStatus.PH_Status)) throw new KnownException("无光电", LogLevelEnum.Mid);
+        //    if (device.Data2.Request != 1) throw new KnownException("无请求", LogLevelEnum.Mid);
+        //}
+
+        /// <summary>
+        ///  获取BCR码
+        /// </summary>
+        /// <returns></returns>
+        public static string GetBCRCode(this IBCR81 bCR)
+        {
+            var barcode = bCR.Content.RemoveEscapeCharacters();
+            return barcode;
+        }
+
+        ///// <summary>
+        /////  获取历史扫码记录
+        ///// </summary>
+        ///// <returns></returns>
+        //public static List<string> GetBcrCodeList(this IBCR83 bCR)
+        //{
+        //    return new List<string>() {
+        //        bCR.BcrCode1.RemoveEscapeCharacters(),
+        //        bCR.BcrCode2.RemoveEscapeCharacters(),
+        //        bCR.BcrCode3.RemoveEscapeCharacters(),
+        //        bCR.BcrCode4.RemoveEscapeCharacters(),
+        //        bCR.BcrCode5.RemoveEscapeCharacters(),
+        //        bCR.BcrCode6.RemoveEscapeCharacters(),
+        //        bCR.BcrCode7.RemoveEscapeCharacters(),
+        //        bCR.BcrCode8.RemoveEscapeCharacters(),
+        //        bCR.BcrCode9.RemoveEscapeCharacters(),
+        //        bCR.BcrCode10.RemoveEscapeCharacters(),
+        //        bCR.BcrCode11.RemoveEscapeCharacters() };
+        //}
+
+        /// <summary>
+        ///  获取缓存列表
+        /// </summary>
+        /// <returns></returns>
+        public static IEnumerable<string> GetBcrCodeList(this IStation525 station525)
+        {
+            var properties = station525.GetType().GetProperties().ToArray();
+            var index = 1;
+            for (var i = 1; i <= 50; i++)
+            {
+                var value = properties[index].GetValue(station525).ToString().RemoveEscapeCharacters();
+                if (!value.IsNullOrEmpty()) yield return value;
+                index += 7;
+            }
+        }
+
+        /// <summary>
+        ///  获取缓存列表
+        /// </summary>
+        /// <returns></returns>
+        public static IEnumerable<int> GetTaskNoList(this ITruss531 station525)
+        {
+            var properties = station525.GetType().GetProperties().ToArray();
+            var index = 0;
+            for (var i = 0; i <= 59; i++)
+            {
+                var value = properties[index].GetValue(station525)!.ToInt();
+                if (value > 0) yield return value;
+                index += 1;
+            }
+        }
+
+        /// <summary>
+        ///  获取缓存列表
+        /// </summary>
+        /// <returns></returns>
+        public static IEnumerable<int> GetTaskNoList(this IRobot531 station525)
+        {
+            var properties = station525.GetType().GetProperties().ToArray();
+            var index = 0;
+            for (var i = 0; i <= 29; i++)
+            {
+                var value = properties[index].GetValue(station525)!.ToInt();
+                if (value > 0) yield return value;
+                index += 1;
+            }
+        }
+
+        /// <summary>
+        ///  通过仓库编码获取对应堆垛机信息
+        /// </summary>
+        /// <param name="warehouseCode">仓库编码</param>
+        /// <returns></returns>
+        public static string WarehouseToSrm(this string warehouseCode)
+        {
+            return warehouseCode switch
+            {
+                "1N" => "SRM1",
+                "1S" => "SRM2",
+                "2N" => "SRM3",
+                "2S" => "SRM4",
+                "3N" => "SRM5",
+                "3S" => "SRM6",
+                _ => "Error"
+            };
+        }
+
+        #endregion 设备扩展方法
+
+        /// <summary>
+        ///  获取仓库号
+        /// </summary>
+        public static string GetWareCode(this string add)
+        {
+            return add switch
+            {
+                #region 库一
+
+                //北
+                "SRM1" => "1N",
+                "2532" => "1N",
+                "2527" => "1N",
+                "2528" => "1N",
+                "418" => "1N",
+                "1606" => "1N",
+                "1666" => "1NR",
+                "1661" => "1NR",
+                "18" => "1N",
+                //南
+                "SRM2" => "1S",
+                "2732" => "1S",
+                "2727" => "1S",
+                "2728" => "1S",
+                "618" => "1S",
+                "1676" => "1SR",
+                "1681" => "1SR",
+
+                #endregion 库一
+
+                #region 库二
+
+                //北
+                "SRM3" => "2N",
+                "818" => "2N",
+                "1691" => "2NR",
+                "1696" => "2NR",
+                //南
+                "SRM4" => "2S",
+                "1018" => "2S",
+                "3128" => "2S",
+                "1711" => "2SR",
+                "1706" => "2SR",
+
+                #endregion 库二
+
+                #region 库二
+
+                //北
+                "SRM5" => "3N",
+                "1218" => "3N",
+                "1721" => "3NR",
+                "1726" => "3NR",
+                //南
+                "SRM6" => "3S",
+                "1418" => "3S",
+                "1736" => "3SR",
+                "1741" => "3SR",
+
+                #endregion 库二
+
+                _ => "",
+            };
+        }
+    }
+
+    /// <summary>
+    ///  设备标签
+    /// </summary>
+    [Flags]
+    public enum DeviceFlags : ulong
+
+    {
+        扫码 = 1L << 0,
+        称重 = 1L << 1,
+        外检 = 1L << 2,
+
+        顶升 = 1L << 3,
+        移栽 = 1L << 4,
+        旋转 = 1L << 5,
+
+        入库 = 1L << 6,
+        出库 = 1L << 7,
+
+        巷道口 = 1L << 8,
+        RGV口 = 1L << 9,
+        AGV取货站台口 = 1L << 10,
+
+        直轨 = 1L << 11,
+        弯轨 = 1L << 12,
+        环轨 = 1L << 13,
+
+        巷道 = 1L << 14,
+        堆垛机 = 1L << 15,
+        输送机 = 1L << 16,
+
+        #region 一轨双车堆垛机
+
+        一列堆垛机 = 1L << 23,
+        二列堆垛机 = 1L << 24,
+
+        #endregion 一轨双车堆垛机
+
+        RGV = 1L << 25,
+        桁架 = 1L << 26,
+        一楼扫码 = 1L << 27,
+        满轮主线第一次扫码 = 1L << 28,
+        主线分流点 = 1L << 29,
+        一楼叠盘机 = 1L << 30,
+        环形库分流点 = 1L << 31,
+        桁架分流点 = 1L << 32,
+        桁架缓存放行点 = 1L << 33,
+        桁架取货点 = 1L << 34,
+        拆盘机 = 1L << 35,
+        桁架码垛位 = 1L << 36,
+        环形库码垛工位 = 1L << 37,
+        Robot = 1L << 38,
+        桁架09缓存放行点 = 1L << 39,
+        桁架18缓存放行点 = 1L << 40,
+        桁架09异常缓存放行点 = 1L << 41,
+        二次码垛RGV取货口 = 1L << 42,
+        无交互触发设备 = 1L << 43,
+    }
+
+    /// <summary>
+    /// 输送机段信息
+    /// </summary>
+    public class StationSegmentInfo
+    {
+        /// <summary>
+        ///  构造函数
+        /// </summary>
+        /// <param name="start">起始设备号</param>
+        /// <param name="end">结束设备号</param>
+        /// <param name="ip">ip</param>
+        public StationSegmentInfo(int start, int end, string ip)
+        {
+            Start = start;
+            End = end;
+            Ip = ip;
+        }
+
+        /// <summary>
+        /// 起始设备编号
+        /// </summary>
+        public int Start { get; set; }
+
+        /// <summary>
+        ///结束设备编号
+        /// </summary>
+        public int End { get; set; }
+
+        /// <summary>
+        ///  输送机段所属IP
+        /// </summary>
+        public string Ip { get; set; }
+    }
+
+    /// <summary>
+    /// RGV信息
+    /// </summary>
+    public class RgvSegmentInfo
+    {
+        /// <summary>
+        ///  构造函数
+        /// </summary>
+        /// <param name="code"></param>
+        /// <param name="ip">ip</param>
+        public RgvSegmentInfo(int code, string ip)
+        {
+            Code = code;
+            Ip = ip;
+        }
+
+        /// <summary>
+        /// 设备编号
+        /// </summary>
+        public int Code { get; set; }
+
+        /// <summary>
+        ///  输送机段所属IP
+        /// </summary>
+        public string Ip { get; set; }
+    }
+
+    /// <summary>
+    /// 桁架信息
+    /// </summary>
+    public class TrussSegmentInfo
+    {
+        /// <summary>
+        ///  构造函数
+        /// </summary>
+        /// <param name="code"></param>
+        /// <param name="ip">ip</param>
+        public TrussSegmentInfo(int code, string ip)
+        {
+            Code = code;
+            Ip = ip;
+        }
+
+        /// <summary>
+        /// 设备编号
+        /// </summary>
+        public int Code { get; set; }
+
+        /// <summary>
+        ///  输送机段所属IP
+        /// </summary>
+        public string Ip { get; set; }
+    }
+
+    /// <summary>
+    ///  扫码器信息
+    /// </summary>
+    public class BcrInfo
+    {
+        /// <summary>
+        ///  构造函数
+        /// </summary>
+        /// <param name="deviceNo">设备编号</param>
+        /// <param name="ip">ip</param>
+        public BcrInfo(string[] deviceNo, string ip)
+        {
+            DeviceNo = deviceNo;
+            Ip = ip;
+        }
+
+        /// <summary>
+        ///  设备编号
+        /// </summary>
+        public string[] DeviceNo { get; set; }
+
+        /// <summary>
+        ///  ip
+        /// </summary>
+        public string Ip { get; set; }
+    }
+
+    /// <summary>
+    ///  外形信息
+    /// </summary>
+    public class ShapeInfo
+    {
+        /// <summary>
+        ///  构造函数
+        /// </summary>
+        /// <param name="deviceNo">设备编号</param>
+        /// <param name="ip">ip</param>
+        public ShapeInfo(int[] deviceNo, string ip)
+        {
+            DeviceNo = deviceNo;
+            Ip = ip;
+        }
+
+        /// <summary>
+        ///  设备编号
+        /// </summary>
+        public int[] DeviceNo { get; set; }
+
+        /// <summary>
+        ///  ip
+        /// </summary>
+        public string Ip { get; set; }
+    }
+
+    /// <summary>
+    /// 路径信息
+    /// </summary>
+    public class RouteInfo
+    {
+        /// <summary>
+        ///  构造函数
+        /// </summary>
+        /// <param name="deviceCode">起始点设备号</param>
+        /// <param name="nextList">下一个设备集合</param>
+        public RouteInfo(string deviceCode, string[] nextList)
+        {
+            DeviceCode = deviceCode;
+            NextList = nextList;
+        }
+
+        /// <summary>
+        ///  设备号
+        /// </summary>
+        public string DeviceCode { get; set; }
+
+        /// <summary>
+        ///  下一个设备
+        /// </summary>
+        public string[] NextList { get; set; }
+    }
+
+    /// <summary>
+    ///  缓存分流信息
+    /// </summary>
+    public class CacheBcr
+    {
+        public CacheBcr(string bcrCode, short nextAdd)
+        {
+            BcrCode = bcrCode;
+            NextAdd = nextAdd;
+        }
+
+        /// <summary>
+        ///  条码
+        /// </summary>
+        public string BcrCode { get; set; }
+
+        /// <summary>
+        ///  下一个地址
+        /// </summary>
+        public short NextAdd { get; set; }
+    }
+}

+ 43 - 0
WCS.WorkEngineering/Extensions/FlagExtension.cs

@@ -0,0 +1,43 @@
+using WCS.Core;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    /// <summary>
+    ///  设备标签扩展
+    /// </summary>
+    public static class DeviceFlagExtension
+    {
+        /// <summary>
+        ///  获取指定设备标签的值
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="device"></param>
+        /// <param name="key"></param>
+        /// <returns></returns>
+        public static T GetFlag<T>(this Device device, string key)
+        {
+            if (!device.HasFlag(key))
+            {
+                if (typeof(T) == typeof(int)) device.AddFlag(key, default(int).ToString());
+                else if (typeof(T) == typeof(short)) device.AddFlag(key, default(bool).ToString());
+                else if (typeof(T) == typeof(bool)) device.AddFlag(key, default(bool).ToString());
+            }
+            var valeu = device.GetFlags(key).FirstOrDefault();
+
+            return (T)Convert.ChangeType(valeu, typeof(T));
+        }
+
+        /// <summary>
+        ///  设置指定设备标签的值
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="device"></param>
+        /// <param name="key"></param>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static void SetFlag<T>(this Device device, string key, T value)
+        {
+            device.SetFlag(key, value.ToString());
+        }
+    }
+}

+ 12 - 0
WCS.WorkEngineering/Extensions/RGVExtension.cs

@@ -0,0 +1,12 @@
+using WCS.Core;
+using WCS.Entity.Protocol.RGV;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    public class RGV : Device<IRGV520, IRGV521>
+    {
+        public RGV(Device device, World world) : base(device, world)
+        {
+        }
+    }
+}

+ 26 - 0
WCS.WorkEngineering/Extensions/RedisExtenshin.cs

@@ -0,0 +1,26 @@
+namespace WCS.WorkEngineering.Extensions
+{
+    public static class RedisExtenshin
+    {
+        ///// <summary>
+        /////  写入垛形信息
+        ///// </summary>
+        ///// <param name="redis"></param>
+        ///// <param name="stackInfoList"></param>
+        //public static void SetPalletizingInformation(this RedisClient redis, List<StackInfo> stackInfoList)
+        //{
+        //    var lockKey = nameof(SetPalletizingInformation);
+        //    try
+        //    {
+        //        if (redis.SetNx(lockKey, 1)) //设置锁
+        //        {
+        //            redis.Set("PalletizingInformation", JsonConvert.SerializeObject(stackInfoList));
+        //        }
+        //    }
+        //    finally
+        //    {
+        //        redis.Del(lockKey);
+        //    }
+        //}
+    }
+}

+ 60 - 0
WCS.WorkEngineering/Extensions/SRMExtension.cs

@@ -0,0 +1,60 @@
+using ServiceCenter.Extensions;
+using WCS.Core;
+using WCS.Entity;
+using WCS.Entity.Protocol.SRM;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    /// <summary>
+    /// 堆垛机扩展
+    /// </summary>
+    public class SRM : Device<ISRM520, ISRM521>
+    {
+        public SRM(Device device, World world) : base(device, world)
+        {
+        }
+
+        ///// <summary>
+        /////
+        ///// </summary>
+        ///// <param name="taskInfo"></param>
+        //public void 一轨两车任务下发前置条件判断及处理(WCS_TaskInfo taskInfo)
+        //{
+        //    var col = 0;
+        //    switch (taskInfo.Type)
+        //    {
+        //        case TaskType.SetPlate: return;
+        //        case TaskType.EnterDepot:
+        //            col = taskInfo.AddrTo.Split("-")[1].ToInt();
+        //            break;
+
+        //        case TaskType.OutDepot:
+        //            col = taskInfo.AddrFrom.Split("-")[1].ToInt();
+        //            break;
+
+        //        case TaskType.TransferDepot: return;
+        //        case TaskType.Delivery: return;
+        //        case TaskType.EmptyInit: return;
+        //        default: return;
+        //    }
+        //    if (Entity.HasFlag(DeviceFlags.一列堆垛机))
+        //    {
+        //        if (col > 10)// 跑全程
+        //        {
+        //            if (Data2.Status.HasFlag(SrmStatus.Stopper_Mark))
+        //            {
+        //            }
+        //        }
+        //    }
+        //    else if (Entity.HasFlag(DeviceFlags.二列堆垛机))
+        //    {
+        //        if (col < 11)// 跑全程
+        //        {
+        //            if (Data2.Status.HasFlag(SrmStatus.Stopper_Mark))
+        //            {
+        //            }
+        //        }
+        //    }
+        //}
+    }
+}

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

@@ -0,0 +1,615 @@
+using ServiceCenter.Extensions;
+using ServiceCenter.Logs;
+using ServiceCenter.Redis;
+using ServiceCenter.SqlSugars;
+using SqlSugar;
+using WCS.Core;
+using WCS.Entity;
+using TaskStatus = WCS.Entity.TaskStatus;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    /// <summary>
+    /// 任务扩展
+    /// </summary>
+    public static class TaskExtension
+    {
+        /// <summary>
+        /// 更新任务执行记录
+        /// 同步更新历史任务
+        /// </summary>
+        /// <param name="task">任务信息</param>
+        /// <param name="db">数据库上下文</param>
+        /// <param name="curPoint">当前地址</param>
+        /// <param name="desc">描述</param>
+        public static void AddWCS_TASK_DTL(this WCS_TaskInfo task, SqlSugarScopeProvider db, string curPoint, string desc) => task.AddWCS_TASK_DTL(db, curPoint, "", desc);
+
+        /// <summary>
+        /// 更新任务执行记录
+        /// 同步更新历史任务
+        /// </summary>
+        /// <param name="task">任务信息</param>
+        /// <param name="db">数据库上下文</param>
+        /// <param name="curPoint">当前地址</param>
+        /// <param name="nextPoint">下一个地址</param>
+        /// <param name="desc">描述</param>
+        public static void AddWCS_TASK_DTL(this WCS_TaskInfo task, SqlSugarScopeProvider db, string curPoint, string nextPoint, string desc)
+        {
+            db.InsertableRowLock(new WCS_TaskDtl
+            {
+                ID = Guid.NewGuid(),
+                ParentTaskCode = task.ID,
+                CurPoint = curPoint,
+                NextPoint = nextPoint,
+                Desc = desc,
+                AddWho = "WCS"
+            }).SplitTable().ExecuteCommand();
+            task.UpdateableOldTask(db);
+        }
+
+        /// <summary>
+        /// 更新任务执行记录
+        /// 同步更新历史任务
+        /// </summary>
+        /// <param name="task">任务信息</param>
+        /// <param name="db">数据库上下文</param>
+        /// <param name="curPoint">当前地址</param>
+        /// <param name="desc">描述</param>
+        public static void AddWCS_TASK_DTL(this WCS_TaskOld task, SqlSugarScopeProvider db, string curPoint, string desc) => task.AddWCS_TASK_DTL(db, curPoint, "", desc);
+
+        /// <summary>
+        /// 更新任务执行记录
+        /// 同步更新历史任务
+        /// </summary>
+        /// <param name="task">任务信息</param>
+        /// <param name="db">数据库上下文</param>
+        /// <param name="curPoint">当前地址</param>
+        /// <param name="nextPoint">下一个地址</param>
+        /// <param name="desc">描述</param>
+        public static void AddWCS_TASK_DTL(this WCS_TaskOld task, SqlSugarScopeProvider db, string curPoint, string nextPoint, string desc)
+        {
+            db.Insertable(new WCS_TaskDtl
+            {
+                ID = Guid.NewGuid(),
+                ParentTaskCode = task.Id,
+                CurPoint = curPoint,
+                NextPoint = nextPoint,
+                Desc = desc,
+                AddWho = "WCS"
+            }).SplitTable().ExecuteCommand();
+        }
+
+        /// <summary>
+        ///  更新历史表数据
+        /// </summary>
+        /// <param name="taskInfo"></param>
+        /// <param name="db"></param>
+        public static void UpdateableOldTask(this WCS_TaskInfo taskInfo, SqlSugarScopeProvider db)
+        {
+            if (taskInfo.Status == TaskStatus.NewBuild) return;
+
+            // 同步任务信息
+            var taskOld = db.Queryable<WCS_TaskOld>().Where(v => v.Id == taskInfo.ID).SplitTable(tabs => tabs.Take(2)).ToList().OrderByDescending(v => v.AddTime).First();
+            if (taskOld is not null)
+            {
+                if (taskInfo.Status >= TaskStatus.Finish) taskInfo.CompleteOrCancelTasks(db);
+                else
+                {
+                    taskOld = taskInfo.Mapper<WCS_TaskOld, WCS_TaskInfo>();
+                    taskOld.Id = taskInfo.ID;
+                    db.UpdateableRowLock(taskOld).Where(x => x.Id == taskOld.Id).SplitTable(tabs => tabs.Take(2)).ExecuteCommand();
+                }
+            }
+            else
+            {
+                throw new KnownException($"WCS_TaskOld表中不存在任务:{taskInfo.ID},无法执行WCS_TaskInfo与WCS_TaskOld同步动作", LogLevelEnum.Mid);
+            }
+        }
+
+        /// <summary>
+        /// 完成或取消任务
+        /// </summary>
+        /// <param name="taskInfo"></param>
+        /// <param name="db"></param>
+        public static void CompleteOrCancelTasks(this WCS_TaskInfo taskInfo, SqlSugarScopeProvider db)
+        {
+            if (taskInfo.Status is not Entity.TaskStatus.Finish and not Entity.TaskStatus.Cancel) throw new KnownException("任务未完成或取消,无法执行WCS_TaskInfo与WCS_TaskOld同步动作", LogLevelEnum.Mid);
+            // 任务完成或取消,进行相关同步动作
+            var taskOld = db.Queryable<WCS_TaskOld>().Where(v => v.Id == taskInfo.ID).SplitTable(tabs => tabs.Take(2)).ToList().OrderByDescending(v => v.AddTime).First();
+            if (taskOld is not null)
+            {
+                taskOld = taskInfo.Mapper<WCS_TaskOld, WCS_TaskInfo>();
+                taskOld.Id = taskInfo.ID;
+                //更新任务历史表,删除任务当前表
+                db.UpdateableRowLock(taskOld).Where(x => x.Id == taskOld.Id).SplitTable(tabs => tabs.Take(2)).ExecuteCommand();
+                db.DeleteableRowLock(taskInfo).ExecuteCommand();
+            }
+            else
+            {
+                throw new KnownException($"WCS_TaskOld表中不存在任务:{taskInfo.ID},无法执行WCS_TaskInfo与WCS_TaskOld同步动作", LogLevelEnum.Mid);
+            }
+        }
+
+        /// <summary>
+        ///  更新表数据
+        /// </summary>
+        /// <param name="taskInfo"></param>
+        /// <param name="db"></param>
+        public static void Updateable(this WCS_TaskInfo taskInfo, SqlSugarScopeProvider db)
+        {
+        }
+
+        ///// <summary>
+        /////  获取出库任务
+        ///// </summary>
+        ///// <param name="taskInfo">任务</param>
+        ///// <param name="db">db</param>
+        ///// <param name="allOutCode">可用出库站台</param>
+        ///// <param name="floor">楼层</param>
+        ///// <param name="obj">堆垛机</param>
+        ///// <param name="index">递归次数</param>
+        ///// <returns></returns>
+        ///// <exception cref="KnownException"></exception>
+        //public static WCS_TaskInfo GetOutTask(this WCS_TaskInfo taskInfo, SqlSugarHelper db, List<string> allOutCode, int floor, SRM obj, int index = 1)
+        //{
+        //    return task;
+        //}
+
+        /// <summary>
+        /// 获取AGV任务ID
+        /// </summary>
+        /// <param name="db">db</param>
+        /// <returns></returns>
+        public static int GetAgvTaskId(this SqlSugarHelper db)
+        {
+            //最多任务号不再连续
+            var id = db.Default.Queryable<WCS_AgvTaskInfo>().NoLock().SplitTable(v => v.Take(2)).Max(v => v.ID);
+            return id + 1;
+        }
+
+        /// <summary>
+        /// 更新任务执行记录
+        /// </summary>
+        /// <param name="task">任务信息</param>
+        public static void UpdateRedisHash(this WCS_TaskInfo task)
+        {
+            var key = $"Hash:{task.ID}";
+            if (task.Status >= TaskStatus.Finish)
+            {
+                RedisHub.WMS.Del(key);
+            }
+            else
+            {
+                RedisHub.WMS.HMSet(key, task.ToDic());
+            }
+        }
+
+        #region 工字轮支线分流
+
+        /// <summary>
+        ///  初始化码垛信息
+        /// </summary>
+        /// <param name="task"></param>
+        public static void InitStackStructure(this WCS_TaskInfo task, SqlSugarScopeProvider db, World world)
+        {
+            var billBomsetgrp = db.Queryable<BillBomsetgrp>().Single(x => x.IsStop == 0 && x.BomCode.Contains(task.MatCode));
+            if (billBomsetgrp == null)
+            {
+                world.Log($"物料规格[{task.MatCode}]无可用码垛垛形");
+                return;
+            };
+            var billBomsetinfos = db.Queryable<BillBomsetinfo>().Where(x => x.BomSetHdrId == billBomsetgrp.Id).ToList();
+
+            //开始构造垛形信息
+            var palletizing = new WCS_Palletizing()
+            {
+                Code = billBomsetgrp.Code,
+                ShortCode = billBomsetgrp.ShortCode,
+                ProMaterCode = billBomsetgrp.ProMaterCode,
+                TpTypeCode = billBomsetgrp.TpTypeCode,
+                LayerCountQty = 2,
+                StampType = billBomsetgrp.StampType,
+                Finish = false,
+                AddTime = DateTime.Now,
+                TaskId = task.ID,
+                WarehouseCode = task.WarehouseCode,
+                DeviceCode = task.Device
+            };
+            palletizing = db.InsertableRowLock(palletizing).ExecuteReturnEntity();
+            foreach (var item in billBomsetinfos.Where(x => x.IsEmpty == 0).GroupBy(x => x.Row).OrderBy(x => x.Key))
+            {
+                var layerNo = item.Key <= 6 ? 1 : 2;
+                //获取层信息
+                var palletizingLayer = db.Queryable<WCS_PalletizingLayer>().Single(x => x.PalletizingId == palletizing.Id && x.LayerNo == layerNo);
+
+                if (palletizingLayer == null)
+                {
+                    palletizingLayer = new WCS_PalletizingLayer()
+                    {
+                        LayerNo = layerNo,
+                        PalletizingId = palletizing.Id,
+                        WarehouseCode = palletizing.WarehouseCode,
+                    };
+                    palletizingLayer = db.InsertableRowLock(palletizingLayer).ExecuteReturnEntity();
+                }
+
+                //获取行信息
+                var palletizingRow = db.Queryable<WCS_PalletizingRow>().Single(x => x.PalletizingLayerId == palletizingLayer.Id && x.RowNo == item.Key);
+                if (palletizingRow == null)
+                {
+                    palletizingRow = new WCS_PalletizingRow()
+                    {
+                        RowNo = item.Key,
+                        PalletizingLayerId = palletizingLayer.Id,
+                        PalletizingId = palletizing.Id,
+                        WarehouseCode = palletizingLayer.WarehouseCode
+                    };
+                    palletizingRow = db.InsertableRowLock(palletizingRow).ExecuteReturnEntity();
+                }
+
+                //重新查询最新的数据
+                var layer = palletizingLayer;
+                palletizingLayer = db.Queryable<WCS_PalletizingLayer>().Single(x => x.Id == layer.Id);
+                var row = palletizingRow;
+                palletizingRow = db.Queryable<WCS_PalletizingRow>().Single(x => x.Id == row.Id);
+
+                //构造位信息
+                foreach (var loc in item)
+                {
+                    var palletizingLoc = db.Queryable<WCS_PalletizingLoc>().Single(x => x.PalletizingRowId == palletizingRow.Id && x.XYNo == loc.XYNo);
+
+                    if (palletizingLoc == null)
+                    {
+                        palletizingLoc = new WCS_PalletizingLoc()
+                        {
+                            IsEmpty = loc.IsEmpty != 0,
+                            XYNo = loc.XYNo,
+                            MatCode = loc.MatCode,
+                            SideNum = loc.SideNum,
+                            SpoolType = loc.SpoolType,
+                            TaskId = task.ID,
+                            PalletizingRowId = palletizingRow.Id,
+                            Finish = false,
+                            WarehouseCode = palletizingRow.WarehouseCode
+                        };
+                        db.InsertableRowLock(palletizingLoc).ExecuteReturnEntity();
+                    }
+
+                    //同步是否混合料行
+                    palletizingRow.IsMixRow = loc.IsMixRow != 0;
+                    db.UpdateableRowLock(palletizingRow).ExecuteCommand();
+                }
+                //更新行信息
+                palletizingRow = db.Queryable<WCS_PalletizingRow>().Includes(x => x.Locs).Single(x => x.Id == row.Id);
+                palletizingRow.QtyMaxCount = palletizingRow.Locs.Count(x => !x.IsEmpty);
+                palletizingRow.IsEmpty = palletizingRow.QtyMaxCount <= 0;
+                palletizingRow.MatCodeList = palletizingRow.Locs.Select(x => x.MatCode).ToList().GetMatList();
+                db.UpdateableRowLock(palletizingRow).ExecuteCommand();
+                //更新层信息
+                palletizingLayer = db.Queryable<WCS_PalletizingLayer>().Includes(x => x.Rows, l => l.Locs).Single(x => x.Id == layer.Id);
+                var count = palletizingLayer.Rows.Count(x => !x.IsEmpty); //计算所有不空数量
+                palletizingLayer.IsEmpty = count <= 0;
+                palletizingLayer.RowCountQty = palletizingLayer.Rows.Count;
+                palletizingLayer.Finish = false;
+                palletizingLayer.MatCodeList = palletizingLayer.Rows.SelectMany(x => x.Locs).Select(x => x.MatCode).ToList().GetMatList();
+
+                db.UpdateableRowLock(palletizingLayer).ExecuteCommand();
+            }
+
+            var palletizing1 = palletizing;
+            palletizing = db.Queryable<WCS_Palletizing>().Includes(x => x.Layers, r => r.Rows, l => l.Locs).Single(x => x.Id == palletizing1.Id);
+            //计算垛形信息
+            var goods = palletizing.Layers.Select(x => x.Rows).SelectMany(x => x).Select(x => x.Locs).SelectMany(x => x).ToList();
+            palletizing.CountQty = goods.Count(x => !x.IsEmpty);
+            palletizing.MatCodeList = palletizing.Layers.SelectMany(x => x.Rows).SelectMany(x => x.Locs).Select(x => x.MatCode).ToList().GetMatList();
+            db.UpdateableRowLock(palletizing).ExecuteCommand();
+        }
+
+        public static string GetMatList(this List<string> matList)
+        {
+            return matList.Distinct().Aggregate("", (current, mat) => current + $"[{mat}]");
+        }
+
+        /// <summary>
+        ///  去除转义字符
+        /// </summary>
+        /// <param name="value"></param>
+        /// <returns></returns>
+        public static string RemoveEscapeCharacters(this string? value)
+        {
+            return value.Trim('\0', '\a', '\b', '\f', '\n', '\r', '\t', '\v').Trim();
+        }
+
+        #endregion 工字轮支线分流
+    }
+
+    /// <summary>
+    ///  垛形位信息
+    /// </summary>
+    public class StackPosInfo
+    {
+        /// <summary>
+        ///  任务号
+        /// </summary>
+        public int TaskNumber { get; set; }
+
+        /// <summary>
+        /// 是否空置
+        /// </summary>
+        public bool IsEmpty { get; set; }
+
+        /// <summary>
+        /// 坐标号
+        /// </summary>
+        public string XYNo { get; set; }
+
+        /// <summary>
+        /// 物料编码
+        /// </summary>
+        public string MatCode { get; set; }
+
+        /// <summary>
+        /// 正反面
+        /// </summary>
+        public int SideNum { get; set; }
+
+        /// <summary>
+        /// 工字轮类型
+        /// </summary>
+        public string SpoolType { get; set; }
+
+        /// <summary>
+        ///  是否结束
+        /// </summary>
+        public bool Finish { get; set; }
+    }
+
+    /// <summary>
+    /// 垛型明细表
+    /// </summary>
+    [SugarTable("Bill_BomSetInfo", tableDescription: "垛型明细表")]
+    public partial class BillBomsetinfo
+    {
+        /// <summary>
+        ///  ID
+        /// </summary>
+        [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, ColumnDescription = "ID")]
+        public virtual long Id { get; set; }
+
+        /// <summary>
+        ///  备注
+        /// </summary>
+        [SugarColumn(ColumnName = "Memo", Length = 500, IsNullable = true, ColumnDataType = "nvarchar", DefaultValue = "", ColumnDescription = "备注")]
+        public virtual string Memo { get; set; }
+
+        /// <summary>
+        ///  创建用户
+        /// </summary>
+        [SugarColumn(ColumnName = "AddWho", Length = 50, ColumnDataType = "nvarchar", DefaultValue = "", IsNullable = false, ColumnDescription = "创建用户")]
+        public virtual string AddWho { get; set; } = "";
+
+        /// <summary>
+        ///  更新用户
+        /// </summary>
+        [SugarColumn(ColumnName = "EditWho", Length = 50, ColumnDataType = "nvarchar", DefaultValue = "", IsNullable = false, ColumnDescription = "更新用户")]
+        public virtual string EditWho { get; set; } = "";
+
+        /// <summary>
+        ///  创建时间
+        /// </summary>
+        [SugarColumn(ColumnName = "AddTime", DefaultValue = "1900-1-1", IsNullable = false, ColumnDescription = "创建时间")]
+        public virtual DateTime AddTime { get; set; } = DateTime.Now;
+
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        [SugarColumn(ColumnName = "EditTime", DefaultValue = "1900-1-1", IsNullable = false, ColumnDescription = "更新时间")]
+        public virtual DateTime EditTime { get; set; } = DateTime.Now;
+
+        /// <summary>
+        /// 垛型id
+        /// </summary>
+        [SugarColumn(ColumnDataType = "bigint", IsNullable = false, ColumnDescription = "垛型id")]
+        public long BomSetHdrId { get; set; }
+
+        /// <summary>
+        /// 是否停用
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "是否停用")]
+        public int IsStop { get; set; }
+
+        /// <summary>
+        /// 是否空置
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "是否空置")]
+        public int IsEmpty { get; set; }
+
+        /// <summary>
+        /// 坐标号
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = false, ColumnDescription = "坐标号")]
+        public string XYNo { get; set; }
+
+        /// <summary>
+        /// 物料id
+        /// </summary>
+        [SugarColumn(ColumnDataType = "bigint", ColumnDescription = "物料id")]
+        public long MatId { get; set; }
+
+        /// <summary>
+        /// 物料编码
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", IsNullable = true, Length = 50, ColumnDescription = "物料编码")]
+        public string MatCode { get; set; }
+
+        /// <summary>
+        /// 正反面
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = true, ColumnDescription = "正反面")]
+        public int SideNum { get; set; }
+
+        /// <summary>
+        /// 工字轮类型
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true, ColumnDescription = "工字轮类型")]
+        public string SpoolType { get; set; }
+
+        /// <summary>
+        /// 钢丝类型
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = true, ColumnDescription = "钢丝类型")]
+        public string SilkType { get; set; }
+
+        /// <summary>
+        /// 钢丝直径
+        /// </summary>
+        [SugarColumn(ColumnDataType = "decimal", Length = 18, IsNullable = true, ColumnDescription = "钢丝直径")]
+        public decimal SilkDiam { get; set; }
+
+        /// <summary>
+        /// 钢丝直径上限
+        /// </summary>
+        [SugarColumn(ColumnDataType = "decimal", Length = 18, IsNullable = true, ColumnDescription = "钢丝直径上限")]
+        public decimal SilkDiamMaxCount { get; set; }
+
+        /// <summary>
+        /// 数量上限
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = true, ColumnDescription = "数量上限")]
+        public int QtyMaxCount { get; set; }
+
+        /// <summary>
+        /// 是否芯股
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "是否芯股")]
+        public int IsCore { get; set; }
+
+        /// <summary>
+        /// 09垛型里面的行(1-12)
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = true, ColumnDescription = "09垛型里面的行(1-12)")]
+        public int Row { get; set; }
+
+        /// <summary>
+        /// 是否混合料行(09垛型使用,1是0否)
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "是否混合料行(09垛型使用,1是0否)")]
+        public int IsMixRow { get; set; }
+
+        /// <summary>
+        /// 混合料行编码
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", IsNullable = true, Length = 50, ColumnDescription = "混合料行编码")]
+        public string MixRowCode { get; set; }
+
+        /// <summary>
+        ///  第一种第二种第三种
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "第一种第二种第三种")]
+        public int CategoryId { get; set; }
+    }
+
+    /// <summary>
+    ///  垛形主表
+    /// </summary>
+    [Tenant("fj")]
+    [SugarTable("Bill_BomSetGrp", tableDescription: "垛形主表")]
+    public partial class BillBomsetgrp
+    {
+        /// <summary>
+        ///  ID
+        /// </summary>
+        [SugarColumn(ColumnName = "Id", IsPrimaryKey = true, ColumnDescription = "ID")]
+        public virtual long Id { get; set; }
+
+        /// <summary>
+        ///  备注
+        /// </summary>
+        [SugarColumn(ColumnName = "Memo", Length = 500, IsNullable = true, ColumnDataType = "nvarchar", DefaultValue = "", ColumnDescription = "备注")]
+        public virtual string Memo { get; set; }
+
+        /// <summary>
+        ///  创建用户
+        /// </summary>
+        [SugarColumn(ColumnName = "AddWho", Length = 50, ColumnDataType = "nvarchar", DefaultValue = "", IsNullable = false, ColumnDescription = "创建用户")]
+        public virtual string AddWho { get; set; } = "";
+
+        /// <summary>
+        ///  更新用户
+        /// </summary>
+        [SugarColumn(ColumnName = "EditWho", Length = 50, ColumnDataType = "nvarchar", DefaultValue = "", IsNullable = false, ColumnDescription = "更新用户")]
+        public virtual string EditWho { get; set; } = "";
+
+        /// <summary>
+        ///  创建时间
+        /// </summary>
+        [SugarColumn(ColumnName = "AddTime", DefaultValue = "1900-1-1", IsNullable = false, ColumnDescription = "创建时间")]
+        public virtual DateTime AddTime { get; set; } = DateTime.Now;
+
+        /// <summary>
+        /// 更新时间
+        /// </summary>
+        [SugarColumn(ColumnName = "EditTime", DefaultValue = "1900-1-1", IsNullable = false, ColumnDescription = "更新时间")]
+        public virtual DateTime EditTime { get; set; } = DateTime.Now;
+
+        /// <summary>
+        /// 是否停用
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "是否停用")]
+        public int IsStop { get; set; }
+
+        /// <summary>
+        /// 垛型编码
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = false, ColumnDescription = "垛型编码")]
+        public string Code { get; set; }
+
+        /// <summary>
+        ///  短垛型编码
+        /// </summary>
+        [SugarColumn(ColumnDataType = "smallint", IsNullable = false, ColumnDescription = "短垛型编码")]
+        public short ShortCode { get; set; }
+
+        /// <summary>
+        /// 垛型名称
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 100, IsNullable = false, ColumnDescription = "垛型名称")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// BomCode(投料信息)
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 200, IsNullable = false, ColumnDescription = "BomCode(投料信息)")]
+        public string BomCode { get; set; }
+
+        /// <summary>
+        /// 帘线物料编码
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = false, ColumnDescription = "帘线物料编码")]
+        public string ProMaterCode { get; set; }
+
+        /// <summary>
+        /// 工字轮个数
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "工字轮个数")]
+        public int HWCountQty { get; set; }
+
+        /// <summary>
+        /// 托盘类型
+        /// </summary>
+        [SugarColumn(ColumnDataType = "nvarchar", Length = 50, IsNullable = false, ColumnDescription = "托盘类型")]
+        public string TpTypeCode { get; set; }
+
+        /// <summary>
+        /// 层数
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = false, ColumnDescription = "层数")]
+        public int LayerCountQty { get; set; }
+
+        /// <summary>
+        /// 垛型大类
+        /// </summary>
+        [SugarColumn(ColumnDataType = "int", IsNullable = true, ColumnDescription = "垛型大类")]
+        public int StampType { get; set; }
+    }
+}

+ 12 - 0
WCS.WorkEngineering/Extensions/TrussExtebsion.cs

@@ -0,0 +1,12 @@
+using WCS.Core;
+using WCS.Entity.Protocol.Truss;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    public class Truss : Device<ITruss520, ITruss521, ITruss523>
+    {
+        public Truss(Device device, World world) : base(device, world)
+        {
+        }
+    }
+}

+ 95 - 0
WCS.WorkEngineering/Extensions/WorldExtension.cs

@@ -0,0 +1,95 @@
+using ServiceCenter.Logs;
+using WCS.Core;
+using LogInfo = ServiceCenter.Logs.LogInfo;
+
+namespace WCS.WorkEngineering.Extensions
+{
+    public static class WorldExtension
+    {
+        /// <summary>
+        ///  记录日志
+        ///  默认:低级别 已知 不上抛
+        /// </summary>
+        /// <param name="source">世界</param>
+        /// <param name="msg">消息</param>
+        public static void Log(this World source, string msg)
+        {
+            source.Log(new LogInfo { Level = LogLevelEnum.Low, Type = ErrorTypeEnum.Kown, LogUpLoad = LogUpLoadEnum.NotUpLoad, Message = msg });
+        }
+
+        /// <summary>
+        ///  记录日志
+        ///  默认:已知
+        ///  上抛根据日志级别决定:低级别不上抛,中高级别上抛WMS
+        /// </summary>
+        /// <param name="source">世界</param>
+        /// <param name="msg">消息</param>
+        /// <param name="level">日志级别</param>
+        public static void Log(this World source, string msg, LogLevelEnum level)
+        {
+            LogUpLoadEnum logUpLoad = LogUpLoadEnum.NotUpLoad;
+            switch (level)
+            {
+                case LogLevelEnum.Low:
+                    logUpLoad = LogUpLoadEnum.NotUpLoad;
+                    break;
+
+                case LogLevelEnum.Mid:
+                    logUpLoad = LogUpLoadEnum.UpLoadWMS;
+                    break;
+
+                case LogLevelEnum.High:
+                    logUpLoad = LogUpLoadEnum.UpLoadWMS;
+                    break;
+
+                default:
+                    break;
+            }
+            source.Log(new LogInfo { Level = level, Type = ErrorTypeEnum.Kown, LogUpLoad = logUpLoad, Message = msg });
+        }
+
+        /// <summary>
+        ///  记录日志
+        ///  上抛根据日志级别决定:低级别不上抛,中高级别上抛WMS
+        /// </summary>
+        /// <param name="source">世界</param>
+        /// <param name="msg">消息</param>
+        /// <param name="level">日志级别</param>
+        /// <param name="errorType">日志类型</param>
+        public static void Log(this World source, string msg, LogLevelEnum level, ErrorTypeEnum errorType)
+        {
+            LogUpLoadEnum logUpLoad = LogUpLoadEnum.NotUpLoad;
+            switch (level)
+            {
+                case LogLevelEnum.Low:
+                    logUpLoad = LogUpLoadEnum.NotUpLoad;
+                    break;
+
+                case LogLevelEnum.Mid:
+                    logUpLoad = LogUpLoadEnum.UpLoadWMS;
+                    break;
+
+                case LogLevelEnum.High:
+                    logUpLoad = LogUpLoadEnum.UpLoadWMS;
+                    break;
+
+                default:
+                    break;
+            }
+            source.Log(new LogInfo { Level = level, Type = errorType, LogUpLoad = logUpLoad, Message = msg });
+        }
+
+        /// <summary>
+        ///  记录日志
+        /// </summary>
+        /// <param name="source">世界</param>
+        /// <param name="msg">消息</param>
+        /// <param name="level">日志级别</param>
+        /// <param name="errorType">日志类型</param>
+        /// <param name="logUpLoad">日志上抛类型</param>
+        public static void Log(this World source, string msg, LogLevelEnum level, ErrorTypeEnum errorType, LogUpLoadEnum logUpLoad)
+        {
+            source.Log(new LogInfo { Level = level, Type = errorType, LogUpLoad = logUpLoad, Message = msg });
+        }
+    }
+}

+ 9 - 0
WCS.WorkEngineering/LockHub.cs

@@ -0,0 +1,9 @@
+namespace WCS.WorkEngineering
+{
+    public static class LockHub
+    {
+        public static object ApplyEmptySpoolLock = new object();
+
+        public static object AgvCallbackLock = new object();
+    }
+}

+ 239 - 0
WCS.WorkEngineering/ProtocolProxy.cs

@@ -0,0 +1,239 @@
+using ServiceCenter.SqlSugars;
+using WCS.Core;
+using WCS.Entity.Protocol;
+using WCS.Entity.Protocol.BCR;
+using WCS.Entity.Protocol.DataStructure;
+using WCS.Entity.Protocol.Protocol.DataStructure;
+using WCS.Entity.Protocol.RGV;
+using WCS.Entity.Protocol.Robot;
+using WCS.Entity.Protocol.SRM;
+using WCS.Entity.Protocol.Station;
+using WCS.Entity.Protocol.Truss;
+using WCS.WorkEngineering.Systems;
+
+namespace WCS.WorkEngineering
+{
+    public class ProtocolProxy : ProtocolProxyBase
+    {
+        public static DeviceDataPack DataPack { get; set; } = new DeviceDataPack();
+
+        public ProtocolProxy(Device dev, ProtocolInfo info, Type protocolType, World world) : base(dev, info, protocolType, world)
+        {
+        }
+
+        protected override void DataChanged()
+        {
+            //if (Device.Code == "RGV1" && Info.DBInfo.No == 520)
+            //{
+            //    var a = Items;
+            //    //var dev=Device.Protocol(ProtocolType)
+            //    var b = a;
+            //}
+
+            //try
+            //{
+            //    var datas = DataCollectionSysyem.AllDatas;
+            //    if (Device.Code.All(char.IsNumber))
+            //    {
+            //        if (!datas.ContainsKey(Device.Code)) datas[Device.Code] = new StationData { Code = Device.Code };
+            //    }
+            //    else if (Device.Code.Contains("SRM"))
+            //    {
+            //        if (!datas.ContainsKey(Device.Code)) datas[Device.Code] = new SRMData { Code = Device.Code };
+            //    }
+            //    else if (Device.Code.Contains("Truss"))
+            //    {
+            //        if (!datas.ContainsKey(Device.Code)) datas[Device.Code] = new TrussData() { Code = Device.Code };
+            //    }
+            //    else if (Device.Code.Contains("Robot"))
+            //    {
+            //        if (!datas.ContainsKey(Device.Code)) datas[Device.Code] = new RobotData() { Code = Device.Code };
+            //    }
+            //    else if (Device.Code.Contains("RGV"))
+            //    {
+            //        if (!datas.ContainsKey(Device.Code)) datas[Device.Code] = new RGVData { Code = Device.Code };
+            //    }
+
+            //    if (!datas.TryGetValue(Device.Code, out var data)) return;
+            //    data.Frame = DateTime.Now;
+            //    var p = data.GetType().GetProperties().FirstOrDefault(v => v.PropertyType == ProtocolDataType);
+            //    //var ty90 = WCS_Station90;
+            //    //if (p.getty is ty90 or)
+            //    //{
+            //    //}
+            //    if (p == null) return;
+            //    p.SetValue(data, DictionaryToEntity(ProtocolDataType, Items, data));
+            //}
+            //catch (Exception ex)
+            //{
+            //    Console.WriteLine(ex.Message);
+            //}
+        }
+
+        public void DictionaryToEntity(Type type, Dictionary<string, PlcItem> plcItems, DeviceData data)
+        {
+            //var entity = Activator.CreateInstance(type);
+            ////采集量
+            //Parallel.ForEach(type.GetProperties(), ty =>
+            //{
+            //    if (plcItems.Any(x => x.Key == ty.Name))
+            //    {
+            //        var item = plcItems.First(x => ty.Name == x.Key);
+            //        ty.SetValue(entity, item.Value.Value);
+            //    }
+            //});
+
+            //Parallel.ForEach(type.GetProperties(), ty =>
+            //{
+            //    var items = data.GetType().GetProperties();
+            //    if (items.Any(x => x.Name == ty.Name))
+            //    {
+            //        var item = items.First(x => ty.Name == x.Name);
+            //        ty.SetValue(entity, item.GetValue(data));
+            //    }
+            //});
+
+            //foreach (var ty in type.GetProperties().Where(x => x.Name == nameof(TDengineBaseEntity.CreateSql)))
+            //{
+            //    var db = new SqlSugarHelper().PLC;
+            //    switch (type.Name)
+            //    {
+            //        case nameof(WCS_SRM520):
+            //            var item = entity as WCS_SRM520;
+            //            ty.SetValue(entity, GetString(db.Insertable(item).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_SRM521):
+            //            var item1 = entity as WCS_SRM521;
+            //            ty.SetValue(entity, GetString(db.Insertable(item1).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_SRM537):
+            //            var item2 = entity as WCS_SRM537;
+            //            ty.SetValue(entity, GetString(db.Insertable(item2).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_RGV520):
+            //            var item3 = entity as WCS_RGV520;
+            //            ty.SetValue(entity, GetString(db.Insertable(item3).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_RGV521):
+            //            var item4 = entity as WCS_RGV521;
+            //            ty.SetValue(entity, GetString(db.Insertable(item4).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_BCR80):
+            //            var item5 = entity as WCS_BCR80;
+            //            ty.SetValue(entity, GetString(db.Insertable(item5).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_BCR81):
+            //            var item6 = entity as WCS_BCR81;
+            //            ty.SetValue(entity, GetString(db.Insertable(item6).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_BCR83):
+            //            var item7 = entity as WCS_BCR83;
+            //            ty.SetValue(entity, GetString(db.Insertable(item7).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station520):
+            //            var item8 = entity as WCS_Station520;
+            //            ty.SetValue(entity, GetString(db.Insertable(item8).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station521):
+            //            var item9 = entity as WCS_Station521;
+            //            ty.SetValue(entity, GetString(db.Insertable(item9).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station523):
+            //            var item10 = entity as WCS_Station523;
+            //            ty.SetValue(entity, GetString(db.Insertable(item10).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station524):
+            //            var item11 = entity as WCS_Station524;
+            //            ty.SetValue(entity, GetString(db.Insertable(item11).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station525):
+            //            var item12 = entity as WCS_Station525;
+            //            ty.SetValue(entity, GetString(db.Insertable(item12).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station90):
+            //            var item13 = entity as WCS_Station90;
+            //            ty.SetValue(entity, GetString(db.Insertable(item13).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Station91):
+            //            var item14 = entity as WCS_Station91;
+            //            ty.SetValue(entity, GetString(db.Insertable(item14).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Truss520):
+            //            var item15 = entity as WCS_Truss520;
+            //            ty.SetValue(entity, GetString(db.Insertable(item15).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Truss521):
+            //            var item16 = entity as WCS_Truss521;
+            //            ty.SetValue(entity, GetString(db.Insertable(item16).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Truss523):
+            //            var item17 = entity as WCS_Truss523;
+            //            ty.SetValue(entity, GetString(db.Insertable(item17).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Truss530):
+            //            var item18 = entity as WCS_Truss530;
+            //            ty.SetValue(entity, GetString(db.Insertable(item18).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Truss531):
+            //            var item19 = entity as WCS_Truss531;
+            //            ty.SetValue(entity, GetString(db.Insertable(item19).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Robot520):
+            //            var item20 = entity as WCS_Robot520;
+            //            ty.SetValue(entity, GetString(db.Insertable(item20).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Robot521):
+            //            var item21 = entity as WCS_Robot521;
+            //            ty.SetValue(entity, GetString(db.Insertable(item21).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Robot522):
+            //            var item22 = entity as WCS_Robot522;
+            //            ty.SetValue(entity, GetString(db.Insertable(item22).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Robot530):
+            //            var item23 = entity as WCS_Robot530;
+            //            ty.SetValue(entity, GetString(db.Insertable(item23).ToSqlString()));
+            //            break;
+
+            //        case nameof(WCS_Robot531):
+            //            var item24 = entity as WCS_Robot531;
+            //            ty.SetValue(entity, GetString(db.Insertable(item24).ToSqlString()));
+            //            break;
+            //    }
+            //}
+            //return entity;
+        }
+
+        public string GetString(string value)
+        {
+            return value.Replace("INSERT INTO ", "")
+                .Replace(",N'", ",'")
+                .Replace("\0", "")
+                .Replace("wcs_", "")
+                .Replace("(N'", "('") + "\r";
+        }
+    }
+}

+ 1027 - 0
WCS.WorkEngineering/Systems/DataCollectionSysyem.cs

@@ -0,0 +1,1027 @@
+using ServiceCenter.Extensions;
+using ServiceCenter.SqlSugars;
+using SqlSugar;
+using System.Collections.Concurrent;
+using System.ComponentModel;
+using System.Data;
+using WCS.Core;
+using WCS.Entity.Protocol.DataStructure;
+using WCS.Entity.Protocol.HUB;
+using WCS.Entity.Protocol.Robot;
+using WCS.Entity.Protocol.SRM;
+using WCS.Entity.Protocol.Station;
+using WCS.WorkEngineering.Worlds;
+
+namespace WCS.WorkEngineering.Systems
+{
+    /// <summary>
+    ///  数据采集系统
+    /// </summary>
+    [BelongTo(typeof(MainWorld))]
+    [Description("数据采集系统")]
+    public class DataCollectionSysyem : DeviceSystem<Device<IStation520>>
+    {
+        public static DeviceDataPack pack = new DeviceDataPack();
+
+        private static object locker = new object();
+
+        public DataCollectionSysyem()
+        {
+        }
+
+        /// <summary>
+        ///  所有设备数据
+        ///  Key 是不同设备所使用的类型 例如DeviceDataCollection<SRMData>
+        ///  value 不同设备的具体数据
+        /// </summary>
+        public static ConcurrentDictionary<string, DeviceData> AllDatas = new ConcurrentDictionary<string, DeviceData>();
+
+        protected override bool ParallelDo => true;
+
+        public override bool Select(Device dev)
+        {
+            return dev.Code == "1";
+        }
+
+        public override void Do(Device<IStation520> objDev)
+        {
+            //通过数据条数处理
+            var db = new SqlSugarHelper().PLC;
+            var plcEx = new SqlSugarHelper().PLCEX;
+            ExRobotRunInfo(db, plcEx);
+            ExSrmRunInfo(db, plcEx);
+            //ExRobotAlarmInfo(db);
+            //ExSrmAlarmInfo(db);
+            //ExRgvAlarmInfo(db);
+            ExSrmTaskSumRunInfo(db, plcEx);
+        }
+
+        #region 设备运行信息收集
+
+        /// <summary>
+        /// 机械臂
+        /// </summary>
+        /// <param name="db"></param>
+        public void ExRobotRunInfo(SqlSugarScopeProvider db, SqlSugarScopeProvider plcEx)
+        {
+            var startTime = DateTime.Now;
+            var endTime = DateTime.Now;
+            IEnumerable<IGrouping<string, WCS_Robot521>> robot521 = new List<IGrouping<string, WCS_Robot521>>();
+            var type = DevType.Robot.ToString();
+            //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+            if (plcEx.Queryable<DevRunInfo>().Any(x => x.Type == type))
+            {
+                //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+                var runInfo = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type).OrderByDescending(x => x.Frame).First();
+                startTime = runInfo.Frame.AddHours(+1);//后一个小时
+                startTime = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); //后一个小时的开始时间
+                endTime = startTime.AddHours(+1).AddMilliseconds(-1);//
+            }
+            else
+            {
+                if (!db.Queryable<WCS_Robot521>().Any()) return;
+                startTime = db.Queryable<WCS_Robot521>().OrderBy(x => x.Frame).First().Frame;
+                startTime = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); //后一个小时的开始时间
+                endTime = startTime.AddHours(+1).AddMilliseconds(-1);
+            }
+            //取上一小时的第一秒
+            var now = DateTime.Now;
+            now = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0);
+            var nowEndTime = new DateTime(endTime.Year, endTime.Month, endTime.Day, endTime.Hour, 0, 0);
+            if (endTime > now) return; //当前时间之前
+            if (nowEndTime == now) return; //是当前这一个小时
+            now = new DateTime(now.Year, now.Month, now.Day, now.Hour, 1, 0);
+            if (DateTime.Now < now) return;// 每个小时的前十分钟不进行数据分析处理,仅在每小时的10-59之内进行处理
+
+            if (plcEx.Queryable<DevRunInfo>().Any(x => x.EndTime == endTime && x.Type == type)) return;
+
+            robot521 = db.Queryable<WCS_Robot521>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+            if (!robot521.Any())
+            {
+                plcEx.Insertable(new DevRunInfo()
+                {
+                    Frame = endTime,
+                    Code = "当前时段无有效数据",
+                    RunMode = "当前时段无有效数据",
+                    RunStatus = "当前时段无有效数据",
+                    StartTime = startTime,
+                    EndTime = endTime,
+                    Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+                    Type = type
+                }).ExecuteCommand();
+            }
+
+            foreach (var infos in robot521)
+            {
+                // 获取第一条数据与最后一条数据
+                var start = infos.OrderBy(x => x.Frame).First();
+                var end = infos.OrderByDescending(x => x.Frame).First();
+                //通过是否有最后一条数据来确定当前数据是否有分析存储过
+                if (plcEx.Queryable<DevRunInfo>().Any(x => x.Code == infos.Key && x.EndTime == end.Frame)) return;
+                //获取前一天最后的一条数据
+                var yesterEnd = plcEx.Queryable<DevRunInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
+                List<DevRunInfo> runInfos = new List<DevRunInfo>();
+                foreach (var info in infos.OrderBy(x => x.Frame))
+                {
+                    if (info.Frame == start.Frame) //当天的第一条数据
+                    {
+                        if (yesterEnd == null) //如果没有前一天的最后一条数据,就有用当天的第一条数据状态做计算
+                        {
+                            runInfos.Add(new DevRunInfo()
+                            {
+                                Frame = info.Frame,
+                                Code = info.Code,
+                                RunMode = info.Mode.GetDescription(),
+                                RunStatus = info.Status.GetDescription(),
+                                StartTime = startTime,
+                                EndTime = info.Frame,
+                                Duration = Convert.ToInt64((info.Frame - startTime).TotalMilliseconds),
+                                Type = type
+                            });
+                        }
+                        else //如果有就用前一天的最后一条数据做计算
+                        {
+                            runInfos.Add(new DevRunInfo()
+                            {
+                                Frame = info.Frame,
+                                Code = info.Code,
+                                RunMode = yesterEnd.RunMode,
+                                RunStatus = yesterEnd.RunStatus,
+                                StartTime = startTime,
+                                EndTime = info.Frame,
+                                Duration = Convert.ToInt64((info.Frame - startTime).TotalMilliseconds),
+                                Type = type
+                            });
+                        }
+                    }
+                    else //状态出现状态变化时或最后一条数据
+                    {
+                        if (start.Mode != info.Mode || start.Status != info.Status || info.Frame == end.Frame)
+                        {
+                            var runInfo = new DevRunInfo()
+                            {
+                                Frame = info.Frame,
+                                Code = info.Code,
+                                RunMode = start.Mode.GetDescription(),
+                                RunStatus = start.Status.GetDescription(),
+                                StartTime = start.Frame,
+                                Type = type
+                            };
+                            if (info.Frame == end.Frame) runInfo.EndTime = endTime;
+                            else runInfo.EndTime = info.Frame;
+                            runInfo.Duration = Convert.ToInt64((runInfo.EndTime - runInfo.StartTime).TotalMilliseconds);
+                            runInfos.Add(runInfo);
+                            start = info;
+                        }
+                    }
+                }
+
+                var sql = plcEx.Insertable(runInfos).ToSqlString();
+                plcEx.Ado.ExecuteCommand(GetString(sql));
+            }
+        }
+
+        /// <summary>
+        /// 堆垛机
+        /// </summary>
+        /// <param name="db"></param>
+        public void ExSrmRunInfo(SqlSugarScopeProvider db, SqlSugarScopeProvider plcEx)
+        {
+            var startTime = DateTime.Now;
+            var endTime = DateTime.Now;
+            IEnumerable<IGrouping<string, WCS_SRM521>> srm521 = new List<IGrouping<string, WCS_SRM521>>();
+            var type = DevType.SRM.ToString();
+            //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+            if (plcEx.Queryable<DevRunInfo>().Any(x => x.Type == type))
+            {
+                //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+                var runInfo = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type).OrderByDescending(x => x.Frame).First();
+                startTime = runInfo.Frame.AddHours(+1);//后一个小时
+                startTime = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); //后一个小时的开始时间
+                endTime = startTime.AddHours(+1).AddMilliseconds(-1);//
+            }
+            else
+            {
+                if (!db.Queryable<WCS_SRM521>().Any()) return;
+                startTime = db.Queryable<WCS_SRM521>().OrderBy(x => x.Frame).First().Frame;
+                startTime = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); //后一个小时的开始时间
+                endTime = startTime.AddHours(+1).AddMilliseconds(-1);
+            }
+            //取上一小时的第一秒
+            var now = DateTime.Now;
+            now = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0);
+            var nowEndTime = new DateTime(endTime.Year, endTime.Month, endTime.Day, endTime.Hour, 0, 0);
+            if (endTime > now) return; //当前时间之前
+            if (nowEndTime == now) return; //是当前这一个小时
+            now = new DateTime(now.Year, now.Month, now.Day, now.Hour, 10, 0);
+            if (DateTime.Now < now) return;// 每个小时的前十分钟不进行数据分析处理,仅在每小时的10-59之内进行处理
+            if (plcEx.Queryable<DevRunInfo>().Any(x => x.EndTime == endTime && x.Type == type)) return;
+
+            srm521 = db.Queryable<WCS_SRM521>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+            if (!srm521.Any())
+            {
+                plcEx.Insertable(new DevRunInfo()
+                {
+                    Frame = endTime,
+                    Code = "当前时段无有效数据",
+                    RunMode = "当前时段无有效数据",
+                    RunStatus = "当前时段无有效数据",
+                    StartTime = startTime,
+                    EndTime = endTime,
+                    Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+                    Type = type
+                }).ExecuteCommand();
+            }
+
+            foreach (var infos in srm521)
+            {
+                // 获取第一条数据与最后一条数据
+                var start = infos.OrderBy(x => x.Frame).First();
+                var end = infos.OrderByDescending(x => x.Frame).First();
+                //通过是否有最后一条数据来确定当前数据是否有分析存储过
+                if (plcEx.Queryable<DevRunInfo>().Any(x => x.Code == infos.Key && x.EndTime == end.Frame)) return;
+                //获取前一天最后的一条数据
+                var yesterEnd = plcEx.Queryable<DevRunInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
+                List<DevRunInfo> runInfos = new List<DevRunInfo>();
+                foreach (var info in infos.OrderBy(x => x.Frame))
+                {
+                    if (info.Frame == start.Frame) //当天的第一条数据
+                    {
+                        if (yesterEnd == null) //如果没有前一天的最后一条数据,就有用当天的第一条数据状态做计算
+                        {
+                            runInfos.Add(new DevRunInfo()
+                            {
+                                Frame = info.Frame,
+                                Code = info.Code,
+                                RunMode = info.Mode.GetDescription(),
+                                RunStatus = info.Status.GetDescription(),
+                                StartTime = startTime,
+                                EndTime = info.Frame,
+                                Duration = Convert.ToInt64((info.Frame - startTime).TotalMilliseconds),
+                                Type = type
+                            });
+                        }
+                        else //如果有就用前一天的最后一条数据做计算
+                        {
+                            runInfos.Add(new DevRunInfo()
+                            {
+                                Frame = info.Frame,
+                                Code = info.Code,
+                                RunMode = yesterEnd.RunMode,
+                                RunStatus = yesterEnd.RunStatus,
+                                StartTime = startTime,
+                                EndTime = info.Frame,
+                                Duration = Convert.ToInt64((info.Frame - startTime).TotalMilliseconds),
+                                Type = type
+                            });
+                        }
+                    }
+                    else //状态出现状态变化时或最后一条数据
+                    {
+                        if (start.Mode != info.Mode || start.Status != info.Status || info.Frame == end.Frame)
+                        {
+                            var runInfo = new DevRunInfo()
+                            {
+                                Frame = info.Frame,
+                                Code = info.Code,
+                                RunMode = start.Mode.GetDescription(),
+                                RunStatus = start.Status.GetDescription(),
+                                StartTime = start.Frame,
+                                Type = type
+                            };
+                            if (info.Frame == end.Frame) runInfo.EndTime = endTime;
+                            else runInfo.EndTime = info.Frame;
+                            runInfo.Duration = Convert.ToInt64((runInfo.EndTime - runInfo.StartTime).TotalMilliseconds);
+                            runInfos.Add(runInfo);
+                            start = info;
+                        }
+                    }
+                }
+
+                var sql = plcEx.Insertable(runInfos).ToSqlString();
+                plcEx.Ado.ExecuteCommand(GetString(sql));
+            }
+        }
+
+        #endregion 设备运行信息收集
+
+        #region 异常报警信息
+
+        ///// <summary>
+        ///// 机械臂
+        ///// </summary>
+        ///// <param name="db"></param>
+        //public void ExRobotAlarmInfo(SqlSugarScopeProvider db)
+        //{
+        //    var startTime = DateTime.Now;
+        //    var endTime = DateTime.Now;
+        //    IEnumerable<IGrouping<string, QuestDb_Robot522>> robot522 = new List<IGrouping<string, QuestDb_Robot522>>();
+        //    //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+        //    if (db.Queryable<DevAlarmInfo>().Any(x => x.Type == DevType.Robot))
+        //    {
+        //        //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+        //        var alarmInfo = db.Queryable<DevAlarmInfo>().Where(x => x.Type == DevType.Robot).OrderByDescending(x => x.Frame).First();
+        //        startTime = alarmInfo.Frame.AddDays(+1).Date;//后一天的初始时间
+        //        endTime = startTime.AddDays(+1).AddMilliseconds(-1);//
+        //    }
+        //    else
+        //    {
+        //        if (!db.Queryable<QuestDb_Robot522>().Any()) return;
+        //        startTime = db.Queryable<QuestDb_Robot522>().OrderBy(x => x.Frame).First().Frame.Date;
+        //        endTime = startTime.AddDays(+1).AddMilliseconds(-1);
+        //    }
+        //    if (endTime > DateTime.Now.AddHours(1)) return; //一次处理24小时的数据
+        //    if (db.Queryable<DevAlarmInfo>().Any(x => x.EndTime == endTime && x.Type == DevType.Robot)) return;
+
+        //    robot522 = db.Queryable<QuestDb_Robot522>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+        //    if (!robot522.Any())
+        //    {
+        //        db.Insertable(new DevAlarmInfo()
+        //        {
+        //            Frame = endTime,
+        //            Code = "当前时段无有效数据",
+        //            Alarm = "当前时段无有效数据",
+        //            StartTime = startTime,
+        //            EndTime = endTime,
+        //            Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+        //            Type = DevType.Robot
+        //        }).ExecuteCommand();
+        //    }
+
+        //    Parallel.ForEach(robot522, infos =>
+        //    {
+        //        // 获取第一条数据与最后一条数据
+        //        var start = infos.OrderBy(x => x.Frame).First();
+        //        var end = infos.OrderByDescending(x => x.Frame).First();
+        //        //通过是否有最后一条数据来确定当前数据是否有分析存储过
+        //        if (db.Queryable<DevAlarmInfo>().Any(x => x.Code == infos.Key && x.EndTime == end.Frame)) return;
+        //        //获取前一天最后的一条数据
+        //        var yesterEnd = db.Queryable<DevAlarmInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
+        //        List<DevAlarmInfo> runInfos = new List<DevAlarmInfo>();//需要保存的数据
+        //        var alarmList = new List<EquipmentAlarm>();//未结束的报警信息
+        //        foreach (var info in infos.OrderBy(x => x.Frame))
+        //        {
+        //            if (info.Frame == end.Frame) //最后一条数据
+        //            {
+        //                if (info.Alarm != "无")
+        //                {
+        //                    //先判断是否有没有记录的报警信息
+        //                    var alarmMsg = Convert.ToString(info.Alarm).Split(",").Select(x => x.Split(",")).SelectMany(x => x).ToList();
+        //                    var msgList = alarmList.Select(x => x.Msg);
+
+        //                    //将未记录的报警信息增加到缓存中
+        //                    alarmList.AddRange(alarmMsg.Where(msg => !msgList.Contains(msg)).Select(msg => new EquipmentAlarm() { Time = info.Frame, Msg = msg }));
+        //                }
+
+        //                if (!alarmList.Any()) continue;
+        //                runInfos.AddRange(alarmList.Select(alarm => new DevAlarmInfo()
+        //                {
+        //                    Frame = alarm.Time,
+        //                    Code = info.Code,
+        //                    Alarm = alarm.Msg,
+        //                    StartTime = alarm.Time,
+        //                    EndTime = endTime,
+        //                    Duration = Convert.ToInt64((endTime - alarm.Time).TotalMilliseconds),
+        //                    Type = DevType.Robot
+        //                }));
+        //            }
+        //            else
+        //            {
+        //                if (info.Alarm == "无" && alarmList.Any()) //代表报警全部清空了
+        //                {
+        //                    runInfos.AddRange(alarmList.Select(alarm => new DevAlarmInfo()
+        //                    {
+        //                        Frame = alarm.Time,
+        //                        Code = info.Code,
+        //                        Alarm = alarm.Msg,
+        //                        StartTime = alarm.Time,
+        //                        EndTime = info.Frame,
+        //                        Duration = Convert.ToInt64((info.Frame - alarm.Time).TotalMilliseconds),
+        //                        Type = DevType.Robot
+        //                    }));
+
+        //                    alarmList = new List<EquipmentAlarm>(); //清空现有报警信息
+        //                }
+        //                else if (info.Alarm != "无")
+        //                {
+        //                    var alarmMsg = Convert.ToString(info.Alarm).Split(",").Select(x => x.Split(",")).SelectMany(x => x).ToList();
+        //                    var msgList = alarmList.Select(x => x.Msg);
+
+        //                    //将未记录的报警信息增加到缓存中
+        //                    alarmList.AddRange(alarmMsg.Where(msg => !msgList.Contains(msg)).Select(msg => new EquipmentAlarm() { Time = info.Frame, Msg = msg }));
+        //                    var treatedAlarm = new List<string>();
+        //                    //将已记录,但当前信息中没有的报警结束
+        //                    foreach (var alarm in alarmList.Where(x => !alarmMsg.Contains(x.Msg)))
+        //                    {
+        //                        runInfos.Add(new DevAlarmInfo()
+        //                        {
+        //                            Frame = alarm.Time,
+        //                            Code = info.Code,
+        //                            Alarm = alarm.Msg,
+        //                            StartTime = alarm.Time,
+        //                            EndTime = info.Frame,
+        //                            Duration = Convert.ToInt64((info.Frame - alarm.Time).TotalMilliseconds),
+        //                            Type = DevType.Robot
+        //                        });
+        //                        treatedAlarm.Add(alarm.Msg);
+        //                    }
+
+        //                    alarmList = alarmList.Where(x => !treatedAlarm.Contains(x.Msg)).ToList();
+        //                }
+        //            }
+        //        }
+
+        //        if (runInfos.Any())
+        //        {
+        //            var sql = db.Insertable(runInfos).ToSqlString();
+        //            db.Ado.ExecuteCommand(GetString(sql));
+        //        }
+        //        else
+        //        {
+        //            db.Insertable(new DevAlarmInfo()
+        //            {
+        //                Frame = endTime,
+        //                Code = infos.Key,
+        //                Alarm = "当前时段无有效数据",
+        //                StartTime = startTime,
+        //                EndTime = endTime,
+        //                Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+        //                Type = DevType.Robot
+        //            }).ExecuteCommand();
+        //        }
+        //    });
+        //}
+
+        ///// <summary>
+        ///// 堆垛机
+        ///// </summary>
+        ///// <param name="db"></param>
+        //public void ExSrmAlarmInfo(SqlSugarScopeProvider db)
+        //{
+        //    var startTime = DateTime.Now;
+        //    var endTime = DateTime.Now;
+        //    IEnumerable<IGrouping<string, QuestDb_SRM523>> srm523 = new List<IGrouping<string, QuestDb_SRM523>>();
+        //    //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+        //    if (db.Queryable<DevAlarmInfo>().Any(x => x.Type == DevType.SRM))
+        //    {
+        //        //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+        //        var alarmInfo = db.Queryable<DevAlarmInfo>().Where(x => x.Type == DevType.SRM).OrderByDescending(x => x.Frame).First();
+        //        startTime = alarmInfo.Frame.AddDays(+1).Date;//后一天的初始时间
+        //        endTime = startTime.AddDays(+1).AddMilliseconds(-1);//
+        //    }
+        //    else
+        //    {
+        //        if (!db.Queryable<QuestDb_SRM523>().Any()) return;
+        //        startTime = db.Queryable<QuestDb_SRM523>().OrderBy(x => x.Frame).First().Frame.Date;
+        //        endTime = startTime.AddDays(+1).AddMilliseconds(-1);
+        //    }
+        //    if (endTime > DateTime.Now.AddHours(1)) return; //一次处理24小时的数据
+        //    if (db.Queryable<DevAlarmInfo>().Any(x => x.EndTime == endTime && x.Type == DevType.SRM)) return;
+
+        //    srm523 = db.Queryable<QuestDb_SRM523>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+        //    if (!srm523.Any())
+        //    {
+        //        db.Insertable(new DevAlarmInfo()
+        //        {
+        //            Frame = endTime,
+        //            Code = "当前时段无有效数据",
+        //            Alarm = "当前时段无有效数据",
+        //            StartTime = startTime,
+        //            EndTime = endTime,
+        //            Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+        //            Type = DevType.SRM
+        //        }).ExecuteCommand();
+        //    }
+
+        //    Parallel.ForEach(srm523, infos =>
+        //    {
+        //        // 获取第一条数据与最后一条数据
+        //        var start = infos.OrderBy(x => x.Frame).First();
+        //        var end = infos.OrderByDescending(x => x.Frame).First();
+        //        //通过是否有最后一条数据来确定当前数据是否有分析存储过
+        //        if (db.Queryable<DevAlarmInfo>().Any(x => x.Code == infos.Key && x.EndTime == end.Frame)) return;
+        //        //获取前一天最后的一条数据
+        //        var yesterEnd = db.Queryable<DevAlarmInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
+        //        List<DevAlarmInfo> runInfos = new List<DevAlarmInfo>();//需要保存的数据
+        //        var alarmList = new List<EquipmentAlarm>();//未结束的报警信息
+        //        foreach (var info in infos.OrderBy(x => x.Frame))
+        //        {
+        //            if (info.Frame == end.Frame) //最后一条数据
+        //            {
+        //                if (info.Alarm != "无,无")
+        //                {
+        //                    //先判断是否有没有记录的报警信息
+        //                    var alarmMsg = Convert.ToString(info.Alarm).Split(",").Select(x => x.Split(",")).SelectMany(x => x).Where(x => x != "无").ToList();
+        //                    var msgList = alarmList.Select(x => x.Msg);
+
+        //                    //将未记录的报警信息增加到缓存中
+        //                    alarmList.AddRange(alarmMsg.Where(msg => !msgList.Contains(msg)).Select(msg => new EquipmentAlarm() { Time = info.Frame, Msg = msg }));
+        //                }
+
+        //                if (!alarmList.Any()) continue;
+        //                runInfos.AddRange(alarmList.Select(alarm => new DevAlarmInfo()
+        //                {
+        //                    Frame = alarm.Time,
+        //                    Code = info.Code,
+        //                    Alarm = alarm.Msg,
+        //                    StartTime = alarm.Time,
+        //                    EndTime = endTime,
+        //                    Duration = Convert.ToInt64((endTime - alarm.Time).TotalMilliseconds),
+        //                    Type = DevType.SRM
+        //                }));
+        //            }
+        //            else
+        //            {
+        //                if (info.Alarm == "无,无" && alarmList.Any()) //代表报警全部清空了
+        //                {
+        //                    runInfos.AddRange(alarmList.Select(alarm => new DevAlarmInfo()
+        //                    {
+        //                        Frame = alarm.Time,
+        //                        Code = info.Code,
+        //                        Alarm = alarm.Msg,
+        //                        StartTime = alarm.Time,
+        //                        EndTime = info.Frame,
+        //                        Duration = Convert.ToInt64((info.Frame - alarm.Time).TotalMilliseconds),
+        //                        Type = DevType.SRM
+        //                    }));
+
+        //                    alarmList = new List<EquipmentAlarm>(); //清空现有报警信息
+        //                }
+        //                else if (info.Alarm != "无,无")
+        //                {
+        //                    var alarmMsg = Convert.ToString(info.Alarm).Split(",").Select(x => x.Split(",")).SelectMany(x => x).Where(x => x != "无").ToList();
+        //                    var msgList = alarmList.Select(x => x.Msg);
+
+        //                    //将未记录的报警信息增加到缓存中
+        //                    alarmList.AddRange(alarmMsg.Where(msg => !msgList.Contains(msg)).Select(msg => new EquipmentAlarm() { Time = info.Frame, Msg = msg }));
+        //                    var treatedAlarm = new List<string>();
+        //                    //将已记录,但当前信息中没有的报警结束
+        //                    foreach (var alarm in alarmList.Where(x => !alarmMsg.Contains(x.Msg)))
+        //                    {
+        //                        runInfos.Add(new DevAlarmInfo()
+        //                        {
+        //                            Frame = alarm.Time,
+        //                            Code = info.Code,
+        //                            Alarm = alarm.Msg,
+        //                            StartTime = alarm.Time,
+        //                            EndTime = info.Frame,
+        //                            Duration = Convert.ToInt64((info.Frame - alarm.Time).TotalMilliseconds),
+        //                            Type = DevType.SRM
+        //                        });
+        //                        treatedAlarm.Add(alarm.Msg);
+        //                    }
+
+        //                    alarmList = alarmList.Where(x => !treatedAlarm.Contains(x.Msg)).ToList();
+        //                }
+        //            }
+        //        }
+
+        //        if (runInfos.Any())
+        //        {
+        //            var sql = db.Insertable(runInfos).ToSqlString();
+        //            db.Ado.ExecuteCommand(GetString(sql));
+        //        }
+        //        else
+        //        {
+        //            db.Insertable(new DevAlarmInfo()
+        //            {
+        //                Frame = endTime,
+        //                Code = infos.Key,
+        //                Alarm = "当前时段无有效数据",
+        //                StartTime = startTime,
+        //                EndTime = endTime,
+        //                Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+        //                Type = DevType.SRM
+        //            }).ExecuteCommand();
+        //        }
+        //    });
+        //}
+
+        ///// <summary>
+        ///// rgv
+        ///// </summary>
+        ///// <param name="db"></param>
+        //public void ExRgvAlarmInfo(SqlSugarScopeProvider db)
+        //{
+        //    var startTime = DateTime.Now;
+        //    var endTime = DateTime.Now;
+        //    IEnumerable<IGrouping<string, QuestDb_RGV523>> rgv523 = new List<IGrouping<string, QuestDb_RGV523>>();
+        //    //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+        //    if (db.Queryable<DevAlarmInfo>().Any(x => x.Type == DevType.RGV))
+        //    {
+        //        //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+        //        var alarmInfo = db.Queryable<DevAlarmInfo>().Where(x => x.Type == DevType.RGV).OrderByDescending(x => x.Frame).First();
+        //        startTime = alarmInfo.Frame.AddDays(+1).Date;//后一天的初始时间
+        //        endTime = startTime.AddDays(+1).AddMilliseconds(-1);//
+        //    }
+        //    else
+        //    {
+        //        if (!db.Queryable<QuestDb_RGV523>().Any()) return;
+        //        startTime = db.Queryable<QuestDb_RGV523>().OrderBy(x => x.Frame).First().Frame.Date;
+        //        endTime = startTime.AddDays(+1).AddMilliseconds(-1);
+        //    }
+        //    if (endTime > DateTime.Now.AddHours(1)) return; //一次处理24小时的数据
+        //    if (db.Queryable<DevAlarmInfo>().Any(x => x.EndTime == endTime && x.Type == DevType.RGV)) return;
+
+        //    rgv523 = db.Queryable<QuestDb_RGV523>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+        //    if (!rgv523.Any())
+        //    {
+        //        db.Insertable(new DevAlarmInfo()
+        //        {
+        //            Frame = endTime,
+        //            Code = "当前时段无有效数据",
+        //            Alarm = "当前时段无有效数据",
+        //            StartTime = startTime,
+        //            EndTime = endTime,
+        //            Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+        //            Type = DevType.RGV
+        //        }).ExecuteCommand();
+        //    }
+
+        //    Parallel.ForEach(rgv523/*.Where(x => x.Key == "RGV4")*/, infos =>
+        //    {
+        //        // 获取第一条数据与最后一条数据
+        //        var start = infos.OrderBy(x => x.Frame).First();
+        //        var end = infos.OrderByDescending(x => x.Frame).First();
+        //        //通过是否有最后一条数据来确定当前数据是否有分析存储过
+        //        if (db.Queryable<DevAlarmInfo>().Any(x => x.Code == infos.Key && x.EndTime == end.Frame)) return;
+        //        //获取前一天最后的一条数据
+        //        var yesterEnd = db.Queryable<DevAlarmInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
+        //        List<DevAlarmInfo> runInfos = new List<DevAlarmInfo>();//需要保存的数据
+        //        var alarmList = new List<EquipmentAlarm>();//未结束的报警信息
+        //        foreach (var info in infos.OrderBy(x => x.Frame))
+        //        {
+        //            if (info.Frame == end.Frame) //最后一条数据
+        //            {
+        //                if (info.Alarm != "无")
+        //                {
+        //                    //先判断是否有没有记录的报警信息
+        //                    var alarmMsg = Convert.ToString(info.Alarm).Split(",").Select(x => x.Split(",")).SelectMany(x => x).ToList();
+        //                    var msgList = alarmList.Select(x => x.Msg);
+
+        //                    //将未记录的报警信息增加到缓存中
+        //                    alarmList.AddRange(alarmMsg.Where(msg => !msgList.Contains(msg)).Select(msg => new EquipmentAlarm() { Time = info.Frame, Msg = msg }));
+        //                }
+
+        //                if (!alarmList.Any()) continue;
+        //                runInfos.AddRange(alarmList.Select(alarm => new DevAlarmInfo()
+        //                {
+        //                    Frame = alarm.Time,
+        //                    Code = info.Code,
+        //                    Alarm = alarm.Msg,
+        //                    StartTime = alarm.Time,
+        //                    EndTime = endTime,
+        //                    Duration = Convert.ToInt64((endTime - alarm.Time).TotalMilliseconds),
+        //                    Type = DevType.RGV
+        //                }));
+        //            }
+        //            else
+        //            {
+        //                if (info.Alarm == "无" && alarmList.Any()) //代表报警全部清空了
+        //                {
+        //                    runInfos.AddRange(alarmList.Select(alarm => new DevAlarmInfo()
+        //                    {
+        //                        Frame = alarm.Time,
+        //                        Code = info.Code,
+        //                        Alarm = alarm.Msg,
+        //                        StartTime = alarm.Time,
+        //                        EndTime = info.Frame,
+        //                        Duration = Convert.ToInt64((info.Frame - alarm.Time).TotalMilliseconds),
+        //                        Type = DevType.RGV
+        //                    }));
+
+        //                    alarmList = new List<EquipmentAlarm>(); //清空现有报警信息
+        //                }
+        //                else if (info.Alarm != "无")
+        //                {
+        //                    var alarmMsg = Convert.ToString(info.Alarm).Split(",").Select(x => x.Split(",")).SelectMany(x => x).ToList();
+        //                    var msgList = alarmList.Select(x => x.Msg);
+
+        //                    //将未记录的报警信息增加到缓存中
+        //                    alarmList.AddRange(alarmMsg.Where(msg => !msgList.Contains(msg)).Select(msg => new EquipmentAlarm() { Time = info.Frame, Msg = msg }));
+        //                    var treatedAlarm = new List<string>();
+        //                    //将已记录,但当前信息中没有的报警结束
+        //                    foreach (var alarm in alarmList.Where(x => !alarmMsg.Contains(x.Msg)))
+        //                    {
+        //                        runInfos.Add(new DevAlarmInfo()
+        //                        {
+        //                            Frame = alarm.Time,
+        //                            Code = info.Code,
+        //                            Alarm = alarm.Msg,
+        //                            StartTime = alarm.Time,
+        //                            EndTime = info.Frame,
+        //                            Duration = Convert.ToInt64((info.Frame - alarm.Time).TotalMilliseconds),
+        //                            Type = DevType.RGV
+        //                        });
+        //                        treatedAlarm.Add(alarm.Msg);
+        //                    }
+
+        //                    alarmList = alarmList.Where(x => !treatedAlarm.Contains(x.Msg)).ToList();
+        //                }
+        //            }
+        //        }
+
+        //        if (runInfos.Any())
+        //        {
+        //            db.Insertable(runInfos).ExecuteCommand();
+        //        }
+        //        else
+        //        {
+        //            db.Insertable(new DevAlarmInfo()
+        //            {
+        //                Frame = endTime,
+        //                Code = infos.Key,
+        //                Alarm = "当前时段无有效数据",
+        //                StartTime = startTime,
+        //                EndTime = endTime,
+        //                Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+        //                Type = DevType.RGV
+        //            }).ExecuteCommand();
+        //        }
+        //    });
+        //}
+
+        #endregion 异常报警信息
+
+        #region 任务状态分析
+
+        /// <summary>
+        /// 机械臂
+        /// </summary>
+        /// <param name="db"></param>
+        public void ExRobotTaskSumRunInfo(SqlSugarScopeProvider db)
+        {
+            var startTime = DateTime.Now;
+            var endTime = DateTime.Now;
+            IEnumerable<IGrouping<string, WCS_Robot520>> robot520 = new List<IGrouping<string, WCS_Robot520>>();
+            var type = DevType.RobotTaskSum.ToString();
+            //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+            if (db.Queryable<DevRunInfo>().Any(x => x.Type == type))
+            {
+                //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+                var runInfo = db.Queryable<DevRunInfo>().Where(x => x.Type == type).OrderByDescending(x => x.Frame).First();
+                startTime = runInfo.Frame.AddDays(+1).Date;//后一天的初始时间
+                endTime = startTime.AddDays(+1).AddMilliseconds(-1);//
+            }
+            else
+            {
+                if (!db.Queryable<WCS_Robot520>().Any()) return;
+                startTime = db.Queryable<WCS_Robot520>().OrderBy(x => x.Frame).First().Frame.Date;
+                endTime = startTime.AddDays(+1).AddMilliseconds(-1);
+            }
+            if (endTime > DateTime.Now.AddHours(1)) return; //一次处理24小时的数据
+            if (db.Queryable<DevRunInfo>().Any(x => x.EndTime == endTime && x.Type == type)) return;
+
+            robot520 = db.Queryable<WCS_Robot520>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+            if (!robot520.Any())
+            {
+                db.Insertable(new DevRunInfo()
+                {
+                    Frame = endTime,
+                    Code = "当前时段无有效数据",
+                    RunMode = "当前时段无有效数据",
+                    RunStatus = "当前时段无有效数据",
+                    StartTime = startTime,
+                    EndTime = endTime,
+                    Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+                    Type = type
+                }).ExecuteCommand();
+            }
+
+            Parallel.ForEach(robot520, infos =>
+            {
+                //infos = infos.Where(x => x.TaskNumber1 != 0 || x.TaskNumber2 != 0).ToList();
+
+                // 获取第一条数据与最后一条数据
+                var start = infos.OrderBy(x => x.Frame).First();
+                var end = infos.OrderByDescending(x => x.Frame).First();
+                //通过是否有最后一条数据来确定当前数据是否有分析存储过
+                if (db.Queryable<DevRunInfo>().Any(x => x.Code == infos.Key && x.EndTime == end.Frame)) return;
+                //获取前一天最后的一条数据
+                var yesterEnd = db.Queryable<DevRunInfo>().Where(x => x.Code == infos.Key && x.Frame < endTime).OrderByDescending(x => x.Frame).First();
+                List<DevRunInfo> runInfos = new List<DevRunInfo>();
+                //foreach (var info in infos.OrderBy(x => x.Frame))
+                //{
+                //    if (info.Frame == start.Frame) //当天的第一条数据
+                //    {
+                //        if (yesterEnd == null) //如果没有前一天的最后一条数据,就有用当天的第一条数据状态做计算
+                //        {
+                //            runInfos.Add(new DevRunInfo()
+                //            {
+                //                Frame = info.Frame,
+                //                Code = info.Code,
+                //                RunMode = info.RobotMode.GetDescription(),
+                //                RunStatus = info.RunStatus.GetDescription(),
+                //                StartTime = startTime,
+                //                EndTime = info.Frame,
+                //                Duration = Convert.ToInt64((info.Frame - startTime).TotalMilliseconds),
+                //                Type = DevType.RobotTaskSum
+                //            });
+                //        }
+                //        else //如果有就用前一天的最后一条数据做计算
+                //        {
+                //            runInfos.Add(new DevRunInfo()
+                //            {
+                //                Frame = info.Frame,
+                //                Code = info.Code,
+                //                RunMode = yesterEnd.RunMode,
+                //                RunStatus = yesterEnd.RunStatus,
+                //                StartTime = startTime,
+                //                EndTime = info.Frame,
+                //                Duration = Convert.ToInt64((info.Frame - startTime).TotalMilliseconds),
+                //                Type = DevType.RobotTaskSum
+                //            });
+                //        }
+                //    }
+                //    else if (info.Frame == end.Frame) //当天的最后一条数据
+                //    {
+                //        runInfos.Add(new DevRunInfo()
+                //        {
+                //            Frame = info.Frame,
+                //            Code = info.Code,
+                //            RunMode = info.RobotMode.GetDescription(),
+                //            RunStatus = info.RunStatus.GetDescription(),
+                //            StartTime = info.Frame,
+                //            EndTime = endTime,
+                //            Duration = Convert.ToInt64((endTime - info.Frame).TotalMilliseconds),
+                //            Type = DevType.RobotTaskSum
+                //        });
+                //    }
+                //    else //中间数据
+                //    {
+                //        if (start.RobotMode == info.RobotMode && start.RunStatus == info.RunStatus) continue;
+                //        runInfos.Add(new DevRunInfo()
+                //        {
+                //            Frame = info.Frame,
+                //            Code = info.Code,
+                //            RunMode = start.RobotMode.GetDescription(),
+                //            RunStatus = start.RunStatus.GetDescription(),
+                //            StartTime = start.Frame,
+                //            EndTime = info.Frame,
+                //            Duration = Convert.ToInt64((info.Frame - start.Frame).TotalMilliseconds),
+                //            Type = DevType.RobotTaskSum
+                //        });
+                //        start = info;
+                //    }
+                //}
+
+                var sql = db.Insertable(runInfos).ToSqlString();
+                db.Ado.ExecuteCommand(GetString(sql));
+            });
+        }
+
+        /// <summary>
+        /// 机械臂
+        /// </summary>
+        /// <param name="db"></param>
+        public void ExSrmTaskSumRunInfo(SqlSugarScopeProvider db, SqlSugarScopeProvider plcEx)
+        {
+            var startTime = DateTime.Now;
+            var endTime = DateTime.Now;
+            IEnumerable<IGrouping<string, WCS_SRM520>> srm520 = new List<IGrouping<string, WCS_SRM520>>();
+            var type = DevType.SrmTaskSum.ToString();
+            //查看rgv类型是否存在运行信息,用于系统被第一次启用时进行初始化
+            if (plcEx.Queryable<DevRunInfo>().Any(x => x.Type == type))
+            {
+                //已有信息,查找最后一条信息用于获取开始时间,最后一条通常是前一天的最后条数据
+                var runInfo = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type).OrderByDescending(x => x.Frame).First();
+                startTime = runInfo.Frame.AddHours(+1);//后一个小时
+                startTime = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); //后一个小时的开始时间
+                endTime = startTime.AddHours(+1).AddMilliseconds(-1);//
+            }
+            else
+            {
+                if (db.Queryable<WCS_SRM520>().Count() <= 0) return;
+                startTime = db.Queryable<WCS_SRM520>().OrderBy(x => x.Frame).First().Frame;
+                startTime = new DateTime(startTime.Year, startTime.Month, startTime.Day, startTime.Hour, 0, 0); //后一个小时的开始时间
+                endTime = startTime.AddHours(+1).AddMilliseconds(-1);
+            }
+            //取上一小时的第一秒
+            var now = DateTime.Now;
+            now = new DateTime(now.Year, now.Month, now.Day, now.Hour, 0, 0);
+            var nowEndTime = new DateTime(endTime.Year, endTime.Month, endTime.Day, endTime.Hour, 0, 0);
+            if (endTime > now) return; //当前时间之前
+            if (nowEndTime == now) return; //是当前这一个小时
+            now = new DateTime(now.Year, now.Month, now.Day, now.Hour, 01, 0);
+            if (DateTime.Now < now) return;// 每个小时的前十分钟不进行数据分析处理,仅在每小时的10-59之内进行处理
+
+            if (plcEx.Queryable<DevRunInfo>().Any(x => x.EndTime == endTime && x.Type == type)) return;
+
+            srm520 = db.Queryable<WCS_SRM520>().Where(x => x.Frame > startTime && x.Frame <= endTime).ToList().GroupBy(x => x.Code).ToList();
+
+            if (!srm520.Any())
+            {
+                plcEx.Insertable(new DevRunInfo()
+                {
+                    Frame = endTime,
+                    Code = "当前时段无有效数据",
+                    RunMode = "当前时段无有效数据",
+                    RunStatus = "当前时段无有效数据",
+                    StartTime = startTime,
+                    EndTime = endTime,
+                    Duration = Convert.ToInt64((endTime - startTime).TotalMilliseconds),
+                    Type = type
+                }).ExecuteCommand();
+            }
+
+            foreach (var infos in srm520)
+            {
+                var infoList = infos.Where(x => x.TaskID1 != 0 || x.TaskID2 != 0).ToList().DistinctItemBy(x => x.VoucherNo);
+
+                List<DevRunInfo> runInfos = new List<DevRunInfo>();
+                foreach (var info in infoList.OrderBy(x => x.Frame))
+                {
+                    var taskSum = 0;
+                    if (info.TaskID1 != 0) taskSum += 1;
+                    if (info.TaskID2 != 0) taskSum += 1;
+                    var taskType = info.TaskType1 == 0 ? info.TaskType2 : info.TaskType1;
+                    runInfos.Add(new DevRunInfo()
+                    {
+                        Frame = info.Frame,
+                        Code = info.Code,
+                        RunMode = taskSum.ToString(),
+                        RunStatus = $"{info.TaskID1}--{info.TaskID2}--{taskType}",
+                        Duration = 0,
+                        Type = type
+                    });
+                }
+
+                var sql = plcEx.Insertable(runInfos).ToSqlString();
+                plcEx.Ado.ExecuteCommand(GetString(sql));
+            }
+        }
+
+        #endregion 任务状态分析
+
+        public string GetString(string value)
+        {
+            return value.Replace(",N'", ",'")
+                .Replace("\0", "")
+                .Replace("(N'", "('") + "\r";
+        }
+    }
+
+    /// <summary>
+    ///  设备报警
+    /// </summary>
+    public class EquipmentAlarm
+    {
+        /// <summary>
+        ///  内容
+        /// </summary>
+        public string Msg { get; set; }
+
+        /// <summary>
+        ///  时间
+        /// </summary>
+        public DateTime Time { get; set; }
+    }
+
+    /// <summary>
+    ///  设备状态信息
+    /// </summary>
+    public class EquipmentStatus
+    {
+        /// <summary>
+        ///  设备号
+        /// </summary>
+        public string Code { get; set; }
+
+        /// <summary>
+        ///  内容
+        /// </summary>
+        public string con { get; set; }
+
+        /// <summary>
+        ///  内容
+        /// </summary>
+        public int Status { get; set; }
+
+        /// <summary>
+        ///  时间
+        /// </summary>
+        public DateTime Time { get; set; }
+    }
+
+    /// <summary>
+    ///
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class Quest<T>
+    {
+        public T Data { get; set; }
+    }
+
+    public class KeyValue
+    {
+        public string key { get; set; }
+
+        public string value { get; set; }
+    }
+}

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

@@ -0,0 +1,44 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net7.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591</NoWarn>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <NoWarn>1701;1702;8602;8616;8618;8625;8600;8603;8714;1591</NoWarn>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <None Remove="Systems\puiaxxwa.5zv~" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <PackageReference Include="EPPlus" Version="7.2.1" />
+    <PackageReference Include="MessagePack" Version="2.5.108" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\..\ZTGT-FJZX\ServiceCenter\ServiceCenter.csproj" />
+    <ProjectReference Include="..\..\ZTGT-SX\业务工程\时效库\WCS.Entity.Protocol\WCS.Entity.Protocol.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Reference Include="wms.dto">
+      <HintPath>..\DLC\wms.dto.dll</HintPath>
+    </Reference>
+    <Reference Include="wms.service">
+      <HintPath>..\DLC\wms.service.dll</HintPath>
+    </Reference>
+    <Reference Include="wms.sqlsugar">
+      <HintPath>..\DLC\wms.sqlsugar.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+
+</Project>

+ 716 - 0
WCS.WorkEngineering/WebApi/Controllers/WcsController.cs

@@ -0,0 +1,716 @@
+using Microsoft.AspNetCore.Mvc;
+using OfficeOpenXml;
+using ServiceCenter;
+using ServiceCenter.Extensions;
+using ServiceCenter.SqlSugars;
+using SqlSugar;
+using System.Net.NetworkInformation;
+using System.Text;
+using WCS.Core;
+using WCS.Entity.Protocol.HUB;
+using WCS.Entity.Protocol.SRM;
+using WCS.WorkEngineering.WebApi.Models.WCS.Request;
+using WCS.WorkEngineering.WebApi.Models.WCS.Response;
+
+//using Microsoft.OpenApi.Writers;
+
+// ReSharper disable PossibleLossOfFraction
+
+namespace WCS.WorkEngineering.WebApi.Controllers
+{
+    /// <summary>
+    ///  设备Ip通讯检测结构
+    /// </summary>
+    public class DeviceIpTestResults
+    {
+        public string Ip { get; set; }
+
+        public bool Result { get; set; }
+    }
+
+    /// <summary>
+    /// WCS相关接口控制器
+    /// </summary>
+    [ApiController]
+    [Route("api/[controller]/[action]")]
+    public class WcsController : ControllerBase
+    {
+        /// <summary>
+        ///  获取设备配置信息接口
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet]
+        public List<Device> GetDeviceList()
+        {
+            return Device.All.ToList();
+        }
+
+        /// <summary>
+        ///  获取设备运行状态
+        /// </summary>
+        /// <param name="req"></param>
+        /// <returns></returns>
+        [HttpPost]
+        public PagedInfoResponse<RunInfoViewMode> GetDevRunInfo(DevRunInfoViewMode req)
+        {
+            var runInfos = new List<RunInfoViewMode>();
+            var db = new SqlSugarHelper().Default;
+            var plcEx = new SqlSugarHelper().PLCEX;
+            var type = "";
+            List<List<DateTime>> dateList = new List<List<DateTime>>();
+            switch (req.Scope)
+            {
+                case ScopeType.Day:
+                    dateList = GetDayRanges(req.StateTime, req.EndTime);
+                    break;
+
+                case ScopeType.Hour:
+                    dateList = GetHourRanges(req.StateTime, req.EndTime);
+                    break;
+
+                default:
+                    throw new Exception("不支持此时间范围统计,目前仅支持天与小时");
+            }
+
+            switch (req.Type)
+            {
+                case DevType.SRM: //堆垛机
+
+                    foreach (var date in dateList)
+                    {
+                        var minTime = date.MinBy(x => x);
+                        var maxTime = date.MaxBy(x => x);
+                        type = DevType.SRM.ToString();
+                        //获取时段内设备的运行状态
+                        var runInfoList = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type && x.Frame >= minTime && x.Frame < maxTime).ToList().GroupBy(x => x.Code);
+                        foreach (var runInfo in runInfoList)
+                        {
+                            var info = new RunInfoViewMode()
+                            {
+                                Code = runInfo.Key,
+                                Free = runInfo.Where(x =>
+                                    x.RunMode == SrmMode.自动.GetDescription() &&
+                                    x.RunStatus == SrmRunStatus.空闲.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+                                Alarm = runInfo.Where(x => x.RunMode == SrmMode.维修.GetDescription())
+                                    .Sum(x => x.Duration) / 1000 / 60,
+                                Manual = runInfo.Where(x => x.RunMode == SrmMode.手动.GetDescription() || x.RunMode == SrmMode.半自动.GetDescription())
+                                    .Sum(x => x.Duration) / 1000 / 60,
+                                Automatic = runInfo.Where(x => x.RunMode == SrmMode.自动.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+                                Working = runInfo.Where(x => x.RunMode == SrmMode.自动.GetDescription()
+                                                             && x.RunStatus != SrmRunStatus.空闲.GetDescription()
+                                                             && x.RunStatus != SrmRunStatus.保持维修.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+                                TotalTime = runInfo.Sum(x => x.Duration) / 1000 / 60
+                            };
+
+                            #region 开始分析设备动作,不计算停止时间
+
+                            var devActions = new List<DevActionViewMode>();
+                            double totalTime = 0; //总耗时
+                            double totalCount = 0; //总次数
+                            //入库
+                            var infos = runInfo.Where(x => x.RunMode == SrmMode.自动.GetDescription()).OrderBy(x => x.Frame).ToList();
+                            totalTime = infos.Where(x => x.RunStatus != SrmRunStatus.空闲.GetDescription() && x.RunStatus != SrmRunStatus.保持维修.GetDescription()).Sum(x => x.Duration);
+
+                            #endregion 开始分析设备动作,不计算停止时间
+
+                            #region 计算任务的工位使用数量
+
+                            var stationCounts = new List<StationCount>();
+                            type = DevType.SrmTaskSum.ToString();
+                            var taskSum = plcEx.Queryable<DevRunInfo>().Where(x => x.Code == runInfo.Key && x.Type == type && x.Frame >= minTime && x.Frame < maxTime)
+                                .ToList().Select(x =>
+                                {
+                                    var item = x;
+                                    var taskInfo = item.RunStatus.Split("--");
+                                    if (taskInfo.Length < 3) return null;
+                                    item.Duration = long.Parse(taskInfo[2]);
+                                    return item;
+                                }).Where(x => x != null).ToList();
+
+                            //入库
+                            stationCounts.Add(new StationCount()
+                            {
+                                Type = DevActionType.In.GetDescription(),
+                                StationNum = taskSum.Count(x => x is { RunMode: "1" }),
+                                StationIndex = 1
+                            });
+                            stationCounts.Add(new StationCount()
+                            {
+                                Type = DevActionType.In.GetDescription(),
+                                StationNum = taskSum.Count(x => x is { RunMode: "2" }),
+                                StationIndex = 2
+                            });
+                            info.StationCount = stationCounts;
+                            totalCount = taskSum.Count;
+                            devActions.Add(new DevActionViewMode()
+                            {
+                                Type = DevActionType.In.GetDescription(),
+                                ActionAmount = totalCount,
+                                ActionTime = totalTime == 0 ? 0 : Math.Round((totalTime / totalCount) / 1000, 2),
+                            });
+                            info.DevAction = devActions;
+
+                            #endregion 计算任务的工位使用数量
+
+                            info.StartTime = minTime;
+                            info.EndTime = maxTime;
+                            runInfos.Add(info);
+                        }
+                    }
+
+                    break;
+
+                case DevType.Truss:
+                    //type = DevType.Truss.ToString();
+                    //foreach (var date in dateList)
+                    //{
+                    //    HandleTrussRunInfo(type, db, plcEx, runInfos, date.MinBy(x => x), date.MaxBy(x => x));
+                    //}
+                    break;
+            }
+
+            var res = new PagedInfoResponse<RunInfoViewMode>();
+            res.Result = runInfos.OrderBy(x => x.StartTime).ThenBy(x => x.Code).ToList();
+            res.TotalNum = runInfos.Count;
+
+            // If you are a commercial business and have
+            // purchased commercial licenses use the static property
+            // LicenseContext of the ExcelPackage class:
+            ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.Commercial;
+
+            // If you use EPPlus in a noncommercial context
+            // according to the Polyform Noncommercial license:
+            ExcelPackage.LicenseContext = OfficeOpenXml.LicenseContext.NonCommercial;
+            // 创建一个新的 Excel 文件
+            FileInfo excelFile = new FileInfo("D:\\output.xlsx");
+            using (ExcelPackage package = new ExcelPackage(excelFile))
+            {
+                ExcelWorksheet worksheet = package.Workbook.Worksheets.Add("Sheet1");
+
+                // 写入表头
+                int col = 1;
+                var title = new List<string>() { "时间", "设备", "入库", "空闲", "手动", "自动", "报警", "工作时间", "总时间", "次数", "耗时", "稼动率", "单抓", "双爪" };
+                foreach (var prop in title)
+                {
+                    worksheet.Cells[1, col].Value = prop;
+                    col++;
+                }
+
+                // 写入数据
+                int row = 2;
+                var dateTimeList = res.Result.GroupBy(x => x.StartTime);
+                foreach (var time in dateTimeList)
+                {
+                    var codeLost = time.OrderBy(x => x.Code);
+
+                    foreach (var code in codeLost)
+                    {
+                        worksheet.Cells[row, 1].Value = code.StartTime;
+                        worksheet.Cells[row, 2].Value = code.Code;
+                        worksheet.Cells[row, 3].Value = code.StationCount?.Select(x => x.StationIndex * x.StationNum).Sum(x => x);
+                        worksheet.Cells[row, 4].Value = code.Free;
+                        worksheet.Cells[row, 5].Value = code.Manual;
+                        worksheet.Cells[row, 6].Value = code.Automatic;
+                        worksheet.Cells[row, 7].Value = code.Alarm;
+                        worksheet.Cells[row, 8].Value = code.Working;
+                        worksheet.Cells[row, 9].Value = code.TotalTime;
+                        worksheet.Cells[row, 10].Value = code.DevAction.FirstOrDefault(x => x.Type == "入库")?.ActionAmount;
+                        worksheet.Cells[row, 11].Value = code.DevAction.FirstOrDefault(x => x.Type == "入库")?.ActionTime;
+                        worksheet.Cells[row, 12].Value = code.Working / code.TotalTime;
+                        worksheet.Cells[row, 13].Value = code.StationCount.First(x => x is { Type: "入库", StationIndex: 1 }).StationNum;
+                        worksheet.Cells[row, 14].Value = code.StationCount.First(x => x is { Type: "入库", StationIndex: 2 }).StationNum;
+                        //worksheet.Cells[row, 16].Value = enterDepot2;
+                        //worksheet.Cells[row, 17].Value = outDepot1;
+                        //worksheet.Cells[row, 18].Value = outDepot2;
+                        row++;
+                    }
+
+                    //foreach (var prop in item.GetType().GetProperties())
+                    //{
+                    //    worksheet.Cells[row, col].Value = prop.GetValue(item);
+                    //    col++;
+                    //}
+                }
+
+                // 保存 Excel 文件
+                package.Save();
+            }
+
+            return res;
+
+            //var runInfos = new List<RunInfoViewMode>();
+            //var db = new SqlSugarHelper().Default;
+            //var plcEx = new SqlSugarHelper().PLCEX;
+            //var type = "";
+            //switch (req.Type)
+            //{
+            //    case DevType.SRM:
+            //        type = DevType.SRM.ToString();
+            //        //获取时段内设备的运行状态
+            //        var runInfoList = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type && x.Frame >= req.StateTime && x.Frame < req.EndTime).ToList().GroupBy(x => x.Code);
+            //        foreach (var runInfo in runInfoList)
+            //        {
+            //            var info = new RunInfoViewMode()
+            //            {
+            //                Code = runInfo.Key,
+            //                //EnterDepot = db.Queryable<WCS_TaskOld>().With(SqlWith.NoLock)
+            //                //    .Where(x => x.Status == TaskStatus.Finish && x.Type == TaskType.EnterDepot &&
+            //                //                x.Device == runInfo.Key && x.EndTime >= req.StateTime &&
+            //                //                x.EndTime < req.EndTime)
+            //                //    .SplitTable(x => x.Take(2))
+            //                //    .Count(),
+            //                //OutDepot = db.Queryable<WCS_TaskOld>().With(SqlWith.NoLock)
+            //                //    .Where(x => x.Status == TaskStatus.Finish && x.Type == TaskType.OutDepot &&
+            //                //                x.Device == runInfo.Key && x.EndTime >= req.StateTime &&
+            //                //                x.EndTime < req.EndTime)
+            //                //    .SplitTable(x => x.Take(2))
+            //                //    .Count(),
+            //                //MoveDepot = db.Queryable<WCS_TaskOld>().With(SqlWith.NoLock)
+            //                //    .Where(x => x.Status == TaskStatus.Finish && x.Type == TaskType.TransferDepot &&
+            //                //                x.Device == runInfo.Key && x.EndTime >= req.StateTime &&
+            //                //                x.EndTime < req.EndTime)
+            //                //    .SplitTable(x => x.Take(2))
+            //                //    .Count(),
+            //                Free = runInfo.Where(x =>
+            //                    x.RunMode == SrmMode.自动.GetDescription() &&
+            //                    x.RunStatus == SrmRunStatus.空闲.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //                //Alarm = runInfo.Where(x => x.RunMode == SrmMode.维修.GetDescription())
+            //                //    .Sum(x => x.Duration) / 1000 / 60,
+            //                Manual = runInfo.Where(x => x.RunMode == SrmMode.手动.GetDescription() || x.RunMode == SrmMode.半自动.GetDescription())
+            //                    .Sum(x => x.Duration) / 1000 / 60,
+            //                Automatic = runInfo.Where(x => x.RunMode == SrmMode.自动.GetDescription()
+            //                                               && x.RunStatus != SrmRunStatus.空闲.GetDescription())
+            //                    .Sum(x => x.Duration) / 1000 / 60,
+            //            };
+
+            //            #region 开始分析设备动作,不计算停止时间
+
+            //            var devActions = new List<DevActionViewMode>();
+            //            double totalTime = 0; //总耗时
+            //            double totalCount = 0; //总次数
+            //            //入库
+            //            var infos = runInfo.Where(x => x.RunMode == SrmMode.自动.GetDescription() && x.RunStatus != SrmRunStatus.空闲.GetDescription()).OrderBy(x => x.Frame).ToList();
+            //            totalTime = infos.Sum(x => x.Duration);
+            //            totalCount = infos.Count(x => x.RunStatus == SrmRunStatus.放货收叉.GetDescription());
+            //            devActions.Add(new DevActionViewMode()
+            //            {
+            //                //Type = DevActionType.In.GetDescription(),
+            //                ActionAmount = totalCount,
+            //                ActionTime = totalTime == 0 ? 0 : Math.Round(totalTime / totalCount, 2),
+            //            });
+            //            ////出库
+            //            //infos = runInfo.Where(x => x.RunMode == RobotMode.Automatic.GetDescription()).ToList();
+            //            //totalTime = infos.Where(x => x.RunStatus == RobotRunStatus.OutBoundCrawling.GetDescription() || x.RunStatus == RobotRunStatus.OutStorage.GetDescription()).Sum(x => x.Duration);
+            //            //totalCount = infos.Count(x => x.RunStatus == RobotRunStatus.OutBoundCrawling.GetDescription());
+            //            //devActions.Add(new DevActionViewMode()
+            //            //{
+            //            //    Type = DevActionType.Out.GetDescription(),
+            //            //    ActionAmount = totalCount,
+            //            //    ActionTime = totalTime == 0 ? 0 : Math.Round(totalTime / totalCount, 2),
+            //            //});
+            //            info.DevAction = devActions;
+
+            //            #endregion 开始分析设备动作,不计算停止时间
+
+            //            //#region 计算任务的工位使用数量
+
+            //            //var stationCounts = new List<StationCount>();
+            //            //type = DevType.RobotTaskSum.ToString();
+            //            //var taskSum = plcEx.Queryable<DevRunInfo>().Where(x => x.Code == runInfo.Key && x.Type == type && x.Frame >= req.StateTime && x.Frame < req.EndTime)
+            //            //    .ToList().Select(x =>
+            //            //    {
+            //            //        var item = x;
+            //            //        item.Duration = long.Parse(item.RunStatus.Split("--")[2]);
+            //            //        return item;
+            //            //    }).ToList();
+
+            //            ////入库
+            //            //stationCounts.Add(new StationCount()
+            //            //{
+            //            //    Type = DevActionType.In.GetDescription(),
+            //            //    StationNum = taskSum.Count(x => x is { Duration: 3, RunMode: "1" }),
+            //            //    StationIndex = 1
+            //            //});
+            //            //stationCounts.Add(new StationCount()
+            //            //{
+            //            //    Type = DevActionType.In.GetDescription(),
+            //            //    StationNum = taskSum.Count(x => x is { Duration: 3, RunMode: "2" }),
+            //            //    StationIndex = 2
+            //            //});
+            //            ////出库
+            //            //stationCounts.Add(new StationCount()
+            //            //{
+            //            //    Type = DevActionType.Out.GetDescription(),
+            //            //    StationNum = taskSum.Count(x => x is { Duration: 4, RunMode: "1" }),
+            //            //    StationIndex = 1
+            //            //});
+            //            //stationCounts.Add(new StationCount()
+            //            //{
+            //            //    Type = DevActionType.Out.GetDescription(),
+            //            //    StationNum = taskSum.Count(x => x is { Duration: 4, RunMode: "2" }),
+            //            //    StationIndex = 2
+            //            //});
+            //            //info.StationCount = stationCounts;
+
+            //            //#endregion 计算任务的工位使用数量
+
+            //            runInfos.Add(info);
+            //        }
+            //        break;
+
+            //    case DevType.Robot: //机械臂
+            //        type = DevType.Robot.ToString();
+            //        //获取时段内设备的运行状态
+            //        var robotInfoList = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type && x.Frame >= req.StateTime && x.Frame < req.EndTime).ToList().GroupBy(x => x.Code);
+            //        foreach (var runInfo in robotInfoList)
+            //        {
+            //            var curPoints = new List<string>();
+            //            if (runInfo.Key == "Robot1")
+            //            {
+            //                curPoints = new List<string>() { "8090", "8092" };
+            //            }
+            //            else if (runInfo.Key == "Robot2")
+            //            {
+            //                curPoints = new List<string>() { "8096", "8098" };
+            //            }
+
+            //            var info = new RunInfoViewMode()
+            //            {
+            //                Code = runInfo.Key,
+            //                EnterDepot = db.Queryable<WCS_TaskDtl>().With(SqlWith.NoLock)
+            //                    .Where(x => x.Desc == "码垛抓取完成" && x.AddTime >= req.StateTime && x.AddTime < req.EndTime && curPoints.Contains(x.CurPoint))
+            //                    .SplitTable(x => x.Take(2))
+            //                    .Count(),
+            //                //OutDepot = db.Queryable<WCS_TaskOld>().With(SqlWith.NoLock)
+            //                //    .Where(x => x.Status == TaskStatus.Finish && x.Type == TaskType.OutDepot &&
+            //                //                x.Device == runInfo.Key && x.EndTime >= req.StateTime &&
+            //                //                x.EndTime < req.EndTime)
+            //                //    .SplitTable(x => x.Take(2))
+            //                //    .Count(),
+            //                Free = runInfo.Where(x =>
+            //                    x.RunMode == RobotMode.自动.GetDescription() &&
+            //                    x.RunStatus == RobotRunStatus.空闲.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //                Alarm = runInfo.Where(x => x.RunMode == RobotMode.故障.GetDescription())
+            //                    .Sum(x => x.Duration) / 1000 / 60,
+            //                Manual = runInfo.Where(x => x.RunMode == RobotMode.手动.GetDescription())
+            //                    .Sum(x => x.Duration) / 1000 / 60,
+            //                Automatic = runInfo.Where(x => x.RunMode == RobotMode.自动.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //                Working = runInfo.Where(x => x.RunMode == RobotMode.自动.GetDescription() && (x.RunStatus == RobotRunStatus.抓取中.GetDescription() || x.RunStatus == RobotRunStatus.放置中.GetDescription()))
+            //                    .Sum(x => x.Duration) / 1000 / 60,
+            //                TotalTime = runInfo.Sum(x => x.Duration) / 1000 / 60
+            //            };
+
+            //            #region 开始分析设备动作,不计算停止时间
+
+            //            var devActions = new List<DevActionViewMode>();
+            //            double totalTime = 0; //总耗时
+            //            double totalCount = 0; //总次数
+            //            //入库
+            //            var infos = runInfo.Where(x => x.RunMode == RobotMode.自动.GetDescription()).OrderBy(x => x.Frame).ToList();
+            //            totalTime = infos.Where(x => x.RunStatus == RobotRunStatus.抓取中.GetDescription() || x.RunStatus == RobotRunStatus.放置中.GetDescription()).Sum(x => x.Duration);
+            //            totalCount = infos.Count(x => x.RunStatus == RobotRunStatus.抓取中.GetDescription());
+            //            devActions.Add(new DevActionViewMode()
+            //            {
+            //                Type = DevActionType.In.GetDescription(),
+            //                ActionAmount = totalCount,
+            //                ActionTime = totalTime == 0 ? 0 : Math.Round(totalTime / totalCount, 2),
+            //            });
+            //            info.DevAction = devActions;
+
+            //            #endregion 开始分析设备动作,不计算停止时间
+
+            //            runInfos.Add(info);
+            //        }
+
+            //        break;
+
+            //    case DevType.Truss:
+            //        //type = DevType.Truss.ToString();
+            //        ////获取时段内设备的运行状态
+            //        //var trussInfoList = plcEx.Queryable<DevRunInfo>().Where(x => x.Type == type && x.Frame >= req.StateTime && x.Frame < req.EndTime).ToList().GroupBy(x => x.Code);
+            //        //foreach (var runInfo in trussInfoList)
+            //        //{
+            //        //    var info = new RunInfo()
+            //        //    {
+            //        //        Code = runInfo.Key,
+            //        //        EnterDepot = db.Queryable<WCS_TaskOld>().With(SqlWith.NoLock)
+            //        //            .Where(x => x.Status == TaskStatus.Finish && x.Type == TaskType.SetPlate &&
+            //        //                        x.Device == runInfo.Key && x.EndTime >= req.StateTime &&
+            //        //                        x.EndTime < req.EndTime)
+            //        //            .SplitTable(x => x.Take(2))
+            //        //            .Count(),
+            //        //        Free = runInfo.Where(x => x.RunMode == TrussStatus.Idle.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //        //        Alarm = runInfo.Where(x => x.RunMode == TrussStatus.Alarm.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //        //        Manual = runInfo.Where(x => x.RunMode == TrussStatus.Manual.GetDescription() || x.RunMode == TrussStatus.KeepInRepair.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //        //        Automatic = runInfo.Where(x => x.RunMode == TrussStatus.Execute.GetDescription()).Sum(x => x.Duration) / 1000 / 60,
+            //        //    };
+
+            //        //    #region 开始分析设备动作,不计算停止时间
+
+            //        //    var devActions = new List<DevActionViewMode>();
+            //        //    double totalTime = 0; //总耗时
+            //        //    double totalCount = 0; //总次数
+            //        //    //入库
+            //        //    var infos = runInfo.Where(x => x.RunMode == TrussStatus.Execute.GetDescription()).ToList();
+            //        //    totalTime = infos.Where(x => x.RunMode == TrussStatus.Execute.GetDescription()).Sum(x => x.Duration);
+            //        //    totalCount = infos.Count(x => x.RunMode == TrussStatus.Execute.GetDescription());
+            //        //    devActions.Add(new DevActionViewMode()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        ActionAmount = totalCount,
+            //        //        ActionTime = totalTime == 0 ? 0 : Math.Round(totalTime / totalCount, 2),
+            //        //    });
+
+            //        //    info.DevAction = devActions;
+
+            //        //    #endregion 开始分析设备动作,不计算停止时间
+
+            //        //    #region 计算任务的工位使用数量
+
+            //        //    var stationCounts = new List<StationCount>();
+            //        //    type = DevType.TrussTaskSum.ToString();
+            //        //    var taskSum = plcEx.Queryable<DevRunInfo>().Where(x => x.Code == runInfo.Key && x.Type == type && x.Frame >= req.StateTime && x.Frame < req.EndTime)
+            //        //        .ToList();
+
+            //        //    //入库
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "1" }),
+            //        //        StationIndex = 1
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "2" }),
+            //        //        StationIndex = 2
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "3" }),
+            //        //        StationIndex = 3
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "4" }),
+            //        //        StationIndex = 4
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "5" }),
+            //        //        StationIndex = 5
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "6" }),
+            //        //        StationIndex = 6
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "7" }),
+            //        //        StationIndex = 7
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "8" }),
+            //        //        StationIndex = 8
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "9" }),
+            //        //        StationIndex = 9
+            //        //    });
+            //        //    stationCounts.Add(new StationCount()
+            //        //    {
+            //        //        Type = DevActionType.In.GetDescription(),
+            //        //        StationNum = taskSum.Count(x => x is { RunMode: "10" }),
+            //        //        StationIndex = 10
+            //        //    });
+
+            //        //    info.StationCount = stationCounts;
+
+            //        //    #endregion 计算任务的工位使用数量
+
+            //        //    runInfos.Add(info);
+            //        //}
+            //        break;
+            //}
+            //var res = new PagedInfoResponse<RunInfoViewMode>();
+            //res.Result = runInfos;
+            //res.TotalNum = runInfos.Count;
+            //return res;
+        }
+
+        #region 设备IP相关
+
+        /// <summary>
+        /// 检查Ip是否正常联通
+        /// </summary>
+        /// <param name="strIpOrDName">输入参数,表示IP地址或域名</param>
+        /// <returns></returns>
+        [HttpGet]
+        public static bool PingIpOrDomainName(string strIpOrDName)
+        {
+            try
+
+            {
+                var objPingSender = new Ping();
+
+                var objPinOptions = new PingOptions
+                {
+                    DontFragment = true
+                };
+
+                const string data = "";
+
+                var buffer = Encoding.UTF8.GetBytes(data);
+
+                const int intTimeout = 120;
+
+                var objPinReply = objPingSender.Send(strIpOrDName, intTimeout, buffer, objPinOptions);
+
+                var strInfo = objPinReply.Status.ToString();
+
+                return strInfo == "Success";
+            }
+            catch (Exception)
+
+            {
+                return false;
+            }
+        }
+
+        /// <summary>
+        ///  获取设备IP检测结果
+        /// </summary>
+        /// <returns>设备IP检测结果</returns>
+        [HttpGet]
+        public List<DeviceIpTestResults> DeviceIpTest()
+        {
+            if (!ServiceHub.DeviceIPList.Any()) throw new Exception("未配置任何Ip");
+            var deviceIpTestResults = new List<DeviceIpTestResults>();
+            ServiceHub.DeviceIPList.ForEach(ip =>
+            {
+                deviceIpTestResults.Add(new DeviceIpTestResults
+                {
+                    Ip = ip,
+                    Result = PingIpOrDomainName(ip)
+                });
+            });
+
+            return deviceIpTestResults;
+        }
+
+        /// <summary>
+        ///  获取设备Ip集合
+        /// </summary>
+        /// <returns>设备Ip集合</returns>
+        [HttpGet]
+        public List<string> GetDeviceIpList()
+        {
+            if (!ServiceHub.DeviceIPList.Any()) throw new Exception("未配置任何Ip");
+            return ServiceHub.DeviceIPList;
+        }
+
+        #endregion 设备IP相关
+
+        /// <summary>
+        ///  获取小时范围
+        /// </summary>
+        /// <param name="startTime">开始时间</param>
+        /// <param name="endTime">结束时间</param>
+        /// <returns></returns>
+        private List<List<DateTime>> GetHourRanges(DateTime startTime, DateTime endTime)
+        {
+            List<List<DateTime>> hourRanges = new List<List<DateTime>>(); // 存放每个小时的开始时间和结束时间对
+
+            // 循环遍历每个小时
+            DateTime currentHour = startTime;
+            while (currentHour <= endTime)
+            {
+                DateTime hourStart = currentHour; // 当前小时的开始时间
+                DateTime hourEnd = currentHour.AddHours(1).AddSeconds(-1); // 当前小时的结束时间
+                hourRanges.Add(new[] { hourStart, hourEnd }.ToList()); // 将当前小时的开始时间和结束时间添加到列表中
+                currentHour = currentHour.AddHours(1); // 移动到下一个小时
+            }
+            return hourRanges;
+        }
+
+        /// <summary>
+        ///  获取小时范围
+        /// </summary>
+        /// <param name="startTime">开始时间</param>
+        /// <param name="endTime">结束时间</param>
+        /// <returns></returns>
+        private List<List<DateTime>> GetDayRanges(DateTime startTime, DateTime endTime)
+        {
+            List<List<DateTime>> dayRanges = new List<List<DateTime>>(); // 存放每个小时的开始时间和结束时间对
+
+            // 循环遍历每个小时
+            DateTime currentDate = startTime;
+            while (currentDate <= endTime)
+            {
+                DateTime dayStart = currentDate.Date; // 当天的开始时间(凌晨)
+                DateTime dayEnd = currentDate.Date.AddDays(1).AddSeconds(-1); // 当天的结束时间(当天的23:59:59)
+                dayRanges.Add(new[] { dayStart, dayEnd }.ToList()); // 将当天的开始时间和结束时间添加到列表中
+                currentDate = currentDate.AddDays(1); // 移动到下一天
+            }
+            return dayRanges;
+        }
+    }
+
+    /// <summary>
+    ///  带分页的返回体
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class PagedInfoResponse<T>
+    {
+        /// <summary>
+        /// 每页行数
+        /// </summary>
+        public int PageSize { get; set; } = 10;
+
+        /// <summary>
+        /// 当前页
+        /// </summary>
+        public int PageIndex { get; set; } = 1;
+
+        /// <summary>
+        /// 总记录数
+        /// </summary>
+        public int TotalNum { get; set; }
+
+        /// <summary>
+        /// 总页数
+        /// </summary>
+        public int TotalPage
+        {
+            get
+            {
+                if (TotalNum > 0)
+                {
+                    return TotalNum % PageSize == 0 ? TotalNum / PageSize : TotalNum / PageSize + 1;
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+            set { }
+        }
+
+        public List<T> Result { get; set; }
+        public Dictionary<string, object> Extra { get; set; } = new Dictionary<string, object>();
+
+        public PagedInfoResponse()
+        {
+        }
+    }
+}

+ 28 - 0
WCS.WorkEngineering/WebApi/Models/AGV/AgvResponseCode.cs

@@ -0,0 +1,28 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV
+{
+    /// <summary>
+    ///  AGV 返回结果code值枚举
+    /// </summary>
+    public class AgvResponseCode
+    {
+        /// <summary>
+        ///  成功
+        /// </summary>
+        public static string Success { get; set; } = "0";
+
+        /// <summary>
+        ///  失败
+        /// </summary>
+        public static string Fail { get; set; } = "1";
+
+        /// <summary>
+        ///  重复提交
+        /// </summary>
+        public static string Repeat { get; set; } = "6";
+
+        /// <summary>
+        ///  失败,未知异常
+        /// </summary>
+        public static string Error { get; set; } = "99";
+    }
+}

+ 10 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/AddWcsMoveTaskRequest.cs

@@ -0,0 +1,10 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    ///  获取移库任务请求参数
+    /// </summary>
+    public class AddWcsMoveTaskRequest
+    {
+        public int TaskNum { get; set; }
+    }
+}

+ 115 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/AgvCallbackRequest.cs

@@ -0,0 +1,115 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    public class AgvCallbackRequest
+    {
+        /// <summary>
+        /// 请求编号,每个请求都要一个唯一编号, 同一个请求重复提交, 使用同一编号
+        /// </summary>
+        public string reqCode { get; set; }
+
+        /// <summary>
+        ///  请求站台
+        /// </summary>
+        public string callCode { get; set; }
+
+        /// <summary>
+        /// 请求时间戳,格式: “yyyy-MM-dd HH:mm:ss”
+        /// </summary>
+        public string reqTime { get; set; }
+
+        /// <summary>
+        /// 地码 X 坐标(mm):任务完成时有值
+        /// </summary>
+        public decimal cooX { get; set; }
+
+        /// <summary>
+        /// 地码 Y 坐标(mm):任务完成时有值
+        /// </summary>
+        public decimal cooY { get; set; }
+
+        /// <summary>
+        /// 当前位置编号
+        /// </summary>
+        public string currentPositionCode { get; set; }
+
+        /// <summary>
+        /// 自定义字段
+        /// </summary>
+        public string data { get; set; }
+
+        /// <summary>
+        /// 地图编号
+        /// </summary>
+        public string mapCode { get; set; }
+
+        /// <summary>
+        /// 地码编号:任务完成时有值
+        /// </summary>
+        public string mapDataCode { get; set; }
+
+        /// <summary>
+        /// 仓位编号:叉车与CTU任务时有值
+        /// </summary>
+        public string stgBinCode { get; set; }
+
+        /// <summary>
+        /// 方法名,申请放货、请求取货、
+        /// </summary>
+        public string method { get; set; }
+
+        /// <summary>
+        /// 货架编号:背货架时有值
+        /// </summary>
+        public string podCode { get; set; }
+
+        /// <summary>
+        /// “180”,”0”,”90”,”-90” 分别对应地图的”左”,”右”,”上”,”下”:任务完成时有值
+        /// </summary>
+        public string podDir { get; set; }
+
+        /// <summary>
+        /// 物料编号
+        /// </summary>
+        public string materialLot { get; set; }
+
+        /// <summary>
+        /// AGV编号(同 agvCode )
+        /// </summary>
+        public string robotCode { get; set; }
+
+        /// <summary>
+        /// 当前任务单号
+        /// </summary>
+        public string taskCode { get; set; }
+
+        /// <summary>
+        /// 工作位
+        /// </summary>
+        public string wbCode { get; set; }
+
+        /// <summary>
+        /// 容器编号
+        /// </summary>
+        public string ctnrCode { get; set; }
+
+        /// <summary>
+        /// 容器类型
+        /// </summary>
+        public string ctnrTyp { get; set; }
+
+        /// <summary>
+        /// 巷道编号
+        /// </summary>
+        public string roadWayCode { get; set; }
+
+        /// <summary>
+        /// 巷道内顺序号巷道尾是0,到巷道头依次递增1
+        /// </summary>
+        public string seq { get; set; }
+
+        /// <summary>
+        /// 设备编号
+        /// </summary>
+        public string eqpCode { get; set; }
+    }
+}

+ 18 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/ApplyEmptySpoolRequest.cs

@@ -0,0 +1,18 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    ///  背负式AGV补空请求参数
+    /// </summary>
+    public class AgvFillEmptySpaceRequest
+    {
+        /// <summary>
+        ///  请求号
+        /// </summary>
+        public string Reqid { get; set; }
+
+        /// <summary>
+        /// 仓库编号
+        /// </summary>
+        public string WareCode { get; set; }
+    }
+}

+ 50 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/CancelTaskRequest.cs

@@ -0,0 +1,50 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    ///  取消任务
+    /// </summary>
+    public class CancelTaskRequest
+    {
+        /// <summary>
+        ///  请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+
+        /// <summary>
+        ///  求时间截 格式: “yyyy-MM-dd HH:mm:ss”。
+        /// </summary>
+        public string reqTime { get; set; }
+
+        /// <summary>
+        ///  客户端编号,如PDA,HCWMS等。
+        /// </summary>
+        public string clientCode { get; set; }
+
+        /// <summary>
+        ///  令牌号, 由调度系统颁发
+        /// </summary>
+        public string tokenCode { get; set; }
+
+        /// <summary>
+        ///  取消类型 0表示:取消后货架直接放地上
+        ///  1表示:AGV仍然背着货架, 根据回库区域执行回库指令, 只有潜伏车和CTU车支持。
+        ///  默认的取消模式为0
+        /// </summary>
+        public string forceCancel { get; set; }
+
+        /// <summary>
+        ///  forcecancel=1时有意义,回库区域编号, 如果为空,采用货架配置的库区。
+        /// </summary>
+        public string matterArea { get; set; }
+
+        /// <summary>
+        ///  取消该AGV正在执行的任务单
+        /// </summary>
+        public string agvCode { get; set; }
+
+        /// <summary>
+        /// 任务单编号, 取消该任务单
+        /// </summary>
+        public string taskCode { get; set; }
+    }
+}

+ 68 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/ContinueTaskRequest.cs

@@ -0,0 +1,68 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    /// Agv继续执行任务请求参数
+    /// </summary>
+    public class ContinueTaskRequest
+    {
+        /// <summary>
+        ///  请求编号,每个请求都要一个唯一编号, 同一个请求重复提交, 使用同一编号。
+        /// </summary>
+        public string reqCode { get; set; }
+
+        /// <summary>
+        ///  请求时间截 格式: “yyyy-MM-dd HH:mm:ss”。
+        /// </summary>
+        public string reqTime { get; set; }
+
+        /// <summary>
+        ///  客户端编号,如PDA,HCWMS等。
+        /// </summary>
+        public string clientCode { get; set; }
+
+        /// <summary>
+        ///  令牌号, 由调度系统颁发。
+        /// </summary>
+        public string tokenCode { get; set; }
+
+        /// <summary>
+        ///  工作位,与RCS-2000端配置的位置名称一致。
+        /// </summary>
+        public string wbCode { get; set; }
+
+        /// <summary>
+        ///  货架号,采用货架号触发的方式。
+        /// </summary>
+        public string podCode { get; set; }
+
+        /// <summary>
+        ///  AGV编号,采用 AGV编号触发的方式。
+        /// </summary>
+        public string agvCode { get; set; }
+
+        /// <summary>
+        ///  任务单号,选填, 不填系统自动生 成,必须为 64 位 UUID
+        /// </summary>
+        public string taskCode { get; set; }
+
+        /// <summary>
+        ///  下一个子任务的序列,指定第几个子任务开始执行,校验子任务执行是否正确。不填默认执行下一个子任务。
+        /// </summary>
+        public string taskSeq { get; set; }
+
+        /// <summary>
+        ///  下一个位置信息,在任务类型中配置外部设置时需要传入,否则不需要设置。待现场地图部署、配置完成后可获取
+        /// </summary>
+        public positionCodeClass nextPositionCode { get; set; }
+
+        /// <summary>
+        ///  根据type填写,呼叫站点/策略编号等
+        /// </summary>
+        public string positionCode { get; set; }
+
+        /// <summary>
+        /// 对象类型定义: 00:代表 nextPositionCode是一个位置 02:代表nextPositionCode是一个策略
+        /// </summary>
+        public string type { get; set; }
+    }
+}

+ 70 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/CopperLineAgvTaskStockInToIWmsRequest.cs

@@ -0,0 +1,70 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    public class CopperLineAgvTaskStockInToIWmsRequest
+    {
+        /// <summary>
+        /// 物料编码
+        /// </summary>
+        public string matCode { get; set; }
+
+        /// <summary>
+        /// 工作站编号
+        /// </summary>
+        public string workAreaCode { get; set; }
+
+        /// <summary>
+        /// 空/满工字轮标记(满工字轮:1空工字轮:0)
+        /// </summary>
+        public string outSpoolFull { get; set; }
+
+        /// <summary>
+        /// 取空补满标记
+        /// </summary>
+        public bool intoEmpty { get; set; }
+
+        /// <summary>
+        /// 湿拉出库标记
+        /// </summary>
+        public string wetOut { get; set; }
+
+        /// <summary>
+        /// 湿拉退料出库标记
+        /// </summary>
+        public string wetOutReturn { get; set; }
+
+        /// <summary>
+        /// 改手盘标记
+        /// </summary>
+        public string isSurplus { get; set; }
+
+        /// <summary>
+        /// 返工标记
+        /// </summary>
+        public string isRework { get; set; }
+
+        /// <summary>
+        /// 材料号
+        /// </summary>
+        public string matNo { get; set; }
+
+        /// <summary>
+        /// RFID号
+        /// </summary>
+        public string spoolNo { get; set; }
+
+        /// <summary>
+        /// 质量状态标记
+        /// </summary>
+        public string gradeCode { get; set; }
+
+        /// <summary>
+        /// 快投标记
+        /// </summary>
+        public string matFast { get; set; }
+
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public string taskNo { get; set; }
+    }
+}

+ 95 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/CopperLineAgvTaskStockOutToIWmsRequest.cs

@@ -0,0 +1,95 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    public class CopperLineAgvTaskStockOutToIWmsRequest
+    {
+        /// <summary>
+        /// 物料编码
+        /// </summary>
+        public string matCode { get; set; }
+
+        /// <summary>
+        /// 工作站编号(机台号、立库点位编号)
+        /// </summary>
+        public string wbCode { get; set; }
+
+        /// <summary>
+        /// 空/满工字轮入库标记(空工字轮:0,满工字轮:1,半工字轮:2)
+        /// </summary>
+        public string inSpoolFull { get; set; }
+
+        /// <summary>
+        /// 取满补空标记
+        /// </summary>
+        public bool getOutEmpty { get; set; }
+
+        /// <summary>
+        /// 湿拉上线标记
+        /// </summary>
+        public bool wetInto { get; set; }
+
+        /// <summary>
+        /// 湿拉特殊上线标记
+        /// </summary>
+        public bool wetIntoSpec { get; set; }
+
+        /// <summary>
+        /// 湿拉退料入库标记
+        /// </summary>
+        public bool wetIntoReturn { get; set; }
+
+        /// <summary>
+        /// 湿拉特殊上线点位号
+        /// </summary>
+        public string wetSpecWbCode { get; set; }
+
+        /// <summary>
+        /// 改手盘标记
+        /// </summary>
+        public string isSurplus { get; set; }
+
+        /// <summary>
+        /// 返工标记
+        /// </summary>
+        public string isRework { get; set; }
+
+        /// <summary>
+        /// 材料号
+        /// </summary>
+        public string matNo { get; set; }
+
+        /// <summary>
+        /// RFID号
+        /// </summary>
+        public string spoolNo { get; set; }
+
+        /// <summary>
+        /// 质量状态标记
+        /// </summary>
+        public string gradeCode { get; set; }
+
+        /// <summary>
+        /// 快投标记
+        /// </summary>
+        public string matFast { get; set; }
+
+        /// <summary>
+        /// 长度
+        /// </summary>
+        public string orderProcessLenOut { get; set; }
+
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public string taskNo { get; set; }
+
+        /// <summary>
+        /// 退料原因
+        /// </summary>
+        public string returnDesc { get; set; }
+
+        /// <summary>
+        /// 是否锁定
+        /// </summary>
+        public string lockFlag { get; set; }
+    }
+}

+ 200 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/GenAgvSchedulingTaskRequest.cs

@@ -0,0 +1,200 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    ///  生成任务单接口
+    /// </summary>
+    public class GenAgvSchedulingTaskRequest
+    {
+        /// <summary>
+        /// 请求编号,每个请求都要一个唯一编号, 同一个请求重复提交, 使用同一编号。
+        /// </summary>
+        public string reqCode { get; set; }
+
+        /// <summary>
+        /// 请求时间截 格式: “yyyy-MM-dd HH:mm:ss”
+        /// </summary>
+        public string reqTime { get; set; }
+
+        /// <summary>
+        /// 客户端编号,如PDA,HCWMS等
+        /// </summary>
+        public string clientCode { get; set; }
+
+        /// <summary>
+        /// 令牌号, 由调度系统颁发
+        /// </summary>
+        public string tokenCode { get; set; }
+
+        /// <summary>
+        /// 任务类型
+        /// </summary>
+        public string taskTyp { get; set; }
+
+        /// <summary>
+        /// 容器类型(叉车/CTU专用)叉车项目必传
+        /// </summary>
+        public string ctnrTyp { get; set; }
+
+        /// <summary>
+        ///  RFID
+        /// </summary>
+        public string ctnrCode { get; set; }
+
+        /// <summary>
+        /// 接口名称
+        /// </summary>
+        public string interfaceName { get; set; }
+
+        /// <summary>
+        /// 任务模式
+        /// </summary>
+        public string taskMode { get; set; }
+
+        /// <summary>
+        ///  合金任务模板
+        /// </summary>
+        public string hjTaskTy { get; set; }
+
+        /// <summary>
+        /// 工作位
+        /// </summary>
+        public string wbCode { get; set; }
+
+        /// <summary>
+        /// 位置路径
+        /// </summary>
+        public List<positionCodeClass> positionCodePath { get; set; }
+
+        /// <summary>
+        ///  位置类型
+        /// </summary>
+        public string type { get; set; }
+
+        /// <summary>
+        /// 货架编号
+        /// </summary>
+        public string podCode { get; set; }
+
+        /// <summary>
+        /// “180”,”0”,”90”,”-90”
+        /// </summary>
+        public string podDir { get; set; }
+
+        /// <summary>
+        /// 货架类型, 传空时表示随机找个货架
+        /// </summary>
+        public string podTyp { get; set; }
+
+        /// <summary>
+        /// 物料批次或货架上的物料唯一编码
+        /// </summary>
+        public string materialLot { get; set; }
+
+        /// <summary>
+        /// 优先级,从(1~127)级,最大优先级最高
+        /// </summary>
+        public string priority { get; set; }
+
+        /// <summary>
+        /// 任务单号,选填, 不填系统自动生成,UUID小于等于64位
+        /// </summary>
+        public string taskCode { get; set; }
+
+        /// <summary>
+        /// AGV编号,填写表示指定某一编号的AGV执行该任务
+        /// </summary>
+        public string agvCode { get; set; }
+
+        /// <summary>
+        /// 组编号
+        /// </summary>
+        public string groupId { get; set; }
+
+        /// <summary>
+        /// 自定义字段.JSON格式
+        /// </summary>
+        public string data { get; set; }
+    }
+
+    /// <summary>
+    ///  AGV生成任务单请求TaskTyp对应枚举
+    /// </summary>
+    public class GenAgvSchedulingTaskRequestTaskTyp
+    {
+        /// <summary>
+        ///  厂内货架搬运
+        /// </summary>
+        public string 厂内货架搬运 { get; set; } = "F01";
+
+        /// <summary>
+        ///  厂内货架空满交换
+        /// </summary>
+        public string 厂内货架空满交换 { get; set; } = "F02";
+
+        /// <summary>
+        ///  辊筒搬运接驳
+        /// </summary>
+        public string 辊筒搬运接驳 { get; set; } = "F03";
+
+        /// <summary>
+        ///  厂内货架出库AGV待命
+        /// </summary>
+        public string 厂内货架出库AGV待命 { get; set; } = "F04";
+
+        /// <summary>
+        ///  旋转货架
+        /// </summary>
+        public string 旋转货架 { get; set; } = "F05";
+
+        /// <summary>
+        ///  厂内电梯任务
+        /// </summary>
+        public string 厂内电梯任务 { get; set; } = "F06";
+
+        /// <summary>
+        ///  以下为叉车专用任务类型高位货架到工作台
+        /// </summary>
+        public string 以下为叉车专用任务类型高位货架到工作台 { get; set; } = "F11";
+
+        /// <summary>
+        ///  工作台到高位货架
+        /// </summary>
+        public string 工作台到高位货架 { get; set; } = "F12";
+
+        /// <summary>
+        ///  工作台到巷道
+        /// </summary>
+        public string 工作台到巷道 { get; set; } = "F14";
+
+        /// <summary>
+        ///  高位货架到工作台接驳
+        /// </summary>
+        public string 高位货架到工作台接驳 { get; set; } = "F15";
+
+        /// <summary>
+        ///  工作台到高位货架接驳
+        /// </summary>
+        public string 工作台到高位货架接驳 { get; set; } = "F16";
+
+        /// <summary>
+        ///  巷道到工作台接驳
+        /// </summary>
+        public string 巷道到工作台接驳 { get; set; } = "F17";
+
+        /// <summary>
+        ///  工作台到巷道接驳
+        /// </summary>
+        public string 工作台到巷道接驳 { get; set; } = "F18";
+
+        /// <summary>
+        ///  叉车电梯主任务
+        /// </summary>
+        public string 叉车电梯主任务 { get; set; } = "F20";
+    }
+
+    public class positionCodeClass
+    {
+        public string positionCode { get; set; }
+        public string type { get; set; }
+    }
+}

+ 103 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/zhongTianIntoStockRequest.cs

@@ -0,0 +1,103 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    ///  合金二楼下发AGV入库任务
+    /// </summary>
+    public class zhongTianIntoStockRequest
+    {
+        /// <summary>
+        /// 物料编码
+        /// </summary>
+        public string matCode { get; set; }
+
+        /// <summary>
+        /// 工作站编号
+        /// </summary>
+        public string wbCode { get; set; }
+
+        /// <summary>
+        /// 空/满工字轮入库标记
+        /// </summary>
+        public string inSpoolFull { get; set; }
+
+        /// <summary>
+        /// 取满补空标记
+        /// </summary>
+        public bool getOutEmpty { get; set; }
+
+        /// <summary>
+        /// 湿拉上线标记
+        /// </summary>
+        public bool wetInto { get; set; }
+
+        /// <summary>
+        /// 湿拉特殊上线标记
+        /// </summary>
+        public bool wetIntoSpec { get; set; }
+
+        /// <summary>
+        /// 湿拉退料入库标记
+        /// </summary>
+        public bool wetIntoReturn { get; set; }
+
+        /// <summary>
+        /// 湿拉特殊上线点位号
+        /// </summary>
+        public string wetSpecWbCode { get; set; }
+
+        /// <summary>
+        /// 改手盘标记
+        /// </summary>
+        public string isSurplus { get; set; }
+
+        /// <summary>
+        /// 返工标记
+        /// </summary>
+        public string isRework { get; set; }
+
+        /// <summary>
+        /// 材料号
+        /// </summary>
+        public string matNo { get; set; }
+
+        /// <summary>
+        /// RFID号
+        /// </summary>
+        public string spoolNo { get; set; }
+
+        /// <summary>
+        /// 质量状态标记
+        /// </summary>
+        public string gradeCode { get; set; }
+
+        /// <summary>
+        /// 快投标记
+        /// </summary>
+        public string matFast { get; set; }
+
+        /// <summary>
+        /// 长度
+        /// </summary>
+        public string orderProcessLenOut { get; set; }
+
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public string taskNo { get; set; }
+
+        /// <summary>
+        /// 退料原因
+        /// </summary>
+        public string returnDesc { get; set; }
+
+        /// <summary>
+        /// 是否锁定
+        /// </summary>
+        public string lockFlag { get; set; }
+
+        /// <summary>
+        ///  仓位号
+        /// </summary>
+        public string wetIntoBinCode { get; set; }
+    }
+}

+ 78 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Request/zhongTianOutStockRequest.cs

@@ -0,0 +1,78 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Request
+{
+    /// <summary>
+    ///  合金二楼下发AGV出库任务
+    /// </summary>
+    public class zhongTianOutStockRequest
+    {
+        /// <summary>
+        /// 请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+
+        /// <summary>
+        /// 物料编码
+        /// </summary>
+        public string matCode { get; set; }
+
+        /// <summary>
+        /// 工作站编号
+        /// </summary>
+        public string workAreaCode { get; set; }
+
+        /// <summary>
+        /// 空/满工字轮标记
+        /// </summary>
+        public string outSpoolFull { get; set; }
+
+        /// <summary>
+        /// 取空补满标记
+        /// </summary>
+        public bool intoEmpty { get; set; }
+
+        /// <summary>
+        /// 湿拉出库标记
+        /// </summary>
+        public string wetOut { get; set; }
+
+        /// <summary>
+        /// 湿拉退料出库标记
+        /// </summary>
+        public bool wetOutReturn { get; set; }
+
+        /// <summary>
+        /// 改手盘标记
+        /// </summary>
+        public string isSurplus { get; set; }
+
+        /// <summary>
+        /// 返工标记
+        /// </summary>
+        public string isRework { get; set; }
+
+        /// <summary>
+        /// 材料号
+        /// </summary>
+        public string matNo { get; set; }
+
+        /// <summary>
+        /// RFID号
+        /// </summary>
+        public string spoolNo { get; set; }
+
+        /// <summary>
+        /// 质量状态标记
+        /// </summary>
+        public string gradeCode { get; set; }
+
+        /// <summary>
+        /// 快投标记
+        /// </summary>
+        public string matFast { get; set; }
+
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public string taskNo { get; set; }
+    }
+}

+ 29 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/AddWcsMoveTaskResponse.cs

@@ -0,0 +1,29 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    /// <summary>
+    ///  获取移库任务
+    /// </summary>
+    public class AddWcsMoveTaskResponse
+    {
+        /// <summary>
+        /// 0:失败;1:允许2升位执行;2:执行移库任务;3:一深位有出库任务
+        /// </summary>
+        public WmsApiMoveTask ResType { get; set; } = 0;
+
+        /// <summary>
+        /// 一深位货位号
+        /// </summary>
+        public string CellNo { get; set; }
+    }
+
+    /// <summary>
+    /// 获取移库任务返回结果
+    /// </summary>
+    public enum WmsApiMoveTask
+    {
+        失败 = 0,
+        允许2升位执行 = 1,
+        执行移库任务 = 2,
+        一深位有出库任务 = 3
+    }
+}

+ 35 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/ApplyEmptySpoolResponse.cs

@@ -0,0 +1,35 @@
+using WCS.WorkEngineering.WebApi.Models.WMS.Response;
+
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    /// <summary>
+    ///  agv补空返回参数
+    /// </summary>
+    public class ApplyEmptySpoolResponse
+    {
+        /// <summary>
+        ///  站台编号
+        /// </summary>
+        public string LocCode { get; set; }
+
+        /// <summary>
+        ///  工字轮类型
+        /// </summary>
+        public string SpoolType { get; set; }
+
+        /// <summary>
+        ///  返回状态码
+        /// </summary>
+        public ResponseStatusCodeEnum ResCode { get; set; }
+
+        /// <summary>
+        ///  描述信息
+        /// </summary>
+        public string ResMsg { get; set; }
+
+        /// <summary>
+        ///  任务号
+        /// </summary>
+        public string TaskCode { get; set; }
+    }
+}

+ 23 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/CancelTaskResponse.cs

@@ -0,0 +1,23 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    /// <summary>
+    ///  取消任务返回接口
+    /// </summary>
+    public class CancelTaskResponse
+    {
+        /// <summary>
+        /// 返回码
+        /// </summary>
+        public string code { get; set; }
+
+        /// <summary>
+        /// 返回消息
+        /// </summary>
+        public string message { get; set; }
+
+        /// <summary>
+        /// 请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+    }
+}

+ 23 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/ContinueTaskResponse.cs

@@ -0,0 +1,23 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    /// <summary>
+    ///  Agv继续执行任务返回参数
+    /// </summary>
+    public class ContinueTaskResponse
+    {
+        /// <summary>
+        ///  返回码 对应类:AgvContinueTaskResponseCode
+        /// </summary>
+        public string code { get; set; }
+
+        /// <summary>
+        ///  返回消息
+        /// </summary>
+        public string message { get; set; }
+
+        /// <summary>
+        ///  请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+    }
+}

+ 25 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/GenAgvSchedulingTaskResponse.cs

@@ -0,0 +1,25 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    public class GenAgvSchedulingTaskResponse
+    {
+        /// <summary>
+        /// 返回码
+        /// </summary>
+        public string code { get; set; }
+
+        /// <summary>
+        /// 返回消息
+        /// </summary>
+        public string message { get; set; }
+
+        /// <summary>
+        /// 请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+
+        /// <summary>
+        /// 自定义返回(返回任务单号)
+        /// </summary>
+        public string data { get; set; }
+    }
+}

+ 20 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/agvCallbackResponse.cs

@@ -0,0 +1,20 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    public class AgvCallbackResponse
+    {
+        /// <summary>
+        /// 返回码 1:允许执行 0:不允许执行
+        /// </summary>
+        public string code { get; set; } = "0";
+
+        /// <summary>
+        /// 返回消息
+        /// </summary>
+        public string message { get; set; } = "成功";
+
+        /// <summary>
+        /// 请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+    }
+}

+ 28 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/zhongTianIntoStockResponse.cs

@@ -0,0 +1,28 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    /// <summary>
+    ///
+    /// </summary>
+    public class zhongTianIntoStockResponse
+    {
+        /// <summary>
+        /// 返回码
+        /// </summary>
+        public string code { get; set; }
+
+        /// <summary>
+        /// 自定义返回(返回任务单号)
+        /// </summary>
+        public string data { get; set; }
+
+        /// <summary>
+        /// 返回消息
+        /// </summary>
+        public string message { get; set; }
+
+        /// <summary>
+        /// 请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+    }
+}

+ 25 - 0
WCS.WorkEngineering/WebApi/Models/AGV/Response/zhongTianOutStockResponse.cs

@@ -0,0 +1,25 @@
+namespace WCS.WorkEngineering.WebApi.Models.AGV.Response
+{
+    public class zhongTianOutStockResponse
+    {
+        /// <summary>
+        /// 返回码
+        /// </summary>
+        public string code { get; set; }
+
+        /// <summary>
+        /// 自定义返回(返回任务单号)
+        /// </summary>
+        public string data { get; set; }
+
+        /// <summary>
+        /// 返回消息
+        /// </summary>
+        public string message { get; set; }
+
+        /// <summary>
+        /// 请求编号
+        /// </summary>
+        public string reqCode { get; set; }
+    }
+}

+ 58 - 0
WCS.WorkEngineering/WebApi/Models/WCS/Request/DevRunInfoRequest.cs

@@ -0,0 +1,58 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WCS.Entity.Protocol.HUB;
+
+namespace WCS.WorkEngineering.WebApi.Models.WCS.Request
+{
+    /// <summary>
+    ///  设备运行状态
+    /// </summary>
+    public class DevRunInfoViewMode
+    {
+        /// <summary>
+        ///  类型
+        /// </summary>
+        public DevType Type { get; set; }
+
+        /// <summary>
+        /// 范围
+        /// </summary>
+        public ScopeType Scope { get; set; }
+
+        /// <summary>
+        ///  开始时间
+        /// </summary>
+        public DateTime StateTime { get; set; }
+
+        /// <summary>
+        ///  结束时间
+        /// </summary>
+        public DateTime EndTime { get; set; }
+    }
+
+    public enum ScopeType
+    {
+        /// <summary>
+        ///  年
+        /// </summary>
+        Year = 1,
+
+        /// <summary>
+        ///  月
+        /// </summary>
+        Month = 2,
+
+        /// <summary>
+        ///  日
+        /// </summary>
+        Day = 3,
+
+        /// <summary>
+        ///  小时
+        /// </summary>
+        Hour = 4,
+    }
+}

+ 46 - 0
WCS.WorkEngineering/WebApi/Models/WCS/Request/HandleTaskRequest.cs

@@ -0,0 +1,46 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace WCS.WorkEngineering.WebApi.Models.WCS.Request
+{
+    /// <summary>
+    ///  处理任务请求参数
+    /// </summary>
+    public class HandleTaskRequest
+    {
+        /// <summary>
+        ///  任务号
+        /// </summary>
+        [Required]
+        public List<int> TaskIds { get; set; }
+
+        /// <summary>
+        ///  处理类型
+        /// </summary>
+        [Required]
+        public HandleTaskTypeEnum Type { get; set; }
+
+        /// <summary>
+        ///  用户
+        /// </summary>
+        public string User { get; set; }
+
+        /// <summary>
+        ///  手动处理备注
+        /// </summary>
+        public string ManualRemarks { get; set; }
+
+        /// <summary>
+        /// 优先级
+        /// </summary>
+        public int Priority { get; set; }
+    }
+
+    public enum HandleTaskTypeEnum
+    {
+        取消任务 = 1,
+        完成任务 = 2,
+        重新下发AGV任务 = 3,
+        调整优先级 = 4
+        //重新下发出库AGV任务 = 1,
+    }
+}

+ 57 - 0
WCS.WorkEngineering/WebApi/Models/WCS/Request/PaginationRequest.cs

@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WCS.WorkEngineering.WebApi.Models.WCS.Request
+{
+    /// <summary>
+    ///  有关分页的请求体
+    /// </summary>
+    public class PaginationRequest 
+    {
+        /// <summary>
+        /// 每页行数
+        /// </summary>
+        public int rows { get; set; }
+
+        /// <summary>
+        /// 当前页
+        /// </summary>
+        public int page { get; set; }
+
+        /// <summary>
+        /// 排序列
+        /// </summary>
+        public string sidx { get; set; }
+
+        /// <summary>
+        /// 排序类型
+        /// </summary>
+        public string sord { get; set; }
+
+        /// <summary>
+        /// 总记录数
+        /// </summary>
+        public int records { get; set; }
+
+        /// <summary>
+        /// 总页数
+        /// </summary>
+        public int total
+        {
+            get
+            {
+                if (records > 0)
+                {
+                    return records % this.rows == 0 ? records / this.rows : records / this.rows + 1;
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+        }
+    }
+}

+ 23 - 0
WCS.WorkEngineering/WebApi/Models/WCS/Response/HandleTaskResponse.cs

@@ -0,0 +1,23 @@
+namespace WCS.WorkEngineering.WebApi.Models.WCS.Response
+{
+    /// <summary>
+    ///  处理任务接口返回内容
+    /// </summary>
+    public class HandleTaskResponse
+    {
+        /// <summary>
+        ///  是否成功
+        /// </summary>
+        public bool IsSuccess { get; set; }
+
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public int TaskNo { get; set; }
+
+        /// <summary>
+        ///  消息
+        /// </summary>
+        public string Message { get; set; }
+    }
+}

+ 56 - 0
WCS.WorkEngineering/WebApi/Models/WCS/Response/PagedInfoResponse.cs

@@ -0,0 +1,56 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WCS.WorkEngineering.WebApi.Models.WCS.Response
+{
+    /// <summary>
+    ///  带分页的返回体
+    /// </summary>
+    /// <typeparam name="T"></typeparam>
+    public class PagedInfoResponse<T>
+    {
+        /// <summary>
+        /// 每页行数
+        /// </summary>
+        public int PageSize { get; set; } = 10;
+
+        /// <summary>
+        /// 当前页
+        /// </summary>
+        public int PageIndex { get; set; } = 1;
+
+        /// <summary>
+        /// 总记录数
+        /// </summary>
+        public int TotalNum { get; set; }
+
+        /// <summary>
+        /// 总页数
+        /// </summary>
+        public int TotalPage
+        {
+            get
+            {
+                if (TotalNum > 0)
+                {
+                    return TotalNum % PageSize == 0 ? TotalNum / PageSize : TotalNum / PageSize + 1;
+                }
+                else
+                {
+                    return 0;
+                }
+            }
+            set { }
+        }
+
+        public List<T> Result { get; set; }
+        public Dictionary<string, object> Extra { get; set; } = new Dictionary<string, object>();
+
+        public PagedInfoResponse()
+        {
+        }
+    }
+}

+ 149 - 0
WCS.WorkEngineering/WebApi/Models/WCS/Response/RunInfoViewMode.cs

@@ -0,0 +1,149 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using WCS.WorkEngineering.WebApi.Controllers;
+
+namespace WCS.WorkEngineering.WebApi.Models.WCS.Response
+{
+    /// <summary>
+    ///  有关设备运行信息的dto
+    /// </summary>
+    public class RunInfoViewMode
+    {
+        public string Code { get; set; }
+
+        /// <summary>
+        ///  出库任务
+        /// </summary>
+        public int OutDepot { get; set; }
+
+        /// <summary>
+        ///  入库任务
+        /// </summary>
+        public int EnterDepot { get; set; }
+
+        /// <summary>
+        ///  移库任务
+        /// </summary>
+        public int MoveDepot { get; set; }
+
+        /// <summary>
+        ///  空闲
+        /// </summary>
+        public double Free { get; set; }
+
+        /// <summary>
+        ///  手动
+        /// </summary>
+        public double Manual { get; set; }
+
+        /// <summary>
+        ///  自动
+        /// </summary>
+        public double Automatic { get; set; }
+
+        /// <summary>
+        ///  报警
+        /// </summary>
+        public double Alarm { get; set; }
+
+        /// <summary>
+        ///  运行
+        /// </summary>
+        public double Working { get; set; }
+
+        /// <summary>
+        ///  总时间
+        /// </summary>
+        public double TotalTime { get; set; }
+
+        /// <summary>
+        ///  设备执行分析
+        /// </summary>
+        public List<DevActionViewMode> DevAction { get; set; }
+
+        /// <summary>
+        ///  任务执行工位使用数量
+        /// </summary>
+        public List<StationCount> StationCount { get; set; }
+
+        /// <summary>
+        /// 开始时间
+        /// </summary>
+        public DateTime StartTime { get; set; }
+
+        /// <summary>
+        ///  结束时间
+        /// </summary>
+        public DateTime EndTime { get; set; }
+    }
+
+    /// <summary>
+    ///  设备动作分析
+    /// </summary>
+    public class DevActionViewMode
+    {
+        /// <summary>
+        ///  动作次数
+        /// </summary>
+        public double ActionAmount { get; set; }
+
+        /// <summary>
+        ///  动作平均耗时
+        /// </summary>
+        public double ActionTime { get; set; }
+
+        /// <summary>
+        ///  动作类型
+        /// </summary>
+        public string Type { get; set; }
+    }
+
+    /// <summary>
+    ///  任务执行工位数量
+    /// </summary>
+    public class StationCount
+    {
+        /// <summary>
+        ///  执行数量
+        /// </summary>
+        public double StationNum { get; set; }
+
+        /// <summary>
+        ///  工位数
+        /// </summary>
+        public double StationIndex { get; set; }
+
+        /// <summary>
+        ///  任务类型
+        /// </summary>
+        public string Type { get; set; }
+    }
+
+    /// <summary>
+    ///  设备动作类型
+    /// </summary>
+    public enum DevActionType
+    {
+        /// <summary>
+        ///  入库
+        /// </summary>
+        [Description("入库")]
+        In = 0,
+
+        /// <summary>
+        ///  出库
+        /// </summary>
+        [Description("出库")]
+        Out = 1,
+
+        /// <summary>
+        ///  移库
+        /// </summary>
+        [Description("移库")]
+        Move = 2,
+    }
+}

+ 32 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/ApplyStockInLocRequest.cs

@@ -0,0 +1,32 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    /// 分配货位请求实体
+    /// </summary>
+    public class ApplyStockInLocRequest
+    {
+        /// <summary>
+        /// WMS任务号
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public int TaskNum { get; set; }
+
+        /// <summary>
+        /// 巷道号
+        /// </summary>
+        public int TunnelNum { get; set; }
+
+        /// <summary>
+        /// 取货地点设备编号
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string PickUpEquipmentNo { get; set; }
+
+        /// <summary>
+        ///  大小货 1表示小货 2表示大货
+        /// </summary>
+        public int Height { get; set; }
+    }
+}

+ 20 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/ApplyStockOutTaskRequest.cs

@@ -0,0 +1,20 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    public class ApplyStockOutTaskRequest
+    {
+        /// <summary>
+        ///  托盘类型
+        /// </summary>
+        public PalletType FJPalletType { get; set; }
+
+        /// <summary>
+        ///  巷道
+        /// </summary>
+        public int Tunnel { get; set; }
+
+        /// <summary>
+        ///  目标位置
+        /// </summary>
+        public string AddTo { get; set; }
+    }
+}

+ 18 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/CancelTaskVerifyRequest.cs

@@ -0,0 +1,18 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  取消或完成任务验证请求
+    /// </summary>
+    public class CancelTaskVerifyRequest
+    {
+        /// <summary>
+        ///  任务号集合
+        /// </summary>
+        public List<int> TaskNo { get; set; }
+
+        /// <summary>
+        ///  目标状态
+        /// </summary>
+        public int State { get; set; }
+    }
+}

+ 47 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/CompleteTaskRequest.cs

@@ -0,0 +1,47 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  完成任务请求
+    /// </summary>
+    public class CompleteTaskRequest
+    {
+        /// <summary>
+        /// WMS任务号
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public int TaskNum { get; set; }
+
+        /// <summary>
+        /// 操作类型(1.自动完成 2.手动完成)
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public CompleteTask OperationType { get; set; }
+
+        /// <summary>
+        /// WCS操作人名称
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string WCSUpdateName { get; set; }
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo1 { get; set; } = "";
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo2 { get; set; } = "";
+    }
+
+    /// <summary>
+    ///  完成任务类型
+    /// </summary>
+    public enum CompleteTask
+    {
+        自动完成 = 1,
+        手动完成 = 2
+    }
+}

+ 15 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/CompleteTaskVerifyRequest.cs

@@ -0,0 +1,15 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    public class CompleteTaskVerifyRequest
+    {
+        /// <summary>
+        ///  任务号集合
+        /// </summary>
+        public List<int> TaskNo { get; set; }
+
+        /// <summary>
+        ///  目标状态
+        /// </summary>
+        public int State { get; set; }
+    }
+}

+ 13 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/EnteMainLineRequest.cs

@@ -0,0 +1,13 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  工字轮/芯股进入主线扫码
+    /// </summary>
+    public class EnteMainLineRequest
+    {
+        /// <summary>
+        ///  工字轮条码集合
+        /// </summary>
+        public List<string> IShapedWheelCodes { get; set; }
+    }
+}

+ 3 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/FJBingPalletRequest.cs

@@ -0,0 +1,3 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+}

+ 56 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/FJBuildEmptyPalletsStockRequest.cs

@@ -0,0 +1,56 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    
+
+    /// <summary>
+    ///  分拣业务类型
+    /// </summary>
+    public enum TaskBusType
+    {
+        /// <summary>
+        ///  默认
+        /// </summary>
+        [Description("默认")]
+        def = 0,
+
+        /// <summary>
+        ///  一楼人工入空托盘
+        /// </summary>
+        [Description("一楼人工入空托盘")]
+        OneLayerManualPallets = 1,
+
+        /// <summary>
+        ///  一楼自动入空托盘
+        /// </summary>
+        [Description("一楼自动入空托盘")]
+        OneLayerAutoPallets = 2,
+
+        /// <summary>
+        ///  手动出库
+        /// </summary>
+        [Description("手动出库")]
+        TaskBusType_FJ_ManualOut = 3,
+
+        /// <summary>
+        ///  二楼组盘
+        /// </summary>
+        [Description("二楼组盘")]
+        二楼组盘 = 4,
+
+        /// <summary>
+        ///  二楼空托盘组出库
+        /// </summary>
+        [Description("二楼空托盘组出库")]
+        二楼空托盘组出库 = 5,
+
+        /// <summary>
+        ///  组盘_托盘
+        /// </summary>
+        [Description("组盘_托盘")]
+        组盘_托盘 = 6,
+    }
+}

+ 18 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/FJEnteMainLineRequest.cs

@@ -0,0 +1,18 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  工字轮/芯股进入主线扫码
+    /// </summary>
+    public class FJEnteMainLineRequest
+    {
+        /// <summary>
+        ///  工字轮条码集合
+        /// </summary>
+        public List<string> IShapedWheelCodes { get; set; }
+
+        /// <summary>
+        ///  设备号
+        /// </summary>
+        public string equNo { get; set; }
+    }
+}

+ 9 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/GetTunnelEmptyConCountRequest.cs

@@ -0,0 +1,9 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  获取巷道剩余空轮请求参数
+    /// </summary>
+    public class GetTunnelEmptyConCountRequest
+    {
+    }
+}

+ 23 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/GetTunnelPriorityListRequest.cs

@@ -0,0 +1,23 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  获取巷道请求参数
+    /// </summary>
+    public class GetTunnelPriorityListRequest
+    {
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public int TaskNum { get; set; }
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo1 { get; set; } = "";
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo2 { get; set; } = "";
+    }
+}

+ 54 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/I_WCS_GetInTaskRequest.cs

@@ -0,0 +1,54 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    /// 获取入库任务请求实体
+    /// </summary>
+    public class I_WCS_GetInTaskRequest
+    {
+        /// <summary>
+        /// 容器编号
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string ContainerBarCode { get; set; }
+
+        /// <summary>
+        /// 容器类型
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public int ContainerType { get; set; }
+
+        /// <summary>
+        /// 物料条码
+        /// </summary>
+        public string MatBarCode { get; set; }
+
+        /// <summary>
+        /// 仓库编码
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string WareHouseId { get; set; }
+
+        /// <summary>
+        /// 设备编号
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string EquipmentNo { get; set; }
+
+        /// <summary>
+        /// 目标位置
+        /// </summary>
+        public string EndPostion { get; set; }
+
+        /// <summary>
+        /// 货叉(直接申请货位使用)
+        /// </summary>
+        public int ForkNum { get; set; }
+
+        public string Memo1 { get; set; }
+        public string Memo2 { get; set; }
+        public string Memo3 { get; set; }
+        public string Memo4 { get; set; }
+    }
+}

+ 60 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/OneFloorWorkerBuildEmptyPalletsStockRequest.cs

@@ -0,0 +1,60 @@
+using System.ComponentModel;
+using System.ComponentModel.DataAnnotations;
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    public class OneFloorWorkerBuildEmptyPalletsStockRequest
+    {
+        /// <summary>
+        ///  托盘编码
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string PalletCode { get; set; }
+
+        /// <summary>
+        ///  托盘类型
+        /// </summary>
+        public PalletType PalletType { get; set; }
+
+        /// <summary>
+        ///  入库起始地址
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string StartLoc { get; set; }
+
+        /// <summary>
+        ///  仓库编码
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public string WareCode { get; set; }
+
+        /// <summary>
+        ///  托盘数量
+        /// </summary>
+        [Description("托盘数量")]
+        public string PalletNum { get; set; }
+
+        /// <summary>
+        ///  大小货 1表示小货 2表示大货
+        /// </summary>
+        public int Height { get; set; }
+    }
+
+    /// <summary>
+    ///  托盘类型
+    /// </summary>
+    public enum PalletType
+    {
+        /// <summary>
+        ///  09使用的托盘
+        /// </summary>
+        [Description("09使用的托盘")]
+        Pallet09 = 1,
+
+        /// <summary>
+        ///  非09使用的托盘
+        /// </summary>
+        [Description("非09使用的托盘")]
+        PalletNo09 = 2,
+    }
+}

+ 36 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/PalletizingCreateseErrorTasksRequest.cs

@@ -0,0 +1,36 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  码垛创建异常工字轮信息
+    /// </summary>
+    public class PalletizingCreateseErrorTasksRequest
+    {
+        /// <summary>
+        ///  码垛创建异常工字轮信息
+        /// </summary>
+        /// <param name="equCode"></param>
+        /// <param name="type"></param>
+        /// <param name="warehouseCode"></param>
+        public PalletizingCreateseErrorTasksRequest(string equCode, string type, string warehouseCode)
+        {
+            WarehouseCode = warehouseCode;
+            EquNo = equCode;
+            Type = type;
+        }
+
+        /// <summary>
+        ///  设备号
+        /// </summary>
+        public string? EquNo { get; set; }
+
+        /// <summary>
+        ///  工资轮型号 9/18/34/50
+        /// </summary>
+        public string? Type { get; set; }
+
+        /// <summary>
+        ///  仓库编号
+        /// </summary>
+        public string? WarehouseCode { get; set; }
+    }
+}

+ 36 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/RingApplyPalletizingStockOutRequest.cs

@@ -0,0 +1,36 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  环形库申请码垛出库任务
+    /// </summary>
+    public class RingApplyPalletizingStockOutRequest
+    {
+        /// <summary>
+        ///  环形库申请码垛出库任务
+        /// </summary>
+        /// <param name="wareHouseCode"></param>
+        /// <param name="equCode"></param>
+        /// <param name="device"></param>
+        public RingApplyPalletizingStockOutRequest(string wareHouseCode, string equCode, string device)
+        {
+            WareHouseCode = wareHouseCode;
+            EquCode = equCode;
+            Device = device;
+        }
+
+        /// <summary>
+        /// 仓库编码
+        /// </summary>
+        public string WareHouseCode { get; set; }
+
+        /// <summary>
+        /// 设备编号
+        /// </summary>
+        public string EquCode { get; set; }
+
+        /// <summary>
+        ///  机械臂
+        /// </summary>
+        public string Device { get; set; }
+    }
+}

+ 13 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/SrmPickOutCompletedRequest.cs

@@ -0,0 +1,13 @@
+using System.ComponentModel.DataAnnotations;
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    public class SrmPickOutCompletedRequest
+    {
+        /// <summary>
+        /// WMS任务号
+        /// </summary>
+        [Required(ErrorMessage = "{0} 不可为空")]
+        public int TaskNum { get; set; }
+    }
+}

+ 23 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Request/WcsUploadInfoRequest.cs

@@ -0,0 +1,23 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Request
+{
+    /// <summary>
+    ///  WMS上抛信息请求
+    /// </summary>
+    public class WcsUploadInfoRequest
+    {
+        /// <summary>
+        /// 任务号
+        /// </summary>
+        public int TaskCode { get; set; }
+
+        /// <summary>
+        /// 重量
+        /// </summary>
+        public decimal Weight { get; set; }
+
+        /// <summary>
+        /// RFID
+        /// </summary>
+        public string RFID { get; set; }
+    }
+}

+ 33 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Response/ApplyStockInLocResponse.cs

@@ -0,0 +1,33 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Response
+{
+    /// <summary>
+    /// 分配货位响应实体
+    /// </summary>
+    public class ApplyStockInLocResponse
+    {
+        /// <summary>
+        /// 货位号
+        /// </summary>
+        public string CellNo { get; set; }
+
+        /// <summary>
+        /// 巷道号
+        /// </summary>
+        public string TunnelNum { get; set; }
+
+        /// <summary>
+        /// 行
+        /// </summary>
+        public int Row { get; set; }
+
+        /// <summary>
+        /// 列
+        /// </summary>
+        public int Colomn { get; set; }
+
+        /// <summary>
+        /// 层
+        /// </summary>
+        public int Layer { get; set; }
+    }
+}

+ 18 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Response/GetTunnelEmptyConResponse.cs

@@ -0,0 +1,18 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Response
+{
+    /// <summary>
+    ///  获取巷道空轮数量
+    /// </summary>
+    public class GetTunnelEmptyConResponse
+    {
+        /// <summary>
+        /// 巷道
+        /// </summary>
+        public int Tunnel { get; set; }
+
+        /// <summary>
+        /// 空轮数量
+        /// </summary>
+        public int Count { get; set; }
+    }
+}

+ 46 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Response/I_WCS_GetInTaskResponse.cs

@@ -0,0 +1,46 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Response
+{
+    public class I_WCS_GetInTaskResponse : WcsContractApiResponse
+    {
+        /// <summary>
+        /// WMS任务号
+        /// </summary>
+        public string WMSTaskNum { get; set; } = "0";
+
+        /// <summary>
+        /// 任务类型(1:入库2:出库3:移库4:移动(搬运) 5:异常 )
+        /// </summary>
+        public int TaskType { get; set; }
+
+        /// <summary>
+        /// 仓库名称
+        /// </summary>
+        public string WareHouseName { get; set; } = "";
+
+        /// <summary>
+        /// 入库巷道(集合巷道,由WCS自行判断入到哪一个巷道,最前面的最优先。)
+        /// </summary>
+        public string TunnelNum { get; set; } = "";
+
+        /// <summary>
+        /// 目标位置(入库该地址为srm.如果是移动任务,该地址为WCS传递的目标位置。)
+        /// </summary>
+        public string EndPostion { get; set; } = "";
+
+        public int Priority { get; set; } = 0;
+
+        /// <summary>
+        /// 产品编码(半成品入库使用)
+        /// </summary>
+        public string ContainerCode { get; set; } = "";
+
+        /// <summary>
+        /// 0默认值  1载货  2空盘(包含一个托盘或一摞两种情况)
+        /// </summary>
+        public int ContainerType { get; set; } = 0;
+
+        public string Memo1 { get; set; } = "";
+        public string Memo2 { get; set; } = "";
+        public string Memo3 { get; set; } = "";
+    }
+}

+ 73 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Response/SRes.cs

@@ -0,0 +1,73 @@
+using System.ComponentModel;
+
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Response
+{
+    /// <summary>
+    ///  WCS上传信息接口返回
+    /// </summary>
+    public class SRes
+    {
+        /// <summary>
+        /// 返回状态码
+        /// </summary>
+        public ResponseStatusCodeEnum ResCode { get; set; }
+
+        /// <summary>
+        /// 返回描述信息
+        /// </summary>
+        public string ResMsg { get; set; } = "成功";
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo1 { get; set; }
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo2 { get; set; }
+
+        /// <summary>
+        /// 备用
+        /// </summary>
+        public string Memo3 { get; set; }
+    }
+
+    /// <summary>
+    ///  WMS返回DTO
+    /// </summary>
+    /// <typeparam name="T">泛型</typeparam>
+    public class SRes<T> : SRes
+    {
+        /// <summary>
+        /// 数据实体
+        /// </summary>
+        public T ResData { get; set; }
+
+        /// <summary>
+        ///  集合数据实体
+        /// </summary>
+        public List<T> ResDataList { get; set; }
+    }
+
+    public enum ResponseStatusCodeEnum
+    {
+        /// <summary>
+        /// 成功
+        /// </summary>
+        [Description("成功")]
+        Sucess = 200,
+
+        /// <summary>
+        /// 参数错误
+        /// </summary>
+        [Description("参数错误")]
+        AccountError = 1001,
+
+        /// <summary>
+        /// 业务逻辑错误
+        /// </summary>
+        [Description("业务逻辑错误")]
+        DataSaveErr = 1002,
+    }
+}

+ 11 - 0
WCS.WorkEngineering/WebApi/Models/WMS/Response/WcsContractApiResponse.cs

@@ -0,0 +1,11 @@
+namespace WCS.WorkEngineering.WebApi.Models.WMS.Response
+{
+    public class WcsContractApiResponse
+    {
+        public bool ResType { get; set; }
+        public string ResMessage { get; set; }
+        public string Memo1 { get; set; }
+        public string Memo2 { get; set; }
+        public string Memo3 { get; set; }
+    }
+}

+ 65 - 0
WCS.WorkEngineering/WebApi/Startup.cs

@@ -0,0 +1,65 @@
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.OpenApi.Models;
+using System.Reflection;
+
+namespace ServiceCenter.WebApi
+{
+    public class Startup
+    {
+        public Startup(IConfiguration configuration)
+        {
+            Configuration = configuration;
+        }
+
+        public IConfiguration Configuration { get; }
+
+        public string MyCors = "Cor";
+
+        // This method gets called by the runtime. Use this method to add services to the container.
+        public void ConfigureServices(IServiceCollection services)
+        {
+            services.AddControllers();
+            //跨域配置
+            services.AddCors(v => v.AddPolicy(MyCors, y =>
+            {
+                //声明跨域策略:允许所有域、允许任何请求头和允许全部http方法
+                y.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod();
+            }));
+            services.AddSwaggerGen(c =>
+            {
+                //c.SwaggerDoc("v1", new OpenApiInfo { Title = "WCSAPI", Version = "v1" });
+                c.SwaggerDoc("v1", new OpenApiInfo
+                {
+                    Version = "v1",
+                    Title = "WCSAPI",
+                    Description = "API描述"
+                });
+                var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
+                c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));
+            });
+        }
+
+        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+        {
+            app.UseDeveloperExceptionPage();
+            app.UseSwagger();
+            app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "WebApplication1 v1"));
+            //http://localhost:8080/swagger/index.html
+
+            app.UseHttpsRedirection();
+
+            app.UseRouting();
+            app.UseCors(MyCors);
+            app.UseAuthorization();
+
+            app.UseEndpoints(endpoints =>
+            {
+                endpoints.MapControllers();
+            });
+        }
+    }
+}

+ 128 - 0
WCS.WorkEngineering/WorkStart.cs

@@ -0,0 +1,128 @@
+using ServiceCenter;
+using ServiceCenter.SqlSugars;
+using WCS.Core;
+using WCS.Entity.Protocol;
+using WCS.Entity.Protocol.BCR;
+using WCS.Entity.Protocol.HUB;
+using WCS.Entity.Protocol.RGV;
+using WCS.Entity.Protocol.Robot;
+using WCS.Entity.Protocol.SRM;
+using WCS.Entity.Protocol.Station;
+using WCS.Entity.Protocol.Truss;
+using WCS.WorkEngineering.Extensions;
+using DeviceFlags = WCS.WorkEngineering.Extensions.DeviceFlags;
+
+namespace WCS.WorkEngineering
+{
+    /// <summary>
+    /// 业务工程配置信息
+    /// </summary>
+    public static class WorkStart
+    {
+        /// <summary>
+        ///  初始化 设备信息
+        /// </summary>
+
+        public static void InitializeDeviceInfo()
+        {
+            var conv = new Device("1");
+            conv.AddFlag(DeviceFlags.输送机);
+            conv.AddProtocol<IStation520>(0, 520, "1");
+        }
+
+        /// <summary>
+        ///  初始化数据库连接
+        /// </summary>
+        /// <param name="datas"></param>
+        public static void InitDB(this List<DataBaseConnectionString> datas)
+        {
+            //初始化数据库
+            SqlSugarHelper.Do(db =>
+            {
+                foreach (var connectionString in datas!)
+                {
+                    var _db = db.Connect.GetConnectionScope(connectionString.Key);
+                    switch (connectionString.Key)
+                    {
+                        case "WCSDB"://WCS基本数据库
+                            SqlSugarHelper.SetDefault(connectionString.Key);
+                            break;
+
+                        case "WCSDlog"://WCS日志数据库
+
+                            break;
+
+                        case "PLC"://PLC
+
+                            SqlSugarHelper.SetPLC(connectionString.Key);
+
+                            break;
+
+                        case "PlcEx"://PLC
+
+                            SqlSugarHelper.SetPLCEX(connectionString.Key);
+
+                            //_db.DbMaintenance.CreateDatabase();
+                            //_db.CodeFirst.InitTables<DevRunInfo>();
+                            //_db.CodeFirst.InitTables<DevAlarmInfo>();
+
+                            break;
+
+                        default: //其他库
+                            break;
+                    };
+                };
+            });
+        }
+    }
+
+    public class DevDbConfig<T>
+    {
+        public DevDbConfig()
+        {
+        }
+
+        public DevDbConfig(string ip, T code)
+        {
+            IP = ip;
+            Code = code;
+        }
+
+        public DevDbConfig(string ip, List<DevInterval<T>> devIntervalList)
+        {
+            IP = ip;
+            DevIntervalList = devIntervalList;
+        }
+
+        public DevDbConfig(string ip, List<T> devCodeList)
+        {
+            IP = ip;
+            DevCodeList = devCodeList;
+        }
+
+        public string IP { get; set; }
+
+        public T Code { get; set; }
+
+        public T StartCode { get; set; }
+
+        public T EndCode { get; set; }
+
+        public List<T> DevCodeList { get; set; }
+
+        public List<DevInterval<T>> DevIntervalList { get; set; }
+    }
+
+    public class DevInterval<T>
+    {
+        public DevInterval(T s, T e)
+        {
+            StartCode = s;
+            EndCode = e;
+        }
+
+        public T StartCode { get; set; }
+
+        public T EndCode { get; set; }
+    }
+}

+ 133 - 0
WCS.WorkEngineering/Worlds/MainWorld.cs

@@ -0,0 +1,133 @@
+using ServiceCenter.Logs;
+using System.Collections.Concurrent;
+using System.ComponentModel;
+using WCS.Core;
+using LogInfo = ServiceCenter.Logs.LogInfo;
+
+namespace WCS.WorkEngineering.Worlds
+{
+    /// <summary>
+    /// 主世界,所有的系统(交互点)默认在该世界下执行。
+    /// 如有系统需独立,请自行增加对应世界
+    /// 新增世界应当继承此世界,而不是直接继承World
+    /// </summary>
+    [Description("主世界")]
+    public class MainWorld : World
+    {
+        /// <summary>
+        /// 构造函数
+        /// </summary>
+        public MainWorld()
+        {
+        }
+
+        /// <summary>
+        ///  日志队列
+        /// </summary>
+        protected ConcurrentQueue<KeyLog> Logs = new ConcurrentQueue<KeyLog>();
+
+        /// <summary>
+        ///  世界执行周期间隔
+        ///  单位:毫秒
+        /// </summary>
+        protected override int Interval => 500;
+
+        /// <summary>
+        ///  更新前执行,重写改方法后请自行添加执行内容
+        ///  执行内容:清空日志队列
+        /// </summary>
+        protected override void BeforeUpdate(List<WorkTimes> list)
+        {
+            // 清空日志队列,确保日志队列中只会有当前周期日志
+            Logs.Clear();
+        }
+
+        /// <summary>
+        /// 更新后执行,重写改方法后请自行添加执行内容
+        /// 执行内容:清空日志队列
+        /// </summary>
+        protected override void AfterUpdate(List<WorkTimes> list)
+        {
+            LogHub.WorldPublish(Logs, this.GetType().Name);
+        }
+
+        /// <summary>
+        ///  异常处理,重写改方法后请自行添加执行内容
+        ///  执行内容:Exception as KnownException并添加至日志队列
+        /// </summary>
+        /// <param name="channel"></param>
+        /// <param name="exception"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        protected override void OnError(Channel channel, Exception exception)
+        {
+            if (exception is KnownException)
+            {
+                var ex = exception as KnownException;
+                var log = new LogInfo { Level = ex.Level, Type = ErrorTypeEnum.Kown, LogUpLoad = ex.logUpLoad, Message = ex.Message };
+                Logs.Enqueue(new KeyLog { Channel = channel, Log = log, Time = DateTime.Now });
+            }
+            else
+            {
+                var log = new LogInfo { Level = LogLevelEnum.High, Type = ErrorTypeEnum.Unkown, LogUpLoad = LogUpLoadEnum.UpLoadWMS, Message = exception.Message };
+                Logs.Enqueue(new KeyLog { Channel = channel, Log = log, Time = DateTime.Now });
+            }
+        }
+
+        /// <summary>
+        ///  日志处理,重写改方法后请自行添加执行内容
+        ///  执行内容:LogInfo as KeyLog并添加至日志队列
+        /// </summary>
+        /// <param name="channel"></param>
+        /// <param name="logObj"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        protected override void OnLog(Channel channel, object logObj)
+        {
+            if (channel == null) return;
+            if (logObj.GetType() == typeof(string))
+            {
+                Logs.Enqueue(new KeyLog
+                {
+                    Channel = channel,
+                    Log = new LogInfo()
+                    {
+                        Level = LogLevelEnum.High,
+                        LogUpLoad = LogUpLoadEnum.UpLoadWMS,
+                        Message = logObj as string,
+                    },
+                    Time = DateTime.Now
+                });
+            }
+            else
+            {
+                var log = (LogInfo)logObj;
+                Logs.Enqueue(new KeyLog { Channel = channel, Log = log, Time = DateTime.Now });
+            }
+        }
+
+        /// <summary>
+        ///  日志处理,重写改方法后请自行添加执行内容
+        /// </summary>
+        /// <param name="channel"></param>
+        /// <param name="msg"></param>
+        /// <exception cref="NotImplementedException"></exception>
+        protected override void OnInternalLog(Channel channel, string msg)
+        {
+            var log = new LogInfo { Level = LogLevelEnum.Low, Message = msg };
+            if (msg != "开始" && msg != "结束")
+            {
+                Logs.Enqueue(new KeyLog { Channel = channel, Log = log, Time = DateTime.Now });
+            }
+        }
+
+        /// <summary>
+        ///  获取日志,重写改方法后请自行添加执行内容
+        /// </summary>
+        /// <param name="channel"></param>
+        /// <returns></returns>
+        /// <exception cref="NotImplementedException"></exception>
+        protected override IEnumerable<string> GetChannelMsg(Channel channel)
+        {
+            return Logs.Where(v => v.Channel.ToString() == channel.ToString()).Select(v => v.Log.ToString());
+        }
+    }
+}

+ 252 - 0
WCS.WorkEngineering/数据采集.cs

@@ -0,0 +1,252 @@
+using FreeRedis;
+using ServiceCenter.Extensions;
+using System.ComponentModel;
+using System.Diagnostics;
+using WCS.Core;
+using WCS.Entity.Protocol.BCR;
+using WCS.Entity.Protocol.DataStructure;
+using WCS.Entity.Protocol.Station;
+using WCS.WorkEngineering.Worlds;
+
+namespace WCS.WorkEngineering.Systems
+{
+    //[BelongTo(typeof(MainWorld))]
+    [Description("数据采集系统")]
+    public class 数据采集 : SystemBase
+    {
+        private RedisClient Redis = new RedisClient("");
+
+        public 数据采集()
+        {
+            var gs = Device.All.SelectMany(v => v.Protocols.Select(d => new { DB = $"{d.Value.DBInfo.No}:{d.Value.DBInfo.PLCInfo.IP}", d.Value.Position, TypeStr = d.Key, Dev = v }))
+                .GroupBy(v => v.DB);
+            foreach (var g in gs)
+            {
+                var min = g.OrderBy(v => v.Position).First();
+                var max = g.OrderByDescending(v => v.Position).First();
+                var t = Type.GetType(min.TypeStr);
+                min.Dev.Protocol(t, this.World);
+                max.Dev.Protocol(t, this.World);
+            }
+        }
+
+        public override List<object> GetObjects()
+        {
+            return new List<object>();
+        }
+
+        public override void Update(List<WorkTimes> list)
+        {
+            var sw = new Stopwatch();
+            sw.Start();
+            var pack = new DeviceDataPack();
+            pack.Frame = DateTime.Now;
+            var ps = pack.GetType().GetProperties().OrderBy(x => x.Name);
+            foreach (var p in ps)
+            {
+                if (!p.PropertyType.IsArray&&p.PropertyType!= typeof(IBCR80[]))
+                    continue;
+                var dev = p.PropertyType.GetElementType();
+                if (dev.GetInterfaces().Any(v => v.GetInterfaces().Any(d => d.Name == "IProtocol")))
+                {
+                    var t = p.PropertyType.GetElementType();
+                    var protType = GetProtocolType(t);
+                    var arr = Device.All.Where(v => v.HasProtocol(protType))
+                    .Select(v =>
+                    {
+                        try
+                        {
+                            var obj = Activator.CreateInstance(t);
+                            t.GetProperty("Code").SetValue(obj, v.Code);
+                            dynamic protObj = v.Protocol(protType, World);
+                            if (protType == typeof(IBCR81))
+                            {
+                                var a = new Device<IBCR81>(v, World);
+                                var b = a.Data.Content;
+
+                            }
+                            var value = ServiceCenter.Extensions.TypeExtension.Copy(protObj, t);
+                            //t.GetProperty("Data").SetValue(obj, value);
+                            return obj;
+                        }
+                        catch (Exception ex)
+                        {
+                            return null;
+                        }
+                    }).Where(v => v != null).ToArray();
+
+                    var m = typeof(Enumerable).GetMethod("OfType", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+                    m = m.MakeGenericMethod(t);
+                    var arr2 = m.Invoke(null, new object[] { arr });
+
+                    m = typeof(Enumerable).GetMethod("ToArray", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+                    m = m.MakeGenericMethod(t);
+                    var arr3 = m.Invoke(null, new object[] { arr2 });
+                    p.SetValue(pack, arr3);
+                }
+            }
+
+            //Redis.RPush("Packs", pack);
+            //if (Redis.LLen("Packs") > 50000)
+            //{
+            //    Redis.LTrim("Packs", 5000, -1);
+            //}
+
+            sw.Stop();
+            list.Add(new WorkTimes { Total = sw.ElapsedMilliseconds, Key = "采集数据" });
+            //var sw = new Stopwatch();
+            //sw.Start();
+            //var pack = new DeviceDataPack();
+            //pack.Frame = DateTime.Now;
+            //var ps = pack.GetType().GetProperties();
+            //foreach (var p in ps)
+            //{
+            //    if (!p.PropertyType.IsClass) continue;
+
+            //    var packAct = Activator.CreateInstance(p.PropertyType);
+            //    var prs = p.PropertyType.GetProperties();
+            //    foreach (var pr in prs)
+            //    {
+            //        if (!pr.PropertyType.IsArray) continue;
+
+            //        var yt = pr.PropertyType.GetElementType();
+            //        if (yt.IsClass)
+            //        {
+            //            var pros = yt.GetProperties();
+            //            //var entType = yt.GetGenericArguments()[0];
+            //            //var protType = GetProtocolType(entType);
+            //            var dataAct = Activator.CreateInstance(yt);
+            //            Parallel.ForEach(pros, pro =>
+            //            {
+            //                try
+            //                {
+            //                    if (pro.PropertyType != typeof(DateTime))
+            //                    {
+            //                        if (pro.PropertyType != typeof(string))
+            //                        {
+            //                            var protType = GetProtocolType(pro.PropertyType);
+            //                            var dev = Device.All
+            //                                .Where(v => v.HasProtocol(protType)).Select(v =>
+            //                                {
+            //                                    try
+            //                                    {
+            //                                        var obj = Activator.CreateInstance(pro.PropertyType);
+            //                                        pro.PropertyType.GetProperty("Code").SetValue(obj, v.Code);
+            //                                        var a = v.Protocol(protType, World);
+            //                                        var value = v.Protocol(protType, World).Copy(pro.PropertyType);
+            //                                        pro.SetValue(obj, value);
+            //                                        return obj;
+            //                                    }
+            //                                    catch (Exception ex)
+            //                                    {
+            //                                        return null;
+            //                                    }
+            //                                }).FirstOrDefault(v => v != null);
+
+            //                            if (dev != null)
+            //                            {
+            //                                pro.SetValue(dataAct, dev);
+            //                            }
+            //                        }
+            //                        else
+            //                        {
+            //                        }
+            //                    }
+            //                }
+            //                catch (Exception e)
+            //                {
+            //                    Console.WriteLine(e);
+            //                }
+            //            });
+
+            //            var a = 1;
+
+            //            //var m = typeof(Enumerable).GetMethod("OfType", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+            //            //m = m.MakeGenericMethod(yt);
+            //            //var arr2 = m.Invoke(null, new object[] { datasAct });
+
+            //            //m = typeof(Enumerable).GetMethod("ToArray", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+            //            //m = m.MakeGenericMethod(yt);
+            //            //var arr3 = m.Invoke(null, new object[] { arr2 });
+            //            //p.SetValue(pack, arr3);
+
+            //            //var entType = yt.GetGenericArguments()[0];
+            //            //var protType = GetProtocolType(entType);
+            //            //var arr = Device.All.Where(v => v.HasProtocol(protType))
+            //            //    .Select(v =>
+            //            //    {
+            //            //        try
+            //            //        {
+            //            //            var obj = Activator.CreateInstance(yt);
+            //            //            yt.GetProperty("Code").SetValue(obj, v.Code);
+            //            //            //var value = v.Protocol(protType, World).Copy(entType);
+            //            //            //t.GetProperty("Data").SetValue(obj, value);
+            //            //            return obj;
+            //            //        }
+            //            //        catch (Exception ex)
+            //            //        {
+            //            //            return null;
+            //            //        }
+            //            //    }).Where(v => v != null).ToArray();
+
+            //            //var m = typeof(Enumerable).GetMethod("OfType", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+            //            //m = m.MakeGenericMethod(yt);
+            //            //var arr2 = m.Invoke(null, new object[] { arr });
+
+            //            //m = typeof(Enumerable).GetMethod("ToArray", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+            //            //m = m.MakeGenericMethod(yt);
+            //            //var arr3 = m.Invoke(null, new object[] { arr2 });
+            //            //p.SetValue(pack, arr3);
+            //        }
+            //    }
+
+            //    //var t = p.PropertyType.GetElementType();
+            //    //if (t.IsGenericType)
+            //    //{
+            //    //    var entType = t.GetGenericArguments()[0];
+            //    //    var protType = GetProtocolType(entType);
+            //    //    var arr = Device.All.Where(v => v.HasProtocol(protType))
+            //    //    .Select(v =>
+            //    //    {
+            //    //        try
+            //    //        {
+            //    //            var obj = Activator.CreateInstance(t);
+            //    //            t.GetProperty("Code").SetValue(obj, v.Code);
+            //    //            //var value = v.Protocol(protType, World).Copy(entType);
+            //    //            //t.GetProperty("Data").SetValue(obj, value);
+            //    //            return obj;
+            //    //        }
+            //    //        catch (Exception ex)
+            //    //        {
+            //    //            return null;
+            //    //        }
+            //    //    }).Where(v => v != null).ToArray();
+
+            //    //    var m = typeof(Enumerable).GetMethod("OfType", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+            //    //    m = m.MakeGenericMethod(t);
+            //    //    var arr2 = m.Invoke(null, new object[] { arr });
+
+            //    //    m = typeof(Enumerable).GetMethod("ToArray", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.Public);
+            //    //    m = m.MakeGenericMethod(t);
+            //    //    var arr3 = m.Invoke(null, new object[] { arr2 });
+            //    //    p.SetValue(pack, arr3);
+            //    //}
+            //}
+
+            ////Redis.RPush("Packs", pack);
+            ////if (Redis.LLen("Packs") > 50000)
+            ////{
+            ////    Redis.LTrim("Packs", 5000, -1);
+            ////}
+
+            //sw.Stop();
+            //list.Add(new WorkTimes { Total = sw.ElapsedMilliseconds, Key = "采集数据" });
+        }
+
+        private Type GetProtocolType(Type source)
+        {
+            var t = source.GetInterfaces().FirstOrDefault(v => v.GetInterfaces().Any(d => d.Name == "IProtocol"));
+            return t;
+        }
+    }
+}

+ 51 - 0
时效库数据处理.sln

@@ -0,0 +1,51 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.8.34525.116
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "解决方案项", "解决方案项", "{8E0E232A-9D38-41A9-AE2B-6B0B84EE6745}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.Service", "WCS.Service\WCS.Service.csproj", "{F9A52668-487E-46A0-9D72-E34077E870A4}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.WorkEngineering", "WCS.WorkEngineering\WCS.WorkEngineering.csproj", "{F0C3024E-060C-4F13-B9B7-4833EBED07B1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlSugar", "..\..\..\..\Code\SqlSugar\Src\Asp.NetCore2\SqlSugar\SqlSugar.csproj", "{C561B818-CED4-4DF0-B4E5-F8D83A87109E}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceCenter", "..\ZTGT-FJZX\ServiceCenter\ServiceCenter.csproj", "{6F57617F-A8F0-44A7-A2F8-7051D9F9CBC2}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WCS.Entity.Protocol", "..\ZTGT-SX\业务工程\时效库\WCS.Entity.Protocol\WCS.Entity.Protocol.csproj", "{CF7F40C7-5D4C-4887-A5F6-22388D66C25E}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{F9A52668-487E-46A0-9D72-E34077E870A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F9A52668-487E-46A0-9D72-E34077E870A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F9A52668-487E-46A0-9D72-E34077E870A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F9A52668-487E-46A0-9D72-E34077E870A4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{F0C3024E-060C-4F13-B9B7-4833EBED07B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{F0C3024E-060C-4F13-B9B7-4833EBED07B1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{F0C3024E-060C-4F13-B9B7-4833EBED07B1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{F0C3024E-060C-4F13-B9B7-4833EBED07B1}.Release|Any CPU.Build.0 = Release|Any CPU
+		{C561B818-CED4-4DF0-B4E5-F8D83A87109E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{C561B818-CED4-4DF0-B4E5-F8D83A87109E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{C561B818-CED4-4DF0-B4E5-F8D83A87109E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{C561B818-CED4-4DF0-B4E5-F8D83A87109E}.Release|Any CPU.Build.0 = Release|Any CPU
+		{6F57617F-A8F0-44A7-A2F8-7051D9F9CBC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{6F57617F-A8F0-44A7-A2F8-7051D9F9CBC2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{6F57617F-A8F0-44A7-A2F8-7051D9F9CBC2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{6F57617F-A8F0-44A7-A2F8-7051D9F9CBC2}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CF7F40C7-5D4C-4887-A5F6-22388D66C25E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CF7F40C7-5D4C-4887-A5F6-22388D66C25E}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CF7F40C7-5D4C-4887-A5F6-22388D66C25E}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CF7F40C7-5D4C-4887-A5F6-22388D66C25E}.Release|Any CPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {298CA2CB-433C-4A38-B55E-BB1792EC23FD}
+	EndGlobalSection
+EndGlobal