林豪 左 3 سال پیش
والد
کامیت
67fff91df2

+ 14 - 0
WCS Pedestal.sln

@@ -19,6 +19,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.Core", "WCS.Core\WCS.Co
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.Entity.Protocol", "WCS.Entity.Protocol\WCS.Entity.Protocol.csproj", "{300F18EA-128F-45EE-AE54-51AE8D2E0B57}"
 EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.Service", "WCS.Service\WCS.Service.csproj", "{54AF53DB-5A76-48D4-BD5F-F79D123DE1CB}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WCS.Virtual_PLC", "WCS.Virtual_PLC\WCS.Virtual_PLC.csproj", "{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
@@ -49,6 +53,14 @@ Global
 		{300F18EA-128F-45EE-AE54-51AE8D2E0B57}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{300F18EA-128F-45EE-AE54-51AE8D2E0B57}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{300F18EA-128F-45EE-AE54-51AE8D2E0B57}.Release|Any CPU.Build.0 = Release|Any CPU
+		{54AF53DB-5A76-48D4-BD5F-F79D123DE1CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{54AF53DB-5A76-48D4-BD5F-F79D123DE1CB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{54AF53DB-5A76-48D4-BD5F-F79D123DE1CB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{54AF53DB-5A76-48D4-BD5F-F79D123DE1CB}.Release|Any CPU.Build.0 = Release|Any CPU
+		{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -60,6 +72,8 @@ Global
 		{E20F0C8A-619A-4D78-835C-909E81338458} = {43005E7B-7FC3-407D-B098-E58D0B5B465F}
 		{17CF21B9-84AC-4FB3-9BC1-EF96CF8381CA} = {43005E7B-7FC3-407D-B098-E58D0B5B465F}
 		{300F18EA-128F-45EE-AE54-51AE8D2E0B57} = {9D01B749-E4C4-4AD4-82E1-5C3CA65295E5}
+		{54AF53DB-5A76-48D4-BD5F-F79D123DE1CB} = {9D01B749-E4C4-4AD4-82E1-5C3CA65295E5}
+		{A8557AE8-BDBE-4508-AA58-C7E7F90EFDD2} = {43005E7B-7FC3-407D-B098-E58D0B5B465F}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {BF8ACC41-E6AD-46E2-8A85-2BD2AE0990C0}

+ 2 - 0
WCS.Core/Properties/PublishProfiles/FolderProfile.pubxml

@@ -8,5 +8,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
     <Platform>Any CPU</Platform>
     <PublishDir>D:\XM\Release\DLL</PublishDir>
     <PublishProtocol>FileSystem</PublishProtocol>
+    <VersionPrefix>1.0.0</VersionPrefix>
+    <describe>第一版</describe>
   </PropertyGroup>
 </Project>

+ 1 - 0
WCS.Core/WCS.Core.csproj

@@ -3,6 +3,7 @@
   <PropertyGroup>
     <TargetFramework>netstandard2.1</TargetFramework>
     <Nullable>enable</Nullable>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
   </PropertyGroup>
 
   <ItemGroup>

+ 20 - 0
WCS.Service/AppSettings.cs

@@ -0,0 +1,20 @@
+using Microsoft.Extensions.Configuration;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace WCS.Service
+{
+    public class AppSettings
+    {
+        public static IConfiguration Config { get; private set; }
+        static AppSettings()
+        {
+            Config = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json").Build(); 
+        } 
+      
+    }
+}

+ 40 - 0
WCS.Service/DeviceExtentions.cs

@@ -0,0 +1,40 @@
+using DBHelper_SqlSugar;
+using System.Linq;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+using WCS.Entity.Protocol.Station;
+
+namespace WCS.Service
+{
+    public static class DeviceExtentions
+    {
+    }
+
+    public static class TaskExtentions
+    {
+        public static string AddrCurrent(this WCS_TASK source, Db db)
+        {
+            var q1 = db.Default.Queryable<WCS_Station520>().Where(v => v.ISLAST && v.Tasknum == source.ID).Select(v => new TemporaryModel
+            {
+                TYPE = 1,
+                CODE = v.DEVICE.CODE
+            });
+            var q2 = db.Default.Queryable<WCS_RGV521>().Where(v => v.ISLAST && (v.TaskID_1 == source.ID || v.TaskID_2 == source.ID)).Select(v => new TemporaryModel
+            {
+                TYPE = 2,
+                CODE = v.DEVICE.CODE
+            });
+            var q = db.Default.UnionAll(q1, q2);
+
+            var res = q.OrderBy(v => v.TYPE).First();
+            return res == null ? "" : res.CODE;
+        }
+    }
+
+    public class TemporaryModel
+    {
+        public int TYPE { get; set; }
+
+        public string CODE { get; set; }
+    }
+}

+ 32 - 0
WCS.Service/PLCAccessors/AllenBradleyPLC.cs

@@ -0,0 +1,32 @@
+using HslCommunication.Profinet.AllenBradley;
+using System;
+
+namespace WCS.Service.PLCAccessors
+{
+    public class AllenBradleyPLC : AllenBradleyNet
+    {
+        public AllenBradleyNet plc;
+
+        public AllenBradleyPLC(string ipAddress)
+        {
+            plc = new AllenBradleyNet(ipAddress);
+        }
+
+        public byte[] ReadBytes(ushort db, ushort address, ushort length)
+        {
+            var addr = "DB" + db + "." + address;
+            var res = plc.Read(addr, length);
+            if (res.IsSuccess)
+                return res.Content;
+            throw new Exception("读取PLC数据失败:" + res.Message);
+        }
+
+        public void WriteBytes(ushort db, ushort address, byte[] data)
+        {
+            var start = db + address / 2;
+            var res = plc.Write("D" + start, data);
+            if (!res.IsSuccess)
+                throw new Exception("写入PLC数据失败:" + res.Message);
+        }
+    }
+}

+ 36 - 0
WCS.Service/PLCAccessors/HuiChuangPLC.cs

@@ -0,0 +1,36 @@
+using HslCommunication.Profinet.AllenBradley;
+using System;
+
+namespace WCS.Service.PLCAccessors
+{
+    /// <summary>
+    /// 汇川
+    /// TODO 具体内容需要重构,暂时使用AB基类
+    /// </summary>
+    public class HuiChuangPLC : AllenBradleyNet
+    {
+        public AllenBradleyNet plc;
+
+        public HuiChuangPLC(string ipAddress)
+        {
+            plc = new AllenBradleyNet(ipAddress);
+        }
+
+        public byte[] ReadBytes(ushort db, ushort address, ushort length)
+        {
+            var addr = "DB" + db + "." + address;
+            var res = plc.Read(addr, length);
+            if (res.IsSuccess)
+                return res.Content;
+            throw new Exception("读取PLC数据失败:" + res.Message);
+        }
+
+        public void WriteBytes(ushort db, ushort address, byte[] data)
+        {
+            var start = db + address / 2;
+            var res = plc.Write("D" + start, data);
+            if (!res.IsSuccess)
+                throw new Exception("写入PLC数据失败:" + res.Message);
+        }
+    }
+}

+ 33 - 0
WCS.Service/PLCAccessors/MelsecPLC.cs

@@ -0,0 +1,33 @@
+using HslCommunication.Profinet.Melsec;
+using System;
+
+namespace WCS.Service.PLCAccessors
+{
+    public class MelsecPLC : MelsecMcNet
+    {
+        private MelsecMcNet plc;
+
+        public MelsecPLC(string ip, int port)
+        {
+            plc = new MelsecMcNet(ip, port);
+            plc.ConnectTimeOut = 3000;
+            plc.ConnectServer();
+        }
+
+        public byte[] ReadBytes(ushort db, ushort address, ushort length)
+        {
+            var res = plc.Read("D" + db, length);
+            if (res.IsSuccess)
+                return res.Content;
+            throw new Exception("读取PLC数据失败:" + res.Message);
+        }
+
+        public void WriteBytes(ushort db, ushort address, byte[] data)
+        {
+            var start = db + address / 2;
+            var res = plc.Write("D" + start, data);
+            if (!res.IsSuccess)
+                throw new Exception("写入PLC数据失败:" + res.Message);
+        }
+    }
+}

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

@@ -0,0 +1,22 @@
+using HslCommunication.Core;
+using System;
+using WCS.Core.DataTrans;
+using WCS.Entity;
+
+namespace WCS.Service.PLCAccessors
+{
+    public class PlcAccessorsCreater : IPlcAccessorCreater
+    {
+        public IReadWriteNet Create(WCS_PLC data)
+        {
+            return data.TYPE switch
+            {
+                PLCType.西门子 => new SiemensS7PLC((int)data.MODEL, data.IP),
+                PLCType.三菱 => new MelsecPLC(data.IP, data.PORT),
+                PLCType.AB => new AllenBradleyPLC(data.IP),
+                PLCType.汇川 => new HuiChuangPLC(data.IP),
+                _ => throw new Exception("不支持此PLC")
+            };
+        }
+    }
+}

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

@@ -0,0 +1,54 @@
+using HslCommunication.Profinet.Siemens;
+using Virtual_PLC;
+using WCS.Core;
+using WCS.Core.DataTrans;
+
+namespace WCS.Service.PLCAccessors
+{
+    public class SiemensS7PLC : SiemensS7Net, IPlcAccessor
+    {
+        private SiemensS7Net plc;
+
+        public SiemensS7PLC(SiemensPLCS type) : base(type)
+        {
+        }
+
+        public SiemensS7PLC(SiemensPLCS type, string ip) : base(type, ip)
+        {
+            plc = new SiemensS7Net(type, ip);
+        }
+
+        public SiemensS7PLC(int type, string ip) : base((SiemensPLCS)type, ip)
+        {
+            plc = new SiemensS7Net((SiemensPLCS)type, ip);
+        }
+
+        public byte[] ReadBytes(ushort db, ushort address, ushort length, ushort dataLength)
+        {
+            if (Configs.Any(SystemMode.虚拟PLC))
+            {
+                return PlcData.Read(new PLCData { IP = plc.IpAddress, DB = db, Length = length, DataLength = dataLength });
+            }
+
+            var addr = "DB" + db + "." + address;
+            var res = plc.Read(addr, length);
+            if (res.IsSuccess)
+                return res.Content;
+            throw new WarnException("读取PLC数据失败:" + res.Message);
+        }
+
+        public void WriteBytes(ushort db, ushort address, byte[] data)
+        {
+            if (Configs.Any(SystemMode.虚拟PLC))
+            {
+                PlcData.Write(new PLCData { IP = plc.IpAddress, DB = db }, address, data);
+            }
+            else
+            {
+                var res = plc.Write("D" + address, data);
+                if (!res.IsSuccess)
+                    throw new WarnException("写入PLC数据失败:" + res.Message);
+            }
+        }
+    }
+}

+ 68 - 0
WCS.Service/Program.cs

@@ -0,0 +1,68 @@
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using System;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using WCS.Core;
+
+namespace WCS.Service
+{
+    public class Program
+    {
+        /// <summary>
+        /// 程序入口
+        /// </summary>
+        /// <param name="args"></param>
+        public static void Main(string[] args)
+        {
+            using var mt = new Mutex(true, "WCS");
+            if (mt.WaitOne())
+            {
+                Configs.AddSystemMode(SystemMode.虚拟PLC);
+                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)
+            {
+                return Host.CreateDefaultBuilder(args)
+                    .UseWindowsService()//win
+                    .ConfigureWebHostDefaults(web => //网络访问配置
+                    {
+                        web.UseUrls("http://*:8080"); //设备访问端口
+                        web.UseStartup<Startup>(); //调用启动服务
+                    })
+                    .ConfigureServices((_, services) =>
+                    {
+                        //services.AddDbContext<WCSDB>();
+                        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"
+      }
+    }
+  }
+}

+ 215 - 0
WCS.Service/ProtocolProxy.cs

@@ -0,0 +1,215 @@
+using DBHelper_SqlSugar;
+using FreeRedis;
+using MessagePack;
+using MessagePack.Resolvers;
+using SqlSugar;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Linq.Dynamic.Core;
+using System.Reflection;
+using WCS.Core;
+using WCS.Core.DataTrans;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+using WCS.Service.Extensions;
+
+namespace WCS.Service
+{
+    public class ProtocolProxy : ProtocolProxyBase
+    {
+        private readonly MethodInfo _setMethod;
+
+        public ProtocolProxy(string id, WCS_DATABLOCK db, ushort start, WCS_DEVICEPROTOCOL protocol) : base(id, db, start, protocol)
+        {
+            _setMethod = typeof(SqlSugarScope).GetMethod("Queryable", new Type[] { })?.MakeGenericMethod(this.ProtocolDataType);
+        }
+
+        private static readonly ConcurrentDictionary<Type, object[]> LastDatas = new();
+
+        protected override WCS_PROTOCOLDATA GetLastData(Db db)
+        {
+            if (!LastDatas.ContainsKey(this.ProtocolDataType))
+            {
+                LastDatas[this.ProtocolDataType] = db.Default.Queryable<dynamic>().AS($"{this.ProtocolDataType.Name}", "it").Where("ISLAST=0").OrderBy("ID").ToArray();
+            }
+            dynamic datas = LastDatas[this.ProtocolDataType];
+            var list = new List<WCS_PROTOCOLDATA>();
+            foreach (var data in datas)
+            {
+                if (data.DEVICECOD == Protocol.DEVICE.CODE)
+                    list.Add(data);
+            }
+
+            if (list.Count > 1)
+            {
+                for (var i = 0; i < list.Count - 1; i++)
+                {
+                    var obj = list[i];
+                    db.Default.Insertable((object)obj).ExecuteCommand();
+                    obj.ISLAST = false;
+                }
+            }
+            var res = list.LastOrDefault();
+            return res;
+        }
+
+        private static int _total;
+
+        protected override WCS_PROTOCOLDATA SaveNewData(Db db, WCS_PROTOCOLDATA last, WCS_PROTOCOLDATA newobj, string user)
+        {
+            _total++;
+
+            if (last != null)
+            {
+                var dc = TypeExtension.EntityClassToDictionary((object)last);
+                db.Default.Insertable(dc).AS($"{last.GetType().Name}").ExecuteCommand();
+                last.ISLAST = false;
+            }
+
+            newobj.DEVICECOD = Protocol.DEVICE.CODE;
+            newobj.ISLAST = true;
+            newobj.UPDATETIME = DateTime.Now;
+            newobj.UPDATEUSER = user;
+            newobj.FRAME = LogicHandler.Frame;
+            var typeName = newobj.GetType().Name;
+
+            var dc1 = TypeExtension.EntityClassToDictionary((object)newobj);
+            db.Default.Insertable(dc1).AS($"{typeName}").ExecuteCommand();
+            return newobj;
+        }
+
+        private static readonly RedisClient Redis;
+        public static RedisClient ConcurrencyControlRedis;
+        public static RedisClient Ygwms150Redis;
+        public static RedisClient Ygwcs150Redis;
+
+        static ProtocolProxy()
+        {
+            MessagePackSerializer.DefaultOptions = StandardResolver.Options.WithCompression(MessagePackCompression.Lz4Block);
+            Redis = new RedisClient("127.0.0.1,database=10");
+            Redis.Serialize = obj =>
+            {
+                var bytes = MessagePackSerializer.Serialize(obj);
+                return bytes;
+            };
+            Redis.DeserializeRaw = (bytes, type) =>
+            {
+                var obj = MessagePackSerializer.Deserialize(type, bytes);
+                return obj;
+            };
+            ConcurrencyControlRedis = new RedisClient("127.0.0.1,database=1");
+        }
+
+        public override void Publish(string code, WCS_PROTOCOLDATA obj)
+        {
+            try
+            {
+                var datas = AllDatas;
+                if (code.StartsWith("SRM"))
+                {
+                    if (!datas.ContainsKey(code))
+                        datas[code] = new SCData { Code = code };
+                }
+                else if (code.StartsWith("RGV"))
+                {
+                    if (!datas.ContainsKey(code))
+                        datas[code] = new RGVData { Code = code };
+                }
+                else if (code == "Robot")
+                {
+                    if (!datas.ContainsKey(code))
+                        datas[code] = new RobotData { Code = code };
+                }
+                else if (code.Length == 4)
+                {
+                    if (!datas.ContainsKey(code))
+                        datas[code] = new StationData { Code = code };
+                }
+
+                if (!datas.ContainsKey(code)) return;
+                var data = datas[code];
+                data.Frame = LogicHandler.Frame;
+                var p = data.GetType().GetProperties().FirstOrDefault(v => v.PropertyType == obj.GetType());
+                if (p == null)
+                {
+                    Console.WriteLine("类型" + data.GetType().Name + "不包含类型为" + obj.GetType().Name + "的属性");
+                }
+                else
+                {
+                    p.SetValue(data, obj);
+                }
+            }
+            catch (Exception ex)
+            {
+                Console.WriteLine(ex.Message);
+            }
+        }
+
+        public static ConcurrentDictionary<string, DeviceData> AllDatas = new();
+
+        public static void Do()
+        {
+            Console.ForegroundColor = ConsoleColor.Green;
+            Console.WriteLine($"更改:{_total}");
+            Console.ResetColor();
+            _total = 0;
+            try
+            {
+                var gs = AllDatas.GroupBy(v => v.Value.GetType());
+
+                var pack = new DeviceDataPack
+                {
+                    Frame = LogicHandler.Frame
+                };
+                foreach (var g in gs)
+                {
+                    var value = g.Select(v => v.Value).ToArray();
+                    var etype = g.Key;
+                    var type = typeof(DeviceDataCollection<>).MakeGenericType(etype);
+                    var coll = Activator.CreateInstance(type, LogicHandler.Frame, value);
+                    var p = pack.GetType().GetProperties().First(v => v.PropertyType == type);
+                    p.SetValue(pack, coll);
+                }
+
+                var sw = new Stopwatch();
+                sw.Start();
+                Redis.Set(nameof(DeviceDataPack), pack);
+                sw.Stop();
+                Console.ForegroundColor = ConsoleColor.Blue;
+                Console.WriteLine($"Redis耗时{sw.ElapsedMilliseconds}");
+                Console.ResetColor();
+
+                Redis.RPush("Packs", pack);
+
+                var len = Redis.LLen("Packs");
+                if (len > 150000)
+                {
+                    Redis.LTrim("Packs", 20000, len);
+                }
+
+                foreach (var data in AllDatas)
+                {
+                    data.Value.Info = "";
+                    if (data.Value is not ProdLineData pld) continue;
+                    pld.TaskList.Clear();
+                    pld.Frame = LogicHandler.Frame;
+                    pld.Code = data.Key;
+                }
+            }
+            catch (Exception)
+            {
+                // ignored
+            }
+        }
+    }
+
+    public class PackInfo
+    {
+        public DateTime Frame { get; set; }
+
+        public byte[] Data { get; set; }
+    }
+}

+ 39 - 0
WCS.Service/Uploader.cs

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

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

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk.Worker">
+
+  <PropertyGroup>
+    <TargetFramework>net6.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <UserSecretsId>dotnet-WCS.Service-C558B94D-CBEC-44E4-A78C-24BD73E5FF73</UserSecretsId>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Microsoft.Extensions.Hosting" Version="6.0.1" />
+    <PackageReference Include="WCS.Core" Version="1.0.0" />
+    <PackageReference Include="WCS.Entity.Protocol" Version="1.0.0" />
+  </ItemGroup>
+</Project>

+ 217 - 0
WCS.Service/Worker.cs

@@ -0,0 +1,217 @@
+using DBHelper_SqlSugar;
+using Logs;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Hosting;
+using Microsoft.Extensions.Logging;
+using Newtonsoft.Json;
+using SqlSugar;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using Virtual_PLC;
+using WCS.Core;
+using WCS.Core.DataTrans;
+using WCS.Entity;
+using WCS.Entity.Protocol;
+using WCS.Entity.Protocol.BCR;
+using WCS.Entity.Protocol.Station;
+using WCS.Service.Extensions;
+
+namespace WCS.Service
+{
+    public class Worker : BackgroundService
+    {
+        private readonly ILogger<Worker> _logger;
+
+        public Worker(ILogger<Worker> logger)
+        {
+            _logger = logger;
+        }
+
+        protected override async System.Threading.Tasks.Task ExecuteAsync(CancellationToken stoppingToken)
+        {
+            if (stoppingToken.IsCancellationRequested)
+                return;
+
+            #region 启用日志
+
+            var logConfig = JsonConvert.DeserializeObject<LogConfig>(await File.ReadAllTextAsync("config.json", Encoding.Default, stoppingToken));
+            LogHelper.SetConfigInfo(logConfig!);
+
+            #endregion 启用日志
+
+            InfoLog.INFO_INIT("1111");
+
+            _logger.LogInformation("WCS开始启动");
+            //InfoLog.INFO_INIT("WCS开始启动");
+            Configs.DebugRedisUrl = "127.0.0.1";
+            Configs.ProtocolProxyBaseType = typeof(ProtocolProxy);
+            Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
+            Configs.StringEncoding = Encoding.UTF8;
+
+            Db.CreateContext(new ConnectionConfig()
+            {
+                ConnectionString = AppSettings.Config.GetConnectionString("WCSDB"),
+                DbType = DbType.SqlServer
+            }, "WCSDB");
+            Db.SetDefaultDbContextType("WCSDB");
+            Db.Do(db =>
+            {
+                //TODO:DbMaintenance.CreateDatabase()并没起到作用,如果没有对应的数据库的话任然需要手动新建一个
+                db.Default.DbMaintenance.CreateDatabase();
+                db.Default.CodeFirst.InitTables(typeof(WCS_CMD));
+                db.Default.CodeFirst.InitTables(typeof(WCS_PLC));
+                db.Default.CodeFirst.InitTables(typeof(WCS_DATABLOCK));
+                db.Default.CodeFirst.InitTables(typeof(WCS_DEVICE));
+                db.Default.CodeFirst.InitTables(typeof(WCS_PATH));
+                db.Default.CodeFirst.InitTables(typeof(WCS_PATHPOINT));
+                db.Default.CodeFirst.InitTables(typeof(WCS_ROUTE));
+                db.Default.CodeFirst.InitTables(typeof(WCS_TASK));
+                db.Default.CodeFirst.InitTables(typeof(WCS_TASK_OLD));
+                db.Default.CodeFirst.InitTables(typeof(WCS_EXCEPTION));
+                db.Default.CodeFirst.InitTables(typeof(WCS_SystemConfig));
+                db.Default.CodeFirst.InitTables(typeof(WCS_AGVTask));
+                db.Default.CodeFirst.InitTables(typeof(WCS_DEVICEPROTOCOL));
+                db.Default.CodeFirst.InitTables(typeof(WCS_GROUPMEMBER));
+                db.Default.CodeFirst.InitTables(typeof(WCS_MAPPINGENTRY));
+                db.Default.CodeFirst.InitTables(typeof(WCS_USERS));
+                db.Default.CodeFirst.InitTables(typeof(WCS_StatusLog));
+                db.Default.CodeFirst.InitTables(typeof(WCS_BCR80));
+                db.Default.CodeFirst.InitTables(typeof(WCS_RGV520));
+                db.Default.CodeFirst.InitTables(typeof(WCS_RGV521));
+                db.Default.CodeFirst.InitTables(typeof(WCS_RGV523));
+                db.Default.CodeFirst.InitTables(typeof(WCS_SRM520));
+                db.Default.CodeFirst.InitTables(typeof(WCS_SRM521));
+                db.Default.CodeFirst.InitTables(typeof(WCS_SRM537));
+                db.Default.CodeFirst.InitTables(typeof(WCS_Station520));
+                db.Default.CodeFirst.InitTables(typeof(WCS_Station521));
+                db.Default.CodeFirst.InitTables(typeof(WCS_Station523));
+            });
+
+            //从现有结构解析出需要的结构
+            List<PLCData> list = new List<PLCData>();
+            Db.Do(db =>
+            {
+                var dataBlocks = db.Default.Queryable<WCS_DATABLOCK>().Includes(v => v.PLC).ToList();
+                foreach (var dataBlock in dataBlocks)
+                {
+                    list.Add(new PLCData()
+                    {
+                        IP = dataBlock.PLC.IP,
+                        DB = dataBlock.NO,
+                        Length = dataBlock.LENGTH,
+                        DataLength = dataBlock.DATALENGTH,
+                    });
+                }
+            });
+
+            PlcData.Init("127.0.0.1,database=0").InitPlcData(list);
+            //日志发布事件s
+            Configs.PublishEvent += () =>
+            {
+                WMS.UploadDevInfo();
+                ProtocolProxy.Do();
+            };
+            //异常上抛
+            Configs.UploadException = (d, s) =>
+            {
+                if (s == "接口调用中") return;
+                if (ProtocolProxy.AllDatas.ContainsKey(d))
+                {
+                    ProtocolProxy.AllDatas[d].Info = s;
+                    ProtocolProxy.AllDatas[d].Frame = LogicHandler.Frame;
+                }
+                WMS.TaskException(d, s);
+            };
+
+            //LogicHandler.DbLog = Helpers.LogHelper.AddWCS_EXCEPTION;
+
+            PlcAccessor.Creater = new PLCAccessors.PlcAccessorsCreater();
+            try
+            {
+                Db.Do(db =>
+                {
+                    var items = db.Default.Queryable<WCS_DEVICEPROTOCOL>()
+                    .Includes(d => d.DEVICE, r => r.ROUTES)
+                    .Includes(d => d.DEVICE, p => p.PATHS)
+                    .Includes(d => d.DB, p => p.PLC).ToArray();
+                    items.Select(v => v.Data()).ToArray();
+                    LogicHandler.AllObjects.AddRange(items);
+
+                    var devices = db.Default.Queryable<WCS_DEVICE>()
+                                            .Includes(v => v.ROUTES)
+                                            .Includes(v => v.PATHS)
+                                            .Includes(v => v.DEVICEGROUP, g => g.GROUP)
+                                            .AsNavQueryable()
+                                            .Includes(v => v.DEVICEGROUP, g => g.MEMBER, d => d.DEVICEPROTOCOLS, p => p.DB, c => c.PLC)
+                                            .Includes(v => v.DEVICEGROUP, g => g.MEMBER, d => d.DEVICEPROTOCOLS, p => p.DEVICE)
+                                            .Includes(v => v.DEVICEPROTOCOLS, p => p.DB, c => c.PLC)
+                                            .Includes(v => v.DEVICEPROTOCOLS, p => p.DEVICE)
+                                            .ToList();
+                    LogicHandler.AllObjects.AddRange(devices);
+                });
+
+                #region 设备扩展数据配置
+
+                WCS_DEVICEExtension.AddFlag(DF.一楼RGV放货, "G1035", "G1044", "G1053", "G1062");
+                WCS_DEVICEExtension.AddFlag(DF.SRM, "SRM1", "SRM2", "SRM3", "SRM4", "SRM5", "SRM6", "SRM7", "SRM8");
+                WCS_DEVICEExtension.AddFlag(DF.月台, "G1469", "G1561", "G1538", "G1574", "G1509");
+                WCS_DEVICEExtension.AddFlag(DF.SRM二级品取货, "1040", "1041", "1042", "1043", "1049", "1050", "1051", "1052");
+                WCS_DEVICEExtension.AddFlag(DF.SRM二级品取货, "1058", "1059", "1060", "1061", "1067", "1068");
+                WCS_DEVICEExtension.AddFlag(DF.SRMBOPP取货, "1195", "1194", "1193", "1192", "1204", "1203", "1202", "1201");
+                WCS_DEVICEExtension.AddFlag(DF.SRMBOPP取货, "1213", "1212", "1210", "1211", "1220", "1219", "1230", "1228");
+                WCS_DEVICEExtension.AddFlag(DF.SRM月台放货, "1473", "1476", "1474", "1475", "1491", "1492", "1493", "1494");
+                WCS_DEVICEExtension.AddFlag(DF.SRM月台放货, "1520", "1521", "1522", "1523", "1545", "1546", "1451", "1453");
+                WCS_DEVICEExtension.AddFlag(DF.SRM涂布取货, "1431", "1432", "1422", "1423", "1424", "1425", "1415", "1416");
+                WCS_DEVICEExtension.AddFlag(DF.SRM涂布取货, "1605", "1606", "1406", "1407", "1408", "1409");
+                WCS_DEVICEExtension.AddFlag(DF.SRM涂布放货, "1283", "1284", "1290", "1291", "1292", "1293", "1299", "1300");
+                WCS_DEVICEExtension.AddFlag(DF.SRM涂布放货, "1301", "1302", "1308", "1309", "1310", "1311");
+                WCS_DEVICEExtension.AddFlag(DF.涂布RGV, "RGV9", "RGV10", "RGV11", "RGV12", "RGV13", "RGV14");
+                WCS_DEVICEExtension.AddFlag(DF.涂布RGV取货设备组, "G2", "G3", "G5", "G7", "G9", "G11");
+                WCS_DEVICEExtension.AddFlag(DF.涂布RGV放货设备组, "G1", "G4", "G6", "G8", "G10");
+                WCS_DEVICEExtension.AddFlag(DF.涂布出库RGV取货站台, "1285", "1286", "1294", "1295", "1303", "1304", "1312", "1313");
+                WCS_DEVICEExtension.AddFlag(DF.涂布入库RGV取货站台, "1391", "1392", "1399", "1400");
+                WCS_DEVICEExtension.AddFlag(DF.涂布RGV取货站台, "1285", "1286", "1294", "1295", "1303", "1304", "1312", "1313");
+                WCS_DEVICEExtension.AddFlag(DF.涂布RGV取货站台, "1391", "1392", "1399", "1400");
+                WCS_DEVICEExtension.AddFlag(DF.BOPPRGV, "RGV1", "RGV2", "RGV3", "RGV4", "RGV5", "RGV6", "RGV7");
+                WCS_DEVICEExtension.AddFlag(DF.BOPPRGV取货设备组, "G19", "G23");
+                WCS_DEVICEExtension.AddFlag(DF.BOPPRGV放货设备组, "G12", "G13", "G14", "G15", "G16");
+
+                #endregion 设备扩展数据配置
+
+                #region 启用所有的逻辑处理器
+
+                var managerTypes = Assembly.GetExecutingAssembly().GetTypes().Where(v => v.IsSubclassOf(typeof(LogicHandler)) && !v.IsGenericType && !v.IsAbstract).ToArray();
+                foreach (var type in managerTypes)
+                {
+                    var m = Activator.CreateInstance(type);
+                    LogicHandler.AddManager(m as LogicHandler);
+                }
+
+                LogicHandler.StartAll();
+
+                #endregion 启用所有的逻辑处理器
+
+                _logger.LogInformation("WCS启动成功");
+                //InfoLog.INFO_INIT("WCS启动成功");
+            }
+            catch (Exception ex)
+            {
+                _logger.LogError("WCS启动失败{0}", ex.Message);
+                //InfoLog.INFO_INIT($"WCS启动失败{ex.Message}");
+            }
+        }
+
+        public override System.Threading.Tasks.Task StopAsync(CancellationToken cancellationToken)
+        {
+            _logger.LogError("WCS关闭");
+            //InfoLog.INFO_INIT("WCS关闭");
+            LogicHandler.StopAll();
+            return base.StopAsync(cancellationToken);
+        }
+    }
+}

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

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

+ 17 - 0
WCS.Service/appsettings.json

@@ -0,0 +1,17 @@
+{
+  "Logging": {
+    "LogLevel": {
+      "Default": "Information",
+      "Microsoft": "Warning",
+      "Microsoft.Hosting.Lifetime": "Information"
+    }
+  },
+  "ConnectionStrings": {
+    //"WCSDB": "Data Source=192.168.251.12;User ID=sa;Password=abc.123;Initial Catalog=WCS_MW;Integrated Security=False"
+    //"WCSDB": "Data Source=192.168.249.120;User ID=sa;Password=password@123$%^;Initial Catalog=WCS_MW;Integrated Security=False;"
+    //本地数据库
+    "WCSDB": "Data Source=LAPTOP-D9OEIS1K\\SQLEXPRESS;User ID=sa;Password=7166766;Initial Catalog=wcs;Integrated Security=False;"
+    //MySql
+    //"WCSDB": "server=localhost;Database=wcs;Uid=root;Pwd=123456;AllowLoadLocalInfile=true"
+  }
+}

+ 56 - 0
WCS.Service/config.json

@@ -0,0 +1,56 @@
+{
+  "LogPath": "D:\\WCSLog\\", // ��־Ŀ¼
+  "LogDays": 30,
+  "LogSize": 100,
+  "Logs": [
+    {
+      "Name": "Info",
+      "FileName": "D:\\WCSLog\\",
+      "SubLogNames": {
+        "INFO_INIT": "Info_Init",
+        "INFO_SRMALARM": "Info_SrmAlarm",
+        "INFO_INFO": "Info_Info",
+        "INFO_ERROR": "Info_Error",
+        "INFO_SRMINFO": "Info_SRMInfo",
+        "INFO_SYTASKSTATUS": "Info_SyTaskTatus",
+        "INFO_WMSREQUEST": "INFO_WMSRequest",
+        "INFO_WARN": "Info_Warn",
+        "INFO_PLCREADLOG": "Info_PlcReadLog",
+        "INFO_TIMING": "Info_Timing",
+        "INFO_UPEX": "INFO_I_WCS_GetExcTask",
+        "INFO_CREATETASKIN": "CreateTaskIn",
+        "INFO_RGVINFO": "RgvInfo",
+        "INFO_AGV": "info_agv"
+      }
+    },
+
+    {
+      "Name": "PLC",
+      "FileName": "D:\\WCSLog\\",
+      "SubLogNames": {
+        "PLC_LINKS": "PLC_Links"
+      }
+    },
+    {
+      "Name": "Error",
+      "FileName": "D:\\WCSLog\\",
+      "SubLogNames": {
+      }
+    },
+    {
+      "Name": "Warn",
+      "FileName": "D:\\WCSLog\\",
+      "SubLogNames": {
+      }
+    },
+    {
+      "Name": "Db",
+      "FileName": "D:\\WCSLog\\",
+      "SubLogNames": {
+        "DB_EX": "Db_Ex",
+        "DB_CLEAN": "Db_Clean",
+        "INFO_PLCREADLOG": "Info_PlcReadLog"
+      }
+    }
+  ]
+}

+ 275 - 0
WCS.Service/log4net.config

@@ -0,0 +1,275 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration>
+  <log4net>
+    <appender name="Info_InitAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_Init.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_Init">
+      <level value="ALL" />
+      <appender-ref ref="Info_InitAppender" />
+    </logger>
+    <appender name="Info_SrmAlarmAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_SrmAlarm.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_SrmAlarm">
+      <level value="ALL" />
+      <appender-ref ref="Info_SrmAlarmAppender" />
+    </logger>
+    <appender name="Info_InfoAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_Info.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_Info">
+      <level value="ALL" />
+      <appender-ref ref="Info_InfoAppender" />
+    </logger>
+    <appender name="Info_ErrorAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_Error.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_Error">
+      <level value="ALL" />
+      <appender-ref ref="Info_ErrorAppender" />
+    </logger>
+    <appender name="Info_SRMInfoAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_SRMInfo.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_SRMInfo">
+      <level value="ALL" />
+      <appender-ref ref="Info_SRMInfoAppender" />
+    </logger>
+    <appender name="Info_SyTaskTatusAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_SyTaskTatus.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_SyTaskTatus">
+      <level value="ALL" />
+      <appender-ref ref="Info_SyTaskTatusAppender" />
+    </logger>
+    <appender name="INFO_WMSRequestAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'INFO_WMSRequest.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="INFO_WMSRequest">
+      <level value="ALL" />
+      <appender-ref ref="INFO_WMSRequestAppender" />
+    </logger>
+    <appender name="Info_WarnAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_Warn.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_Warn">
+      <level value="ALL" />
+      <appender-ref ref="Info_WarnAppender" />
+    </logger>
+    <appender name="Info_PlcReadLogAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_PlcReadLog.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_PlcReadLog">
+      <level value="ALL" />
+      <appender-ref ref="Info_PlcReadLogAppender" />
+    </logger>
+    <appender name="Info_TimingAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info_Timing.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info_Timing">
+      <level value="ALL" />
+      <appender-ref ref="Info_TimingAppender" />
+    </logger>
+    <appender name="INFO_I_WCS_GetExcTaskAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'INFO_I_WCS_GetExcTask.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="INFO_I_WCS_GetExcTask">
+      <level value="ALL" />
+      <appender-ref ref="INFO_I_WCS_GetExcTaskAppender" />
+    </logger>
+    <appender name="CreateTaskInAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'CreateTaskIn.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="CreateTaskIn">
+      <level value="ALL" />
+      <appender-ref ref="CreateTaskInAppender" />
+    </logger>
+    <appender name="RgvInfoAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'RgvInfo.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="RgvInfo">
+      <level value="ALL" />
+      <appender-ref ref="RgvInfoAppender" />
+    </logger>
+    <appender name="info_agvAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'info_agv.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="info_agv">
+      <level value="ALL" />
+      <appender-ref ref="info_agvAppender" />
+    </logger>
+    <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender">
+      <param name="Encoding" value="utf-8" />
+      <file value="D:\WCSLog\Info" />
+      <appendToFile value="true" />
+      <rollingStyle value="Composite" />
+      <maxSizeRollBackups value="-1" />
+      <maximumFileSize value="100MB" />
+      <lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
+      <staticLogFileName value="false" />
+      <DatePattern value="/yyyyMMdd/'Info.log'" />
+      <layout type="log4net.Layout.PatternLayout">
+        <conversionPattern value="%date || %5level || %logger || %message || %exception || end %newline" />
+      </layout>
+    </appender>
+    <logger name="Info">
+      <level value="ALL" />
+      <appender-ref ref="InfoAppender" />
+    </logger>
+  </log4net>
+</configuration>

+ 179 - 0
WCS.Virtual_PLC/PlcData.cs

@@ -0,0 +1,179 @@
+using FreeRedis;
+using Newtonsoft.Json;
+using System.Collections.Generic;
+using System.Linq;
+using WCS.Redis;
+
+namespace WCS.Virtual_PLC
+{
+    /// <summary>
+    /// plc数据
+    /// </summary>
+    public class PlcData
+    {
+        private static string RedisKey = "Virtual_PLC";
+        private static RedisClient Redis;
+
+        /// <summary>
+        /// 数据结构缓存
+        /// </summary>
+        private static List<PLCData> PLCDatas { get; set; } = new List<PLCData>();
+
+        /// <summary>
+        /// redis 链接字符串
+        /// </summary>
+        /// <param name="redisClient">Redis链接字符串</param>
+        public PlcData(string redisClient)
+        {
+            Redis = RedisHelper.CreateContext(redisClient, RedisKey);
+        }
+
+        /// <summary>
+        /// redis 链接字符串
+        /// </summary>
+        /// <param name="redisClient"></param>
+        /// <returns></returns>
+        public static PlcData Init(string redisClient)
+        {
+            return new PlcData(redisClient);
+        }
+
+        /// <summary>
+        /// 初始化PLC数据
+        /// </summary>
+        /// <param name="pLCData">一个PLC</param>
+        private void InitPlcData(PLCData pLCData)
+        {
+            if (!PLCDatas.Contains(pLCData))
+            {
+                PLCDatas.Add(pLCData);
+            }
+            //用总长度除以数据长度,再以每断数据的起始位置、IP、DB组成Key
+            var mun = pLCData.Length / pLCData.DataLength;
+            int addstart = 0;
+
+            for (int i = 0; i < mun; i++)
+            {
+                var key = $"{pLCData.IP}:{pLCData.DB}:{addstart}";
+                if (Redis.Exists(key)) continue;
+                Redis.Set(key, new byte[pLCData.DataLength]);
+                addstart = addstart + pLCData.DataLength;
+            }
+        }
+
+        /// <summary>
+        /// 初始化PLC数据
+        /// </summary>
+        /// <param name="pLCDatas">多个PLC</param>
+        public void InitPlcData(List<PLCData> pLCDatas)
+        {
+            string key = "PLCDataList";
+            if (Redis.Exists(key))
+            {
+                //判断值是否相等,值相等代表已经有过数据生成了,并且没有任何变化
+                var RedisPlcData = Redis.Get(key);
+                if (JsonConvert.SerializeObject(pLCDatas) == RedisPlcData)
+                {
+                    PLCDatas = pLCDatas;
+                    return;
+                }
+            }
+
+            pLCDatas.ForEach(v =>
+            {
+                InitPlcData(v);
+            });
+            Redis.Set(key, JsonConvert.SerializeObject(pLCDatas));
+        }
+
+        /// <summary>
+        /// 按照DB读取
+        /// </summary>
+        /// <param name="pLCData"></param>
+        /// <returns></returns>
+        public static byte[] Read(PLCData pLCData)
+        {
+            byte[] data = new byte[pLCData.Length];
+            //用总长度除以数据长度,再以每断数据的起始位置、IP、DB组成Key
+            var mun = pLCData.Length / pLCData.DataLength;
+            int addstart = 0;
+
+            for (int i = 0; i < mun; i++)
+            {
+                var a = Redis.Get<byte[]>($"{pLCData.IP}:{pLCData.DB}:{addstart}");
+                a.CopyTo(data, addstart);
+                addstart = addstart + pLCData.DataLength;
+            }
+            return data;
+        }
+
+        /// <summary>
+        /// 按照长度读取
+        /// </summary>
+        /// <param name="pLCData"></param>
+        /// <param name="startLength">起始长度</param>
+        /// <returns></returns>
+        public static byte[] Read(PLCData pLCData, int startLength)
+        {
+            return System.Text.Encoding.Default.GetBytes(Redis.Get($"{pLCData.IP}:{pLCData.DB}:{startLength}"));
+        }
+
+        /// <summary>
+        /// 写入数据
+        /// </summary>
+        /// <param name="pLCData"></param>
+        /// <param name="startLength"></param>
+        /// <param name="value"></param>
+        public static void Write(PLCData pLCData, int startLength, byte[] value)
+        {
+            var data = PLCDatas.Find(v => v.IP == pLCData.IP && v.DB == pLCData.DB);
+            int start = 0;
+            if (data.DataLength == data.Length) //数据长度与总长度相等时计算
+            {
+                start = startLength < data.DataLength ? 0 : (data.DataLength - startLength) + startLength;
+            }
+            else //不等
+            {
+                int addstart = 0;
+                var mun = data.Length / data.DataLength;
+                var a = new List<int>();
+                for (int i = 0; i < mun; i++)
+                {
+                    a.Add(addstart);
+                    addstart = addstart + data.DataLength;
+                }
+                start = a.Where(v => v <= startLength).OrderByDescending(v => v).FirstOrDefault();
+                startLength = startLength - start;
+            }
+            var bytes = System.Text.Encoding.Default.GetBytes(Redis.Get($"{pLCData.IP}:{pLCData.DB}:{start}")); //获取原有数据
+            value.CopyTo(bytes, startLength); //将变更的数据,更新到redis字节组中
+            Redis.Set($"{pLCData.IP}:{pLCData.DB}:{start}", bytes);
+        }
+    }
+
+    /// <summary>
+    /// PLC数据结构
+    /// </summary>
+    public class PLCData
+    {
+        /// <summary>
+        /// IP
+        /// </summary>
+        public string IP { get; set; }
+
+        /// <summary>
+        /// DB
+        /// </summary>
+        public int DB { get; set; }
+
+        /// <summary>
+        /// 总长度
+        /// </summary>
+        public int Length { get; set; }
+
+        /// <summary>
+        /// 数据长度
+        /// </summary>
+        public int DataLength { get; set; }
+    }
+}

+ 15 - 0
WCS.Virtual_PLC/WCS.Virtual_PLC.csproj

@@ -0,0 +1,15 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>netstandard2.1</TargetFramework>
+    <Nullable>enable</Nullable>
+    <GenerateDocumentationFile>True</GenerateDocumentationFile>
+    <Description>$(describe)</Description>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
+    <PackageReference Include="WCS.Redis" Version="1.0.2" />
+  </ItemGroup>
+
+</Project>