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.Runtime.Intrinsics.X86; using System.Text; 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"); // 使用工厂方法创建服务实例 // 获取当前的工序/工站 //WorkstationCode = await Context.Queryable().Where(it => it.PlcIP == plcSetting.IpAddress) // .Select(it => it.WorkstationCode).FirstAsync(); plcSetting = _optionsSetting.PlcSettings.Where(it => it.WorkStationCode == WorkstationCode).First(); Enum.TryParse(plcSetting.PlcType, out CpuType cpuType); if (!plcSetting.isEnble) { return; } using (_plcService = new PlcConntectHepler(plcSetting.IpAddress, cpuType)) { while (!stoppingToken.IsCancellationRequested) { try { //心跳检测 "DB1010.DBW0" // await _plcService.WriteAsync(plcSetting.intoStation.Heartbeat, (short)1); await _plcService.WriteAsync2(plcSetting.intoStation.Heartbeat, 1); // 轮询进站请求信号/工位开始查询请求 "DB1001.DBW2000" short 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); string productModel = ReadPlcStringAsync(_plcService._plc, plcSetting.intoStation.ProductModel, 48).Result; //"DB1001.DBB1054" //string productSN = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductSN); string productSN = ReadPlcStringAsync(_plcService._plc, plcSetting.intoStation.ProductSN, 48).Result; // 获取工单 //获取工艺路线工序 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(); //判断改产品是正常件还是返工件 string Routingcode = processOperations?.First()?.FkRoutingCode ?? ""; //插入入站请求ASK过站记录 ProductPassStationRecord ASKintoStation = new ProductPassStationRecord { ProductSN = productSN, WorkstationCode = WorkstationCode, Routingcode = Routingcode, 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, (short)result); await _plcService.WriteAsync2(plcSetting.intoStation.IntoStationResp, 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"); } /// /// 按业务规则解析PLC字符串(支持中文GBK编码) /// private async Task ReadPlcStringAsync(Plc plc, string addr, int maxLen) { try { // 解析地址:DB1010.DBB50 → DB编号1010 + 起始字节50 var parts = addr.Split('.', StringSplitOptions.RemoveEmptyEntries); if (parts.Length != 2) return string.Empty; if (!int.TryParse(parts[0].Replace("DB", ""), out int dbNum) || dbNum <= 0) return string.Empty; if (!int.TryParse(parts[1].Replace("DBB", ""), out int startByte) || startByte < 0) return string.Empty; // 读取字节数组 var bytes = await Task.Run(() => plc.ReadBytes(DataType.DataBlock, dbNum, startByte, maxLen)); if (bytes == null || bytes.Length < 3) return string.Empty; // 严格按规则解析:bytes[0]=总长度(备用)、bytes[1]=有效长度、bytes[2+]=内容 int validLen = Math.Clamp(bytes[1], 0, bytes.Length - 2); if (validLen <= 0) return string.Empty; // 提取有效内容并转字符串(GBK编码支持中文) var contentBytes = new byte[validLen]; Array.Copy(bytes, 2, contentBytes, 0, validLen); return Encoding.GetEncoding("GBK").GetString(contentBytes).Trim('\0', ' ', '\t'); } catch { return string.Empty; } } /// /// 校验是否可以进站 /// /// 产品型号 /// 产品SN /// protected virtual async Task checkEntryPermission(string productModel, string productSN, string workstationCode, List processOperations) { EntryPermissionResult result = EntryPermissionResult.UnkownException; //2上工位无记录 //ProcessOperation LastOperation1 = processOperations.Where(operation => operation.OperationSeq < processOperations.Where(it => it.OperationCode == workstationCode).Select(it => it.OperationSeq).First()).OrderByDescending(operation => operation.OperationSeq).First(); var LastOperationSeqObject = processOperations .Where(operation => operation.OperationCode == workstationCode) .FirstOrDefault(); if (LastOperationSeqObject.LastOperationSeq == 0) { return EntryPermissionResult.NoRecordAtPreviousStation; } ProcessOperation LastOperation = processOperations.Where(it => it.OperationSeq == LastOperationSeqObject. LastOperationSeq).First(); // 2. 清理 OperationCode:去除首尾空格 + 移除 \r 等不可见控制字符(兼容你之前的问题) string cleanOperationCode = LastOperation.OperationCode .Replace("\r", "") // 移除回车符 .Replace("\n", "") // 可选:移除换行符 .Trim(); // 移除首尾空格 // 3. 构建并执行真正的异步查询(补充执行方法,使 await 生效) var ExistLastOperationRecord = await DbScoped.SugarScope.CopyNew() .Queryable() .Where(it => it.ProductSN == productSN) .Where(it => it.OperationCode == cleanOperationCode) // 使用清理后的字符串匹配 // 补充:根据需求选择合适的执行方法(二选一或其他) .ToListAsync(); // 推荐:返回符合条件的所有数据(列表) // .FirstOrDefaultAsync(); // 若只需返回第一条数据,用这个(无匹配返回 null) // bool isExistLastOperationRecord = false; if (ExistLastOperationRecord != null &&ExistLastOperationRecord.Count()==0) { return EntryPermissionResult.NoRecordAtPreviousStation; } //// 3 上工位NG 入站或者出站结果 NG //bool isExistLastOperationNG = Context.Queryable() // .Where(it => it.ProductSN == productSN) // .Where(it => it.OperationCode == cleanOperationCode) // .Where(it => it.PasstationType == 3) // .Where(it => it.ResultCode == 1) // .Any(); // 3 上工位NG 入站或者出站结果 NG var ExistLastOperationNG = Context.Queryable() .Where(it => it.ProductSN == productSN) .Where(it => it.OperationCode == cleanOperationCode) .Where(it => it.PasstationType == 3) .Where(it => it.ResultCode == 1).ToList(); if (ExistLastOperationNG.Count() == 0 ) { 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 == cleanOperationCode) .Where(it => it.PasstationType == 3) .MaxAsync(it => it.OutStationTime??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; } } }