From 6d6d8c6e090338f353b36eb1c2aac7d321348127 Mon Sep 17 00:00:00 2001 From: gcw_MV9p2JJN Date: Wed, 28 Jan 2026 20:42:36 +0800 Subject: [PATCH] =?UTF-8?q?=E8=BF=9B=E7=AB=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Infrastructure/Model/OptionsSetting.cs | 5 +- RIZO.Admin.WebApi/appsettings.json | 9 +- .../product_trace/ProductPassStationRecord.cs | 7 + .../Into/PlcIntoStationService_Common.cs | 257 ++++++++++++++++++ .../Into/PlcIntoStationService_OP70_01.cs | 33 ++- .../Into/PlcIntoStationService_OP70_02.cs | 37 +++ 6 files changed, 329 insertions(+), 19 deletions(-) create mode 100644 RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_Common.cs create mode 100644 RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_02.cs diff --git a/Infrastructure/Model/OptionsSetting.cs b/Infrastructure/Model/OptionsSetting.cs index 7f2ab3d..9bcd0ec 100644 --- a/Infrastructure/Model/OptionsSetting.cs +++ b/Infrastructure/Model/OptionsSetting.cs @@ -56,7 +56,7 @@ namespace Infrastructure.Model /// public RedisServerConfig RedisServer { get; set; } - public PlcSettings PlcSettings { get; set; } + public PlcSettings[] PlcSettings { get; set; } @@ -193,6 +193,7 @@ namespace Infrastructure.Model { public string Id { get; set; } public string WorkStationName { get; set; } + public string WorkStationCode { get; set; } public string PlcType { get; set; } public string IpAddress { get; set; } @@ -203,6 +204,8 @@ namespace Infrastructure.Model { public string Heartbeat { get; set; } public string IntoStationAsk { get; set; } + + public string ProductModel { get; set; } public string ProductSN { get; set; } public string IntoStationResp { get; set; } } diff --git a/RIZO.Admin.WebApi/appsettings.json b/RIZO.Admin.WebApi/appsettings.json index 90acf0a..2ba5eb6 100644 --- a/RIZO.Admin.WebApi/appsettings.json +++ b/RIZO.Admin.WebApi/appsettings.json @@ -114,14 +114,15 @@ "PlcSettings": [ { "Id": 1, - "WorkStationName": "OP080-2 PCBA拧紧", - "WorkStation": "OP080-2", + "WorkStationCode": "OP70_01", + "WorkStationName": "OP70_01 点散热胶GF1500", "PlcType": "S71500", "IpAddress": "192.168.11.71", "IntoStation": { - "Heartbeat": "DB1020.DBW0", // 心跳地址 + "Heartbeat": "DB1010.DBW0", // 心跳地址 "IntoStationAsk": "DB1001.DBW2000", // 进站请求地址 - "ProductSN": "", + "ProductModel": "DB1001.DBB1000", + "ProductSN": "DB1001.DBB1054", "IntoStationResp": "DB1010.DBW2000" // 进站回复结果地址 } } diff --git a/RIZO.Model/MES/product_trace/ProductPassStationRecord.cs b/RIZO.Model/MES/product_trace/ProductPassStationRecord.cs index 02887e8..8d40c6d 100644 --- a/RIZO.Model/MES/product_trace/ProductPassStationRecord.cs +++ b/RIZO.Model/MES/product_trace/ProductPassStationRecord.cs @@ -54,6 +54,13 @@ namespace RIZO.Model.MES.product_trace [StringLength(50)] public string OperationCode { get; set; } + /// + /// 工序name + /// + [SugarColumn(ColumnName = "operationName", Length = 50)] + [StringLength(50)] + public string OperationName { get; set; } + /// /// 当前产品处于的生命周期 /// diff --git a/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_Common.cs b/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_Common.cs new file mode 100644 index 0000000..2d8b5d1 --- /dev/null +++ b/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_Common.cs @@ -0,0 +1,257 @@ +using Infrastructure.Attribute; +using Infrastructure.Extensions; +using Infrastructure.Model; +using MDM.Model.Plant; +using MDM.Model.Process; +using Microsoft.AspNetCore.JsonPatch.Operations; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NLog; +using RIZO.Model.Mes; +using RIZO.Model.MES.product_trace; +using RIZO.Repository; +using RIZO.Service.MES.product.IService; +using RIZO.Service.PLC; +using S7.Net; +using SqlSugar.IOC; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace RIZO.Service.PLCBackground.Stations.Into +{ + /// + /// OP点散热胶GF1500 + /// + //[AppService(ServiceType = typeof(PlcIntoStationService_Common), ServiceLifetime = LifeTime.Singleton)] + public class PlcIntoStationService_Common : BackgroundService + { + protected static Logger _logger = LogManager.GetCurrentClassLogger(); + protected PlcConntectHepler _plcService; + protected readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(5); + // private readonly string _ipAddress = "192.168.10.222"; + // private readonly CpuType _cpuType = CpuType.S71500; + protected string WorkstationCode; + protected readonly SqlSugarClient Context; + protected readonly OptionsSetting _optionsSetting; + protected PlcSettings plcSetting; + public PlcIntoStationService_Common(IOptions options) + { + Context = DbScoped.SugarScope.CopyNew(); + _optionsSetting= options.Value; + + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + _logger.Info("PLC Polling Service started"); + + // 使用工厂方法创建服务实例 + Enum.TryParse(plcSetting.PlcType, out CpuType cpuType); + // 获取当前的工序/工站 + //WorkstationCode = await Context.Queryable().Where(it => it.PlcIP == plcSetting.IpAddress) + // .Select(it => it.WorkstationCode).FirstAsync(); + plcSetting = _optionsSetting.PlcSettings.Where(it => it.WorkStationCode == WorkstationCode).First(); + using (_plcService = new PlcConntectHepler(plcSetting.IpAddress, cpuType)) + { + + + + while (!stoppingToken.IsCancellationRequested) + { + try + { + //心跳检测 "DB1010.DBW0" + await _plcService.WriteAsync(plcSetting.intoStation.Heartbeat, 1); + + // 轮询进站请求信号/工位开始查询请求 "DB1001.DBW2000" + int intoStationAsk = await _plcService.ReadAsync(plcSetting.intoStation.IntoStationAsk); + if (intoStationAsk == 1) + { + // 处理进站请求 + _logger.Info("Processing into station request..."); + + //获取产品SN码 "DB1001.DBB1000" + string productModel = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductModel); + //"DB1001.DBB1054" + string productSN = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductSN); + + // 获取工单 + + //获取工艺路线工序 + List processOperations = await Context.Queryable().LeftJoin((r, o) => r.RoutingCode == o.FkRoutingCode) + .Where((r, o) => r.FkProductMaterialCode == productModel && r.Status == 1) + .Select((r, o) => o).ToListAsync(); + //判断改产品是正常件还是返工件 + + + //插入入站请求ASK过站记录 + ProductPassStationRecord ASKintoStation = new ProductPassStationRecord + { + ProductSN = productSN, + WorkstationCode = WorkstationCode, + Routingcode = processOperations.First().FkRoutingCode, + OperationCode = WorkstationCode, + OperationName= plcSetting.WorkStationName, + ProductionLifeStage = 1, // 1表示生产中 + PasstationType = 0, // 0表示入站请求 + PasstationDescription = "入站请求ASK", + EffectTime = DateTime.Now, + CreatedTime = DateTime.Now, + }; + await Context.Insertable(ASKintoStation).ExecuteCommandAsync(); + //判断该产品是否允许进站 + EntryPermissionResult result = await checkEntryPermission(productModel, productSN, WorkstationCode, processOperations); + // "DB1010.DBW2000" + await _plcService.WriteAsync(plcSetting.intoStation.IntoStationResp, (int)result); + } + await Task.Delay(_pollingInterval, stoppingToken); + + } + catch (OperationCanceledException) + { + _logger.Info("PLC Polling Service is stopping"); + break; + } + catch (Exception ex) + { + _logger.Error(ex, "PLC polling error"); + await Task.Delay(TimeSpan.FromSeconds(10), stoppingToken); + } + } + } + + _logger.Info("PLC Polling Service stopped"); + } + + /// + /// 校验是否可以进站 + /// + /// 产品型号 + /// 产品SN + /// + protected async Task checkEntryPermission(string productModel, string productSN, string workstationCode, List processOperations) + { + + EntryPermissionResult result = EntryPermissionResult.UnkownException; + + + //2上工位无记录 + + //ProcessOperation LastOperation = processOperations.Where(operation => operation.OperationSeq < processOperations.Where(it => it.OperationCode == workstationCode).Select(it => it.OperationSeq).First()).OrderByDescending(operation => operation.OperationSeq).First(); + int LastOperationSeq = processOperations.Where(operation => operation.OperationCode == workstationCode).Select(operation => operation.LastOperationSeq ?? -1).First(); + ProcessOperation LastOperation = processOperations.Where(it => it.OperationSeq == LastOperationSeq).First(); + bool isExistLastOperationRecord = await DbScoped.SugarScope.CopyNew().Queryable() + .Where(it => it.ProductSN == productSN) + .Where(it => it.OperationCode == LastOperation.OperationCode) + .AnyAsync(); + if (!isExistLastOperationRecord) + { + return EntryPermissionResult.NoRecordAtPreviousStation; + } + + // 3 上工位NG 入站或者出站结果 NG + bool isExistLastOperationNG = await Context.Queryable() + .Where(it => it.ProductSN == productSN) + .Where(it => it.OperationCode == LastOperation.OperationCode) + .Where(it => it.PasstationType == 2 || it.PasstationType == 4) + .Where(it => it.ResultCode != 1) + .AnyAsync(); + if (!isExistLastOperationNG) + { + result = EntryPermissionResult.PreviousStationNG; + goto InsertPassrecord; + } + + + // 4 扫码产品型号错误 + bool isExistproductSN = await Context.Queryable() + .Where(it => it.ProductSN == productSN).AnyAsync(); + + + bool isExistproductSNProducting = await Context.Queryable() + .Where(it => it.ProductSN == productSN && (it.ProductCurrentStatus != 1 && it.ProductCurrentStatus != 3)).AnyAsync(); + if (!isExistproductSN || !isExistproductSNProducting) + { + result = EntryPermissionResult.ProductModelError; + goto InsertPassrecord; + } + //6时间超出规定无法生产 + + + //7固化时间未到规定时间 + + int LastOperationStandardTime = processOperations.Where(operation => operation.OperationCode == LastOperation.OperationCode) + .Select(operation => operation.StandardTime??0).First(); + + + + if (LastOperationStandardTime > 0) + { + // 上一站的入站时间 和本站的请求时间差值 + DateTime LastInStationTime= await Context.Queryable() + .Where(it => it.ProductSN == productSN) + .Where(it => it.OperationCode == LastOperation.OperationCode) + .Where(it => it.PasstationType == 1) + .MaxAsync(it => it.InStationTime??DateTime.MinValue); + TimeSpan timeDiff = DateTime.Now - LastInStationTime; + double totalSeconds = timeDiff.TotalSeconds; + if(totalSeconds < LastOperationStandardTime) + { + result = EntryPermissionResult.CuringTimeNotReached; + goto InsertPassrecord; + } + + } + + //12 最大重复入站次数 + int MaxRepeatEntries = processOperations.Where(operation => operation.OperationCode == workstationCode).Select(operation => operation.MaxStationCount??1).First(); + if(MaxRepeatEntries>1) + { + int currentStationEntryCount = await Context.Queryable() + .Where(it => it.ProductSN == productSN) + .Where(it => it.OperationCode == workstationCode) + .Where(it => it.PasstationType == 1) + .CountAsync(); + if(currentStationEntryCount >= MaxRepeatEntries) + { + result = EntryPermissionResult.MaximumRepeatedEntries; + goto InsertPassrecord; + } + } + + //OK 准许进站 + result = EntryPermissionResult.AllowIntoStation; + goto InsertPassrecord; + InsertPassrecord: + //插入入站请求Resp过站记录 + ProductPassStationRecord RespintoStation = new ProductPassStationRecord + { + ProductSN = productSN, + WorkstationCode = WorkstationCode, + Routingcode = processOperations.First().FkRoutingCode, + OperationCode = WorkstationCode, + OperationName = plcSetting.WorkStationName, + ProductionLifeStage = 1, // 1表示生产中 + PasstationType = 1, // 入站完毕 + PasstationDescription = "入站完毕Resp", + EffectTime = DateTime.Now, + InStationTime = DateTime.Now, + ResultCode = (int)result, + ResultDescription = result.ToString(), + CreatedTime = DateTime.Now, + + }; + await Context.Insertable(RespintoStation).ExecuteCommandAsync(); + + return result; + } + + + + + } + +} + diff --git a/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_01.cs b/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_01.cs index c0ca638..f35d459 100644 --- a/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_01.cs +++ b/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_01.cs @@ -3,6 +3,7 @@ using Infrastructure.Extensions; using Infrastructure.Model; using MDM.Model.Plant; using MDM.Model.Process; +using Microsoft.AspNetCore.JsonPatch.Operations; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -29,8 +30,8 @@ namespace RIZO.Service.PLCBackground.Stations.Into private static Logger _logger = LogManager.GetCurrentClassLogger(); private PlcConntectHepler _plcService; private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(5); - private readonly string _ipAddress = "192.168.10.222"; - private readonly CpuType _cpuType = CpuType.S71500; + // private readonly string _ipAddress = "192.168.10.222"; + // private readonly CpuType _cpuType = CpuType.S71500; private string WorkstationCode; private readonly SqlSugarClient Context; private readonly OptionsSetting _optionsSetting; @@ -39,19 +40,19 @@ namespace RIZO.Service.PLCBackground.Stations.Into { Context = DbScoped.SugarScope.CopyNew(); _optionsSetting= options.Value; - plcSetting = _optionsSetting.PlcSettings; + plcSetting = _optionsSetting.PlcSettings.Where(it=>it.WorkStationCode== "OP70_01").First(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.Info("PLC Polling Service started"); - // 使用工厂方法创建服务实例 - using (_plcService = new PlcConntectHepler(_ipAddress, _cpuType)) + Enum.TryParse(plcSetting.PlcType, out CpuType cpuType); + using (_plcService = new PlcConntectHepler(plcSetting.IpAddress, cpuType)) { // 获取当前的工序/工站 - WorkstationCode = await Context.Queryable().Where(it => it.PlcIP == _ipAddress) + WorkstationCode = await Context.Queryable().Where(it => it.PlcIP == plcSetting.IpAddress) .Select(it => it.WorkstationCode).FirstAsync(); @@ -59,19 +60,20 @@ namespace RIZO.Service.PLCBackground.Stations.Into { try { - //心跳检测 - await _plcService.WriteAsync("DB1010.DBW0", 1); + //心跳检测 "DB1010.DBW0" + await _plcService.WriteAsync(plcSetting.intoStation.Heartbeat, 1); - // 轮询进站请求信号/工位开始查询请求 - int intoStationAsk = await _plcService.ReadAsync("DB1001.DBW2000"); + // 轮询进站请求信号/工位开始查询请求 "DB1001.DBW2000" + int intoStationAsk = await _plcService.ReadAsync(plcSetting.intoStation.IntoStationAsk); if (intoStationAsk == 1) { // 处理进站请求 _logger.Info("Processing into station request..."); - //获取产品SN码 - string productModel = await _plcService.ReadStringAsync("DB1001.DBB1000"); - string productSN = await _plcService.ReadStringAsync("DB1001.DBB1054"); + //获取产品SN码 "DB1001.DBB1000" + string productModel = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductModel); + //"DB1001.DBB1054" + string productSN = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductSN); // 获取工单 @@ -89,6 +91,7 @@ namespace RIZO.Service.PLCBackground.Stations.Into WorkstationCode = WorkstationCode, Routingcode = processOperations.First().FkRoutingCode, OperationCode = WorkstationCode, + OperationName= plcSetting.WorkStationName, ProductionLifeStage = 1, // 1表示生产中 PasstationType = 0, // 0表示入站请求 PasstationDescription = "入站请求ASK", @@ -98,7 +101,8 @@ namespace RIZO.Service.PLCBackground.Stations.Into await Context.Insertable(ASKintoStation).ExecuteCommandAsync(); //判断该产品是否允许进站 EntryPermissionResult result = await checkEntryPermission(productModel, productSN, WorkstationCode, processOperations); - await _plcService.WriteAsync("DB1010.DBW2000", (int)result); + // "DB1010.DBW2000" + await _plcService.WriteAsync(plcSetting.intoStation.IntoStationResp, (int)result); } await Task.Delay(_pollingInterval, stoppingToken); @@ -226,6 +230,7 @@ namespace RIZO.Service.PLCBackground.Stations.Into WorkstationCode = WorkstationCode, Routingcode = processOperations.First().FkRoutingCode, OperationCode = WorkstationCode, + OperationName = plcSetting.WorkStationName, ProductionLifeStage = 1, // 1表示生产中 PasstationType = 1, // 入站完毕 PasstationDescription = "入站完毕Resp", diff --git a/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_02.cs b/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_02.cs new file mode 100644 index 0000000..7eb6c56 --- /dev/null +++ b/RIZO.Service/PLCBackground/Stations/Into/PlcIntoStationService_OP70_02.cs @@ -0,0 +1,37 @@ +using Infrastructure.Attribute; +using Infrastructure.Extensions; +using Infrastructure.Model; +using MDM.Model.Plant; +using MDM.Model.Process; +using Microsoft.AspNetCore.JsonPatch.Operations; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using NLog; +using RIZO.Model.Mes; +using RIZO.Model.MES.product_trace; +using RIZO.Repository; +using RIZO.Service.MES.product.IService; +using RIZO.Service.PLC; +using S7.Net; +using SqlSugar.IOC; +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace RIZO.Service.PLCBackground.Stations.Into +{ + /// + /// OP点散热胶GF1500 + /// + [AppService(ServiceType = typeof(PlcIntoStationService_OP70_02), ServiceLifetime = LifeTime.Singleton)] + public class PlcIntoStationService_OP70_02 : PlcIntoStationService_Common + { + public PlcIntoStationService_OP70_02(IOptions options) : base(options) + { + WorkstationCode = "OP70_02"; + } + } + +} +