2026-02-02 14:08:46 +08:00

335 lines
16 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
{
/// <summary>
/// OP点散热胶GF1500
/// </summary>
//[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<OptionsSetting> 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<PlantWorkstation>().Where(it => it.PlcIP == plcSetting.IpAddress)
// .Select(it => it.WorkstationCode).FirstAsync();
plcSetting = _optionsSetting.PlcSettings.Where(it => it.WorkStationCode == WorkstationCode).First();
Enum.TryParse<CpuType>(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<short>(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<ProcessOperation> processOperations = await Context.Queryable<ProcessRouting>().LeftJoin<ProcessOperation>((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,(int) result);
}
await Task.Delay(_pollingInterval, stoppingToken);
}
catch (OperationCanceledException ex)
{
_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");
}
/// <summary>
/// 按业务规则解析PLC字符串支持中文GBK编码
/// </summary>
private async Task<string> 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;
}
}
/// <summary>
/// 校验是否可以进站
/// </summary>
/// <param name="productModel">产品型号</param>
/// <param name="productSN">产品SN</param>
/// <returns></returns>
protected virtual async Task<EntryPermissionResult> checkEntryPermission(string productModel, string productSN, string workstationCode, List<ProcessOperation> processOperations)
{
workstationCode = workstationCode.Trim();
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();
var LastOperationSeqObject = processOperations?.Where(operation =>
string.Equals(operation.OperationCode, workstationCode, StringComparison.OrdinalIgnoreCase))
.FirstOrDefault();
if (LastOperationSeqObject.LastOperationSeq != 0)
{
ProcessOperation LastOperation = processOperations.Where(it => it.OperationSeq == LastOperationSeqObject.LastOperationSeq).First();
// 2. 清理 OperationCode去除首尾空格 + 移除 \r 等不可见控制字符(兼容你之前的问题)
string cleanOperationCode = LastOperation.OperationCode
.Replace("\r", "") // 移除回车符
.Replace("\n", "") // 可选:移除换行符
.Trim(); // 移除首尾空格
productSN = productSN.Replace("\r", "").Replace("\n", "").Trim();
// 3. 构建并执行真正的异步查询(补充执行方法,使 await 生效)
var ExistLastOperationRecord = await DbScoped.SugarScope.CopyNew()
.Queryable<ProductPassStationRecord>()
.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<ProductPassStationRecord>()
// .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<ProductPassStationRecord>()
.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<ProductLifecycle>()
// .Where(it => it.ProductSN == productSN).AnyAsync();
//bool isExistproductSNProducting = await Context.Queryable<ProductLifecycle>()
// .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<ProductPassStationRecord>()
.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<ProductPassStationRecord>()
.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;
}
}
}