This commit is contained in:
gcw_MV9p2JJN 2026-01-28 20:42:36 +08:00
parent d26440e5c2
commit 6d6d8c6e09
6 changed files with 329 additions and 19 deletions

View File

@ -56,7 +56,7 @@ namespace Infrastructure.Model
/// </summary> /// </summary>
public RedisServerConfig RedisServer { get; set; } 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 Id { get; set; }
public string WorkStationName { get; set; } public string WorkStationName { get; set; }
public string WorkStationCode { get; set; }
public string PlcType { get; set; } public string PlcType { get; set; }
public string IpAddress { get; set; } public string IpAddress { get; set; }
@ -203,6 +204,8 @@ namespace Infrastructure.Model
{ {
public string Heartbeat { get; set; } public string Heartbeat { get; set; }
public string IntoStationAsk { get; set; } public string IntoStationAsk { get; set; }
public string ProductModel { get; set; }
public string ProductSN { get; set; } public string ProductSN { get; set; }
public string IntoStationResp { get; set; } public string IntoStationResp { get; set; }
} }

View File

@ -114,14 +114,15 @@
"PlcSettings": [ "PlcSettings": [
{ {
"Id": 1, "Id": 1,
"WorkStationName": "OP080-2 PCBA拧紧", "WorkStationCode": "OP70_01",
"WorkStation": "OP080-2", "WorkStationName": "OP70_01 点散热胶GF1500",
"PlcType": "S71500", "PlcType": "S71500",
"IpAddress": "192.168.11.71", "IpAddress": "192.168.11.71",
"IntoStation": { "IntoStation": {
"Heartbeat": "DB1020.DBW0", // "Heartbeat": "DB1010.DBW0", //
"IntoStationAsk": "DB1001.DBW2000", // "IntoStationAsk": "DB1001.DBW2000", //
"ProductSN": "", "ProductModel": "DB1001.DBB1000",
"ProductSN": "DB1001.DBB1054",
"IntoStationResp": "DB1010.DBW2000" // "IntoStationResp": "DB1010.DBW2000" //
} }
} }

View File

@ -54,6 +54,13 @@ namespace RIZO.Model.MES.product_trace
[StringLength(50)] [StringLength(50)]
public string OperationCode { get; set; } public string OperationCode { get; set; }
/// <summary>
/// 工序name
/// </summary>
[SugarColumn(ColumnName = "operationName", Length = 50)]
[StringLength(50)]
public string OperationName { get; set; }
/// <summary> /// <summary>
/// 当前产品处于的生命周期 /// 当前产品处于的生命周期
/// </summary> /// </summary>

View File

@ -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
{
/// <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");
// 使用工厂方法创建服务实例
Enum.TryParse<CpuType>(plcSetting.PlcType, out CpuType cpuType);
// 获取当前的工序/工站
//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();
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<int>(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<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();
//判断改产品是正常件还是返工件
//插入入站请求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");
}
/// <summary>
/// 校验是否可以进站
/// </summary>
/// <param name="productModel">产品型号</param>
/// <param name="productSN">产品SN</param>
/// <returns></returns>
protected async Task<EntryPermissionResult> checkEntryPermission(string productModel, string productSN, string workstationCode, List<ProcessOperation> 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<ProductPassStationRecord>()
.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<ProductPassStationRecord>()
.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<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 == 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<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;
}
}
}

View File

@ -3,6 +3,7 @@ using Infrastructure.Extensions;
using Infrastructure.Model; using Infrastructure.Model;
using MDM.Model.Plant; using MDM.Model.Plant;
using MDM.Model.Process; using MDM.Model.Process;
using Microsoft.AspNetCore.JsonPatch.Operations;
using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -29,8 +30,8 @@ namespace RIZO.Service.PLCBackground.Stations.Into
private static Logger _logger = LogManager.GetCurrentClassLogger(); private static Logger _logger = LogManager.GetCurrentClassLogger();
private PlcConntectHepler _plcService; private PlcConntectHepler _plcService;
private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(5); private readonly TimeSpan _pollingInterval = TimeSpan.FromSeconds(5);
private readonly string _ipAddress = "192.168.10.222"; // private readonly string _ipAddress = "192.168.10.222";
private readonly CpuType _cpuType = CpuType.S71500; // private readonly CpuType _cpuType = CpuType.S71500;
private string WorkstationCode; private string WorkstationCode;
private readonly SqlSugarClient Context; private readonly SqlSugarClient Context;
private readonly OptionsSetting _optionsSetting; private readonly OptionsSetting _optionsSetting;
@ -39,19 +40,19 @@ namespace RIZO.Service.PLCBackground.Stations.Into
{ {
Context = DbScoped.SugarScope.CopyNew(); Context = DbScoped.SugarScope.CopyNew();
_optionsSetting= options.Value; _optionsSetting= options.Value;
plcSetting = _optionsSetting.PlcSettings; plcSetting = _optionsSetting.PlcSettings.Where(it=>it.WorkStationCode== "OP70_01").First();
} }
protected override async Task ExecuteAsync(CancellationToken stoppingToken) protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{ {
_logger.Info("PLC Polling Service started"); _logger.Info("PLC Polling Service started");
// 使用工厂方法创建服务实例 // 使用工厂方法创建服务实例
using (_plcService = new PlcConntectHepler(_ipAddress, _cpuType)) Enum.TryParse<CpuType>(plcSetting.PlcType, out CpuType cpuType);
using (_plcService = new PlcConntectHepler(plcSetting.IpAddress, cpuType))
{ {
// 获取当前的工序/工站 // 获取当前的工序/工站
WorkstationCode = await Context.Queryable<PlantWorkstation>().Where(it => it.PlcIP == _ipAddress) WorkstationCode = await Context.Queryable<PlantWorkstation>().Where(it => it.PlcIP == plcSetting.IpAddress)
.Select(it => it.WorkstationCode).FirstAsync(); .Select(it => it.WorkstationCode).FirstAsync();
@ -59,19 +60,20 @@ namespace RIZO.Service.PLCBackground.Stations.Into
{ {
try try
{ {
//心跳检测 //心跳检测 "DB1010.DBW0"
await _plcService.WriteAsync("DB1010.DBW0", 1); await _plcService.WriteAsync(plcSetting.intoStation.Heartbeat, 1);
// 轮询进站请求信号/工位开始查询请求 // 轮询进站请求信号/工位开始查询请求 "DB1001.DBW2000"
int intoStationAsk = await _plcService.ReadAsync<int>("DB1001.DBW2000"); int intoStationAsk = await _plcService.ReadAsync<int>(plcSetting.intoStation.IntoStationAsk);
if (intoStationAsk == 1) if (intoStationAsk == 1)
{ {
// 处理进站请求 // 处理进站请求
_logger.Info("Processing into station request..."); _logger.Info("Processing into station request...");
//获取产品SN码 //获取产品SN码 "DB1001.DBB1000"
string productModel = await _plcService.ReadStringAsync("DB1001.DBB1000"); string productModel = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductModel);
string productSN = await _plcService.ReadStringAsync("DB1001.DBB1054"); //"DB1001.DBB1054"
string productSN = await _plcService.ReadStringAsync(plcSetting.intoStation.ProductSN);
// 获取工单 // 获取工单
@ -89,6 +91,7 @@ namespace RIZO.Service.PLCBackground.Stations.Into
WorkstationCode = WorkstationCode, WorkstationCode = WorkstationCode,
Routingcode = processOperations.First().FkRoutingCode, Routingcode = processOperations.First().FkRoutingCode,
OperationCode = WorkstationCode, OperationCode = WorkstationCode,
OperationName= plcSetting.WorkStationName,
ProductionLifeStage = 1, // 1表示生产中 ProductionLifeStage = 1, // 1表示生产中
PasstationType = 0, // 0表示入站请求 PasstationType = 0, // 0表示入站请求
PasstationDescription = "入站请求ASK", PasstationDescription = "入站请求ASK",
@ -98,7 +101,8 @@ namespace RIZO.Service.PLCBackground.Stations.Into
await Context.Insertable(ASKintoStation).ExecuteCommandAsync(); await Context.Insertable(ASKintoStation).ExecuteCommandAsync();
//判断该产品是否允许进站 //判断该产品是否允许进站
EntryPermissionResult result = await checkEntryPermission(productModel, productSN, WorkstationCode, processOperations); 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); await Task.Delay(_pollingInterval, stoppingToken);
@ -226,6 +230,7 @@ namespace RIZO.Service.PLCBackground.Stations.Into
WorkstationCode = WorkstationCode, WorkstationCode = WorkstationCode,
Routingcode = processOperations.First().FkRoutingCode, Routingcode = processOperations.First().FkRoutingCode,
OperationCode = WorkstationCode, OperationCode = WorkstationCode,
OperationName = plcSetting.WorkStationName,
ProductionLifeStage = 1, // 1表示生产中 ProductionLifeStage = 1, // 1表示生产中
PasstationType = 1, // 入站完毕 PasstationType = 1, // 入站完毕
PasstationDescription = "入站完毕Resp", PasstationDescription = "入站完毕Resp",

View File

@ -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
{
/// <summary>
/// OP点散热胶GF1500
/// </summary>
[AppService(ServiceType = typeof(PlcIntoStationService_OP70_02), ServiceLifetime = LifeTime.Singleton)]
public class PlcIntoStationService_OP70_02 : PlcIntoStationService_Common
{
public PlcIntoStationService_OP70_02(IOptions<OptionsSetting> options) : base(options)
{
WorkstationCode = "OP70_02";
}
}
}