PLC数采

This commit is contained in:
quowingwang 2026-01-28 19:53:01 +08:00
parent da16fed4dc
commit 2c8e09d794
2 changed files with 140 additions and 56 deletions

View File

@ -12,6 +12,7 @@ using S7.Net;
using SqlSugar;
using SqlSugar.IOC;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -41,11 +42,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
// 先在类中添加2个核心优化字段支撑高频访问
private readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(15, 50); // 限制20并发适配50台PLC
private readonly ConcurrentDictionary<string, (Plc Client, DateTime LastUsedTime)> _plcConnPool = new(); // 连接池
private readonly SqlSugarClient Context;
public PlcService()
{
Context = DbScoped.SugarScope.CopyNew();
}
#region PLC地址块儿映射
//MES返回PLC请求映射
private readonly Dictionary<string, string> _mesIntReturnMap = new()
@ -615,36 +611,36 @@ namespace RIZO.Admin.WebApi.PLC.Service
// OP085 专属地址映射自动上料到波峰焊工位DB1001
private readonly Dictionary<string, (string Addr, int Len)> _op085StringMap = new()
{
{ "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte
{ "产品型号", ("DB1001.DBB1000", 48) }, // String[48]
{ "产品名称", ("DB1001.DBB1054", 48) }, // String[48]
{ "波峰焊托盘条码", ("DB1001.DBB2070", 40) }, // String[40]
{ "穴位1产品SN", ("DB1001.DBB2486", 40) }, // String[40]
{ "穴位2产品SN", ("DB1001.DBB2528", 40) }, // String[40]
{ "穴位3产品SN", ("DB1001.DBB2570", 40) }, // String[40]
{ "穴位4产品SN", ("DB1001.DBB2612", 40) } // String[40]
{ "报警信息", ("DB1030.DBB58", 48) }, // Array[1..48] of Byte
{ "产品型号", ("DB1030.DBB1000", 48) }, // String[48]
{ "产品名称", ("DB1030.DBB1054", 48) }, // String[48]
{ "波峰焊托盘条码", ("DB1030.DBB2070", 40) }, // String[40]
{ "穴位1产品SN", ("DB1030.DBB2486", 40) }, // String[40]
{ "穴位2产品SN", ("DB1030.DBB2528", 40) }, // String[40]
{ "穴位3产品SN", ("DB1030.DBB2570", 40) }, // String[40]
{ "穴位4产品SN", ("DB1030.DBB2612", 40) } // String[40]
};
private readonly Dictionary<string, string> _op085IntMap = new()
{
// 基础状态
{ "运行状态", "DB1001.DBW0" }, // Int - 1=空闲2=运行中3=故障
{ "设备模式", "DB1001.DBW2" }, // Int - 1=空模式2=手动4=初始化8=自动16=CycleStop
{ "设备在线状态", "DB1001.DBW4" }, // Int - 1=离线,0=在线
{ "ByPass", "DB1001.DBW6" }, // Int - 1=ByPass,0=正常模式
{ "生产模式", "DB1001.DBW8" }, // Int - 1=点检2=返工4=样件5=正常
{ "运行状态", "DB1030.DBW0" }, // Int - 1=空闲2=运行中3=故障
{ "设备模式", "DB1030.DBW2" }, // Int - 1=空模式2=手动4=初始化8=自动16=CycleStop
{ "设备在线状态", "DB1030.DBW4" }, // Int - 1=离线,0=在线
{ "ByPass", "DB1030.DBW6" }, // Int - 1=ByPass,0=正常模式
{ "生产模式", "DB1030.DBW8" }, // Int - 1=点检2=返工4=样件5=正常
// 产量数据
{ "实际产量", "DB1001.DBD1104" }, // DInt
{ "合格数量", "DB1001.DBD1108" }, // DInt
{ "失败数量", "DB1001.DBD1112" }, // DInt
{ "实际产量", "DB1030.DBD1104" }, // DInt
{ "合格数量", "DB1030.DBD1108" }, // DInt
{ "失败数量", "DB1030.DBD1112" }, // DInt
// 操作请求
{ "保存请求", "DB1001.DBW2002" }, // Int - 1=请求开始0=无请求
{ "线体托盘号", "DB1001.DBW2070" }, // Int
{ "保存请求", "DB1030.DBW2002" }, // Int - 1=请求开始0=无请求
{ "线体托盘号", "DB1030.DBW2070" }, // Int
// 工艺参数
{ "节拍时间", "DB1001.DBD2242" } // Real
{ "节拍时间", "DB1030.DBD2242" } // Real
};
// OP100 专属地址映射(手动上料壳体&盖板工位DB1001
@ -1344,7 +1340,7 @@ namespace RIZO.Admin.WebApi.PLC.Service
? $"{plcName}生产数据读取成功(复用连接)"
: $"{plcName}生产数据读取成功(新建连接)";
WritePlcSaveRequestResult(plc, ip, plcName, prodData, "1");
_ = Task.Run(() => RecordPlcOperationResult(plcName, prodData)).ConfigureAwait(false);
_ = Task.Run(() => RecordPlcOperationResult(plcName, prodData)).ConfigureAwait(false);
return (true, prodData, successMsg);
}
@ -1403,11 +1399,17 @@ namespace RIZO.Admin.WebApi.PLC.Service
var taskTrayNo = ReadPlcIntAsync(plc, _op020_2IntMap["托盘号"]);
var taskTotalResult = ReadPlcIntAsync(plc, _op020_2IntMap["总结果"]);
// DInt任务单独提取避免字典开销
var taskActualOutput = ReadPlcIntAsync(plc, _op050IntMap["实际产量"]);
var taskQualifiedQty = ReadPlcIntAsync(plc, _op050IntMap["合格数量"]);
var taskFailedQty = ReadPlcIntAsync(plc, _op050IntMap["失败数量"]);
// 1.3 等待所有并行任务完成(关键:耗时=最慢的单个读取任务,而非总和)
await Task.WhenAll(
taskProductModel, taskProductName, taskSN,
taskRunStatus, taskMachineModel, taskOnlineStatus,
taskProduceModel, taskTrayNo, taskTotalResult);
taskProduceModel, taskTrayNo, taskTotalResult, taskActualOutput, taskQualifiedQty, taskFailedQty);
// 2. 获取读取结果(带空值兜底,避免后续空引用)
string productModel = await taskProductModel ?? string.Empty;
@ -1419,6 +1421,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
int produceModel = await taskProduceModel;
int trayNo = await taskTrayNo;
int totalResult = await taskTotalResult;
int actualOutput = await taskActualOutput;
int qualifiedQty = await taskQualifiedQty;
int failedQty = await taskFailedQty;
// 3. 异步复位上传请求(非阻塞,不影响数据读取效率)
_ = ResetUploadRequestAsync(plc, ip, _op020_2IntMap["上传请求"]);
@ -1438,9 +1443,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
string onlineStatusDesc = onlineStatus == 1 ? "离线" : "在线";
string qualificationFlag = totalResult switch { 1 => "1", 2 => "0", _ => totalResult.ToString() };
// 调试日志优化使用StringBuilder减少拼接开销高频场景更友好
Console.WriteLine($"OP020-2({ip})读取结果:产品型号={productModel},运行状态={runStatusDesc},总结果={qualificationFlag}");
// 5. 构建数据实体优化减少Trim/空值判断,提前兜底)
return new PlcProductionData
{
@ -1460,7 +1462,10 @@ namespace RIZO.Admin.WebApi.PLC.Service
Runstatus = runStatus,
OnlineStatus = onlineStatusDesc,
ProduceModel = produceModelDesc,
TrayNo = trayNo.ToString(), // int转string开销可忽略无需优化
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
TrayNo = trayNo.ToString(),
CreatedBy = "PLC",
CreatedTime = DateTime.Now
};
@ -1494,8 +1499,13 @@ namespace RIZO.Admin.WebApi.PLC.Service
var taskOnlineStatus = ReadPlcIntAsync(plc, _op020_3IntMap["设备在线状态"]);
var taskProduceModel = ReadPlcIntAsync(plc, _op020_3IntMap["生产模式"]);
// DInt任务单独提取避免字典开销
var taskActualOutput = ReadPlcIntAsync(plc, _op050IntMap["实际产量"]);
var taskQualifiedQty = ReadPlcIntAsync(plc, _op050IntMap["合格数量"]);
var taskFailedQty = ReadPlcIntAsync(plc, _op050IntMap["失败数量"]);
// 步骤2等待所有任务并行完成耗时=最慢的单个读取任务,而非总和)
await Task.WhenAll(taskRunStatus, taskMachineModel, taskOnlineStatus, taskProduceModel);
await Task.WhenAll(taskRunStatus, taskMachineModel, taskOnlineStatus, taskProduceModel, taskActualOutput, taskQualifiedQty, taskFailedQty);
// 步骤3获取结果带await确保值已返回无需额外判空
int runStatus = await taskRunStatus;
@ -1503,6 +1513,10 @@ namespace RIZO.Admin.WebApi.PLC.Service
int onlineStatus = await taskOnlineStatus;
int produceModel = await taskProduceModel;
int actualOutput = await taskActualOutput;
int qualifiedQty = await taskQualifiedQty;
int failedQty = await taskFailedQty;
// 2. 业务逻辑计算(优化:常量复用+减少冗余计算)
var reworkFlag = produceModel == 4 ? "1" : "0";
@ -1539,7 +1553,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
LineCode = "line2",
WorkstationCode = workstationCode,
WorkstationName = "热铆", // 补充工站名称
// 状态字段:直接赋值,无冗余转换
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ReworkFlag = reworkFlag,
Automanual = machineModel,
Runstatus = runStatus,
@ -1598,10 +1614,19 @@ namespace RIZO.Admin.WebApi.PLC.Service
{ "产品4结果", ReadPlcIntAsync(plc, _op020_4IntMap["产品4结果"]) }
};
// DInt双整数读取任务复用ReadPlcIntAsyncPLC底层兼容DInt转int
var dIntReadTasks = new Dictionary<string, Task<int>>
{
{ "实际产量", ReadPlcIntAsync(plc, _op050IntMap["实际产量"]) },
{ "合格数量", ReadPlcIntAsync(plc, _op050IntMap["合格数量"]) },
{ "失败数量", ReadPlcIntAsync(plc, _op050IntMap["失败数量"]) }
};
// 2. 并行等待所有读取任务完成合并任务列表单次WaitAll提升效率
var allTasks = new List<Task>();
allTasks.AddRange(stringReadTasks.Values);
allTasks.AddRange(intReadTasks.Values);
allTasks.AddRange(dIntReadTasks.Values);
await Task.WhenAll(allTasks).ConfigureAwait(false);
// 3. 提取读取结果(直接取值+空值兜底减少await重复调用
@ -1624,6 +1649,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
int product3Result = intReadTasks["产品3结果"].Result;
int product4Result = intReadTasks["产品4结果"].Result;
int actualOutput = dIntReadTasks["实际产量"].Result;
int qualifiedQty = dIntReadTasks["合格数量"].Result;
int failedQty = dIntReadTasks["失败数量"].Result;
// 4. 业务逻辑转换(极简逻辑,仅保留必要转换)
string reworkFlag = produceModel == 4 ? "1" : "0";
@ -1668,6 +1696,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
Product4Result = product1Result.ToString(),
SN1 = product1SN,
SN2 = product2SN,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
QualificationFlag = qualificationFlag
};
}
@ -1755,6 +1786,7 @@ namespace RIZO.Admin.WebApi.PLC.Service
int byPass = intReadTasks["ByPass"].Result;
int produceModel = intReadTasks["生产模式"].Result;
int trayNo = intReadTasks["托盘号"].Result;
int actualOutput = dIntReadTasks["实际产量"].Result;
int qualifiedQty = dIntReadTasks["合格数量"].Result;
int failedQty = dIntReadTasks["失败数量"].Result;
@ -1927,7 +1959,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
TrayNo = string.Empty,
CreatedBy = "PLC",
CreatedTime = DateTime.Now,
// 产量/节拍核心字段
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ProductionCycle = (int)Math.Round(cycleTime), // 节拍时间转整数秒
QualificationFlag = qualificationFlag,
// 备注记录产量信息(便于排查)
@ -1978,9 +2012,18 @@ namespace RIZO.Admin.WebApi.PLC.Service
{ "托盘号", ReadPlcIntAsync(plc, _op051ScanIntMap["托盘号"]) }
};
// DInt双整数读取任务复用ReadPlcIntAsyncPLC底层兼容DInt转int
var dIntReadTasks = new Dictionary<string, Task<int>>
{
{ "实际产量", ReadPlcIntAsync(plc, _op050IntMap["实际产量"]) },
{ "合格数量", ReadPlcIntAsync(plc, _op050IntMap["合格数量"]) },
{ "失败数量", ReadPlcIntAsync(plc, _op050IntMap["失败数量"]) }
};
// 2. 并行等待所有任务完成单次WaitAll最小化等待时间
var allTasks = new List<Task>();
allTasks.AddRange(stringTasks.Values);
allTasks.AddRange(dIntReadTasks.Values);
allTasks.AddRange(intTasks.Values);
await Task.WhenAll(allTasks).ConfigureAwait(false);
@ -2000,6 +2043,10 @@ namespace RIZO.Admin.WebApi.PLC.Service
var uploadRequest = intTasks["上传结果请求"].Result;
var trayNo = intTasks["托盘号"].Result.ToString(); // 转换为字符串,适配实体字段
int actualOutput = dIntReadTasks["实际产量"].Result;
int qualifiedQty = dIntReadTasks["合格数量"].Result;
int failedQty = dIntReadTasks["失败数量"].Result;
// 4. 核心逻辑转换(极简表达式,适配扫码工位业务)
var reworkFlag = produceModel == 4 ? "1" : "0"; // 4=返工模式→1其他→0
var produceModelDesc = produceModel switch
@ -2040,6 +2087,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
Product2SN = sn2,
Product3SN = sn3,
QualificationFlag = qualificationFlag,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
CreatedBy = "PLC",
CreatedTime = now,
};
@ -2178,6 +2228,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
Product1Result = pin1Result.ToString(),
Product2Result = pin2Result.ToString(),
QualificationFlag = qualificationFlag,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ProductionCycle = (int)Math.Round(cycleTime),
};
@ -2268,6 +2321,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
PinPressure1 = pressure.ToString(),
PinStroke1 = stroke.ToString(),
QualificationFlag = qualificationFlag,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ProductionCycle = (int)Math.Round(cycleTime),
CreatedBy = "PLC_System",
CreatedTime = now,
@ -2353,6 +2409,8 @@ namespace RIZO.Admin.WebApi.PLC.Service
var onlineStatus = intTasks["设备在线状态"].Result;
var produceModel = intTasks["生产模式"].Result;
var actualOutput = intTasks["实际产量"].Result;
var qualifiedQty = intTasks["合格数量"].Result;
var failedQty = intTasks["失败数量"].Result;
var productTotalResult = intTasks["产品总结果"].Result;
var screw1Result = intTasks["1#螺丝结果"].Result;
var screw2Result = intTasks["2#螺丝结果"].Result;
@ -2408,6 +2466,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
OnlineStatus = onlineStatusDesc,
ProduceModel = produceModelDesc,
QualificationFlag = qualificationFlag,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ProductionCycle = (int)Math.Round(cycleTime),
CreatedBy = "PLC",
CreatedTime = now,
@ -2482,7 +2543,10 @@ namespace RIZO.Admin.WebApi.PLC.Service
var taskTrayNo = ReadPlcIntAsync(plc, _op070_1IntMap["托盘号"]);
var taskCameraResult = ReadPlcIntAsync(plc, _op070_1IntMap["相机结果"]);
var taskStationResult = ReadPlcIntAsync(plc, _op070_1IntMap["站位结果"]);
// DInt任务单独提取避免字典开销
var taskActualOutput = ReadPlcIntAsync(plc, _op050IntMap["实际产量"]);
var taskQualifiedQty = ReadPlcIntAsync(plc, _op050IntMap["合格数量"]);
var taskFailedQty = ReadPlcIntAsync(plc, _op050IntMap["失败数量"]);
// ========== Real字段并行任务 ==========
var taskCycleTime = ReadPlcRealAsync(plc, _op070_1IntMap["节拍时间"]);
@ -2494,7 +2558,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel,
taskTrayNo, taskCameraResult, taskStationResult,
// Real任务
taskCycleTime);
taskCycleTime,
// DInt任务
taskActualOutput, taskQualifiedQty, taskFailedQty);
// 3. 获取并行读取结果(带空值兜底,避免后续空引用)
// 字符串字段
@ -2513,10 +2579,14 @@ namespace RIZO.Admin.WebApi.PLC.Service
int trayNo = await taskTrayNo;
int cameraResult = await taskCameraResult;
int stationResult = await taskStationResult;
int actualOutput = await taskActualOutput;
int qualifiedQty = await taskQualifiedQty;
int failedQty = await taskFailedQty;
// Real字段
float cycleTime = await taskCycleTime;
// 4. 异步复位保存请求(非阻塞,不影响读取效率)
//_ = ResetSaveRequestAsync(plc, ip, _op070_1IntMap["保存请求"]);
@ -2553,6 +2623,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
SN1 = sn1,
SN2 = sn2,
QualificationFlag = qualificationFlag,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ReworkFlag = reworkFlag,
Automanual = machineModel,
Runstatus = runStatus,
@ -2609,6 +2682,11 @@ namespace RIZO.Admin.WebApi.PLC.Service
// ========== Real字段并行任务 ==========
var taskCycleTime = ReadPlcRealAsync(plc, _op075IntMap["节拍时间"]);
// DInt任务单独提取避免字典开销
var taskActualOutput = ReadPlcIntAsync(plc, _op050IntMap["实际产量"]);
var taskQualifiedQty = ReadPlcIntAsync(plc, _op050IntMap["合格数量"]);
var taskFailedQty = ReadPlcIntAsync(plc, _op050IntMap["失败数量"]);
// 2. 等待所有并行任务完成(核心:耗时=最慢的单个读取任务,而非总和)
await Task.WhenAll(
// 字符串任务
@ -2617,7 +2695,10 @@ namespace RIZO.Admin.WebApi.PLC.Service
taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel,
taskTrayNo, taskStationResult,
// Real任务
taskCycleTime);
taskCycleTime
// DInt任务
, taskActualOutput, taskQualifiedQty, taskFailedQty
);
// 3. 获取并行读取结果(带空值兜底,避免后续空引用)
// 字符串字段
@ -2636,7 +2717,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
int produceModel = await taskProduceModel;
int trayNo = await taskTrayNo;
int stationResult = await taskStationResult;
int actualOutput = await taskActualOutput;
int qualifiedQty = await taskQualifiedQty;
int failedQty = await taskFailedQty;
// Real字段
float cycleTime = await taskCycleTime;
@ -2676,6 +2759,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
SN2 = sn2,
ChipSN = chipsn, // 保留芯片SN独有字段
QualificationFlag = qualificationFlag,
ActualOutQty = actualOutput.ToString(),
QualifiedQty = qualifiedQty.ToString(),
FailedQty = failedQty.ToString(),
ReworkFlag = reworkFlag,
Automanual = machineModel,
Runstatus = runStatus,
@ -2690,16 +2776,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
catch (Exception ex)
{
// 增强异常日志:包含堆栈,便于定位问题
Console.WriteLine($"OP075({ip})数据读取异常:{ex.Message}\n{ex.StackTrace}");
Console.WriteLine($"OP075({ip})数据读取异常:{ex.Message}");
// 异常时返回基础实体,避免业务空引用
return new PlcProductionData
{
PlcIp = ip,
WorkstationCode = workstationCode,
CreatedBy = "PLC",
CreatedTime = DateTime.Now,
OnlineStatus = "读取异常"
};
return null;
}
}
@ -5479,17 +5558,22 @@ namespace RIZO.Admin.WebApi.PLC.Service
try
{
var routingCode = await Context.Queryable<ProcessRouting>()
// 在方法内创建上下文并使用using确保自动释放
using var context = DbScoped.SugarScope.CopyNew();
if (context == null)
throw new InvalidOperationException("无法创建数据库上下文请检查SqlSugar配置");
var routingCode = string.Empty;
try
{
routingCode = await context.Queryable<ProcessRouting>()
.LeftJoin<ProcessOperation>((r, o) => r.RoutingCode == o.FkRoutingCode)
.Where((r, o) => r.FkProductMaterialCode == strSN && r.Status == 1)
.Select((r, o) => o.FkRoutingCode)
.Select((r, o) => o.FkRoutingCode)
.FirstAsync(); // 直接取第一条避免ToList+First的双重开销
// 4.1 工艺路线校验(合并判断,减少分支)
if (string.IsNullOrWhiteSpace(routingCode))
{
Console.WriteLine($"[{now:HH:mm:ss}] {plcName} - {strSN} 工艺路线无效,跳过记录");
return;
}
catch
{
}
// 5. 返工状态判断(无分配判断,直接赋值)
@ -5510,7 +5594,7 @@ namespace RIZO.Admin.WebApi.PLC.Service
};
// 7. 插入数据库批量插入优化减少IO交互
var insertCount = await Context.Insertable(askOutStation).ExecuteCommandAsync();
var insertCount = await context.Insertable(askOutStation).ExecuteCommandAsync();
}
catch (ArgumentNullException ex)

View File

@ -23,7 +23,7 @@ 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.11.56";
private readonly string _ipAddress = "192.168.1.111";
private readonly CpuType _cpuType = CpuType.S71500;
private string WorkstationCode;
private readonly SqlSugarClient Context;