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;
}
}
}