// 统一引入所有必要命名空间 using MDM.Services.Plant; using Microsoft.Extensions.Options; using NPOI.SS.Formula.Functions; using Quartz; using RIZO.Admin.WebApi.PLC.Model; using RIZO.Common; using S7.Net; using SqlSugar; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace RIZO.Admin.WebApi.PLC.Service { /// /// PLC通信服务(封装连接、读写、生产数据采集等功能) /// public class PlcService : IDisposable { #region 字段与构造函数 // 标记是否已释放资源,避免重复释放 private bool _disposed = false; // PLC配置参数(从配置文件注入) private readonly List _plcConfigs; private readonly GlobalPlcConfig _globalConfig; private PlcProductionDataService _plcProductionDataService = new PlcProductionDataService(); private PlantWorkstationService _plantWorkstationService = new PlantWorkstationService(); // 先在类中添加2个核心优化字段(支撑高频访问) private readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(15, 50); // 限制20并发(适配50台PLC) private readonly ConcurrentDictionary _plcConnPool = new(); // 连接池 #region PLC地址块儿映射 //MES返回PLC请求映射 private readonly Dictionary _mesIntReturnMap = new() { { "设备使能", "DB1050.DBW0" }, // Int { "工位开始查询结果", "DB1050.DBW2000" }, // Int { "保存结果", "DB1050.DBW2002" }, // Int }; private readonly Dictionary _mesStringReturnMap = new() { { "产品型号", ("DB1001.DBB1016", 14) }, // String[14] { "订单下发", ("DB1001.DBB1032", 50) }, // String[50] }; // OP020-2 专属地址映射(合盖工位,DB1001) private readonly Dictionary _op020_2StringMap = new() { { "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte { "产品型号", ("DB1001.DBB1000", 48) }, // String[48] { "产品名称", ("DB1001.DBB1054", 48) }, // String[48] { "SN", ("DB1001.DBB2106", 48) } // String[48] }; private readonly Dictionary _op020_2IntMap = 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=返工模式;8=换型模式;16=预热模式 //{ "实际产量", "DB1001.DBD1104" }, // DInt //{ "合格数量", "DB1001.DBD1108" }, // DInt //{ "失败数量", "DB1001.DBD1112" }, // DInt { "上传请求", "DB1001.DBW2002" }, // Int - 1=请求开始,0=无请求 { "托盘号", "DB1001.DBW2054" }, // Int { "总结果", "DB1001.DBW2158" }, // Int - 一个托盘上传四次结果 { "节拍时间", "DB1001.DBD2988" } // Real }; // OP020-3 专属地址映射(热铆工位,DB1001) private readonly Dictionary _op020_3IntMap = new() { { "运行状态", "DB1001.DBW0" }, // Int - 1=空闲,2=运行中,3=故障 { "设备模式", "DB1001.DBW2" }, // Int - 1=空模式;2=手动;4=初始化;8=自动;16=CycleStop { "设备在线状态", "DB1001.DBW4" }, // Int - 1=离线,0=在线 { "生产模式", "DB1001.DBW8" }, // Int - 1=正常模式;2=清线模式;4=返工模式;8=换型模式;16=预热模式 }; // OP020-4 专属地址映射(pin压合&视觉检查工位,DB1001) private readonly Dictionary _op020_4StringMap = new() { { "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte { "产品型号", ("DB1001.DBB1000", 48) }, // String[48] { "产品名称", ("DB1001.DBB1054", 48) }, // String[48] { "产品1SN", ("DB1001.DBB2230", 40) }, // String[40] { "产品2SN", ("DB1001.DBB2360", 40) }, // String[40] { "产品3SN", ("DB1001.DBB2490", 40) }, // String[40] { "产品4SN", ("DB1001.DBB2620", 40) } // String[40] }; private readonly Dictionary _op020_4IntMap = 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=返工模式;8=换型模式;16=预热模式 // 上传请求 { "产品1上传请求", "DB1001.DBW2002" }, // Int { "产品2上传请求", "DB1001.DBW2004" }, // Int { "产品3上传请求", "DB1001.DBW2006" }, // Int { "产品4上传请求", "DB1001.DBW2008" }, // Int // 托盘号 { "托盘号", "DB1001.DBW2070" }, // Int // 产品结果 { "产品1结果", "DB1001.DBW2274" }, // Int - 1:OK 2:NG { "产品2结果", "DB1001.DBW2404" }, // Int - 1:OK 2:NG { "产品3结果", "DB1001.DBW2534" }, // Int - 1:OK 2:NG { "产品4结果", "DB1001.DBW2664" }, // Int - 1:OK 2:NG }; // OP070-1 专属地址映射 (点散热胶GF1500工位) OP070-2 点散热胶TC4060 OP070-3 点散热胶GF3500 private readonly Dictionary _op070_1StringMap = new() { //{ "报警信息", ("DB1011.DBB58", 48) }, // Array[1..48] of Byte { "订单名称", ("DB1011.DBB1000", 48) }, // String[48] { "产品名称", ("DB1011.DBB1054", 48) }, // String[48] { "产品型号", ("DB1011.DBB2006", 28) }, // String[28] { "SN_1", ("DB1011.DBB2100", 28) }, // String[28] { "SN_2", ("DB1011.DBB2134", 28) } // String[28] }; private readonly Dictionary _op070_1IntMap = new() { { "运行状态", "DB1011.DBW0" }, // Int { "设备模式", "DB1011.DBW2" }, // Int { "设备在线状态", "DB1011.DBW4" }, // Int { "ByPass", "DB1011.DBW6" }, // Int { "生产模式", "DB1011.DBW8" }, // Int { "实际产量", "DB1011.DBD1104" }, // DInt //{ "合格数量", "DB1011.DBD1108" }, // DInt //{ "失败数量", "DB1011.DBD1112" }, // DInt { "查询请求", "DB1011.DBW2000" }, // Int { "保存请求", "DB1011.DBW2002" }, // Int { "托盘号", "DB1011.DBW2004" }, // Int { "相机结果", "DB1011.DBW2164" }, // Int { "站位结果", "DB1011.DBW2166" }, // Int { "节拍时间", "DB1011.DBD2168" }, // Real }; // OP075 专属地址映射 PWM折弯&装配 private readonly Dictionary _op075StringMap = new() { //{ "报警信息", ("DB1011.DBB58", 48) }, // Array[1..48] of Byte { "订单名称", ("DB1011.DBB1000", 48) }, // String[48] { "产品名称", ("DB1011.DBB1054", 48) }, // String[48] { "产品型号", ("DB1011.DBB2006", 28) }, // String[28] { "SN_1", ("DB1011.DBB2100", 28) }, // String[28] { "SN_2", ("DB1011.DBB2192", 28) }, // String[28] { "芯片SN", ("DB1011.DBB2222", 28) } // String[28] }; private readonly Dictionary _op075IntMap = new() { { "运行状态", "DB1011.DBW0" }, // Int { "设备模式", "DB1011.DBW2" }, // Int { "设备在线状态", "DB1011.DBW4" }, // Int { "ByPass", "DB1011.DBW6" }, // Int { "生产模式", "DB1011.DBW8" }, // Int { "实际产量", "DB1011.DBD1104" }, // DInt //{ "合格数量", "DB1011.DBD1108" }, // DInt //{ "失败数量", "DB1011.DBD1112" }, // DInt { "查询请求", "DB1011.DBW2000" }, // Int { "保存请求", "DB1011.DBW2002" }, // Int { "托盘号", "DB1011.DBW2004" }, // Int { "站位结果", "DB1011.DBW2252" }, // Int { "节拍时间", "DB1011.DBD2284" } // Real }; // OP080-1 专属地址映射 PCBA组装&拧紧 private readonly Dictionary _op080_1StringMap = new() { //{ "报警信息", ("DB1016.DBB58", 48) }, // Array[1..48] of Byte { "订单名称", ("DB1016.DBB1000", 48) }, // String[48] { "产品名称", ("DB1016.DBB1054", 48) }, // String[48] { "合装位机壳_SN", ("DB1016.DBB2100", 28) }, // String[28] { "合装位PCB_SN", ("DB1016.DBB2130", 28) }, // String[28] { "拧紧位机壳_SN", ("DB1016.DBB2904", 28) }, // String[28] { "拧紧位PCB_SN", ("DB1016.DBB3092", 28) }, // String[28] }; private readonly Dictionary _op080_1IntMap = new() { { "运行状态", "DB1016.DBW0" }, // Int { "设备模式", "DB1016.DBW2" }, // Int { "设备在线状态", "DB1016.DBW4" }, // Int { "ByPass", "DB1016.DBW6" }, // Int { "生产模式", "DB1016.DBW8" }, // Int { "实际产量", "DB1016.DBD1104" }, // DInt { "合格数量", "DB1016.DBD1108" }, // DInt { "失败数量", "DB1016.DBD1112" }, // DInt { "合装工位查询请求", "DB1016.DBW2000" }, // Int { "合装结果保存请求", "DB1016.DBW2004" }, // Int { "拧紧结果保存请求", "DB1016.DBW2006" }, // Int { "合装位托盘号", "DB1016.DBW2100" }, // Int { "拧紧位托盘号", "DB1016.DBW3152" }, // Int // 2号螺钉 { "2号螺钉_结果", "DB1016.DBW3164" }, // Int { "2号螺钉_扭矩", "DB1016.DBD3166" }, // Real { "2号螺钉_深度", "DB1016.DBD3170" }, // Real { "2号螺钉_角度", "DB1016.DBD3174" }, // Real { "2号螺钉_拧紧时间", "DB1016.DBD3178" }, // Real // 3号螺钉 { "3号螺钉_结果", "DB1016.DBW3182" }, // Int { "3号螺钉_扭矩", "DB1016.DBD3184" }, // Real { "3号螺钉_深度", "DB1016.DBD3188" }, // Real { "3号螺钉_角度", "DB1016.DBD3192" }, // Real { "3号螺钉_拧紧时间", "DB1016.DBD3196" }, // Real // 4号螺钉 { "4号螺钉_结果", "DB1016.DBW3200" }, // Int { "4号螺钉_扭矩", "DB1016.DBD3202" }, // Real { "4号螺钉_深度", "DB1016.DBD3206" }, // Real { "4号螺钉_角度", "DB1016.DBD3210" }, // Real { "4号螺钉_拧紧时间", "DB1016.DBD3214" }, // Real // 1号螺钉 { "1号螺钉_结果", "DB1016.DBW3218" }, // Int { "1号螺钉_扭矩", "DB1016.DBD3220" }, // Real { "1号螺钉_深度", "DB1016.DBD3224" }, // Real { "1号螺钉_角度", "DB1016.DBD3228" }, // Real { "1号螺钉_拧紧时间", "DB1016.DBD3232" }, // Real // 5号螺钉 { "5号螺钉_结果", "DB1016.DBW3236" }, // Int { "5号螺钉_扭矩", "DB1016.DBD3238" }, // Real { "5号螺钉_深度", "DB1016.DBD3242" }, // Real { "5号螺钉_角度", "DB1016.DBD3246" }, // Real { "5号螺钉_拧紧时间", "DB1016.DBD3250" }, // Real // 6号螺钉 { "6号螺钉_结果", "DB1016.DBW3254" }, // Int { "6号螺钉_扭矩", "DB1016.DBD3256" }, // Real { "6号螺钉_深度", "DB1016.DBD3260" }, // Real { "6号螺钉_角度", "DB1016.DBD3264" }, // Real { "6号螺钉_拧紧时间", "DB1016.DBD3268" }, // Real // 7号螺钉 { "7号螺钉_结果", "DB1016.DBW3272" }, // Int { "7号螺钉_扭矩", "DB1016.DBD3274" }, // Real { "7号螺钉_深度", "DB1016.DBD3278" }, // Real { "7号螺钉_角度", "DB1016.DBD3282" }, // Real { "7号螺钉_拧紧时间", "DB1016.DBD3286" }, // Real }; // OP080-2 专属地址映射(PCBA拧紧工位,DB1001) private readonly Dictionary _op080_2StringMap = new() { // { "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte { "产品型号", ("DB1001.DBB1000", 48) }, // String[48] { "产品名称", ("DB1001.DBB1054", 48) }, // String[48] { "SN1", ("DB1001.DBB2100", 28) }, // String[28] - 条码查询 { "SN2", ("DB1001.DBB2260", 28) }, // String[28] - 结果上传条码 }; private readonly Dictionary _op080_2IntMap = 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=正常 //{ "实际产量", "DB1001.DBD1104" }, // DInt //{ "合格数量", "DB1001.DBD1108" }, // DInt //{ "失败数量", "DB1001.DBD1112" }, // DInt // 请求信号 { "查询请求", "DB1001.DBW2000" }, // Int - 1=请求开始,0=无请求 { "保存请求", "DB1001.DBW2002" }, // Int - 1=请求,0=无请求 // 托盘号 { "托盘号", "DB1001.DBW2290" }, // Int // 产品总结果 { "产品总结果", "DB1001.DBW2292" }, // Int // 1号螺钉 { "1号螺钉_结果", "DB1001.DBW2294" }, // Int { "1号螺钉_扭矩", "DB1001.DBD2296" }, // Real { "1号螺钉_深度", "DB1001.DBD2300" }, // Real { "1号螺钉_角度", "DB1001.DBD2304" }, // Real { "1号螺钉_拧紧时间", "DB1001.DBD2308" }, // Real // 2号螺钉 { "2号螺钉_结果", "DB1001.DBW2312" }, // Int { "2号螺钉_扭矩", "DB1001.DBD2314" }, // Real { "2号螺钉_深度", "DB1001.DBD2318" }, // Real { "2号螺钉_角度", "DB1001.DBD2322" }, // Real { "2号螺钉_拧紧时间", "DB1001.DBD2326" }, // Real // 3号螺钉 { "3号螺钉_结果", "DB1001.DBW2330" }, // Int { "3号螺钉_扭矩", "DB1001.DBD2332" }, // Real { "3号螺钉_深度", "DB1001.DBD2336" }, // Real { "3号螺钉_角度", "DB1001.DBD2340" }, // Real { "3号螺钉_拧紧时间", "DB1001.DBD2344" }, // Real // 4号螺钉 { "4号螺钉_结果", "DB1001.DBW2348" }, // Int { "4号螺钉_扭矩", "DB1001.DBD2350" }, // Real { "4号螺钉_深度", "DB1001.DBD2354" }, // Real { "4号螺钉_角度", "DB1001.DBD2358" }, // Real { "4号螺钉_拧紧时间", "DB1001.DBD2362" }, // Real // 5号螺钉 { "5号螺钉_结果", "DB1001.DBW2366" }, // Int { "5号螺钉_扭矩", "DB1001.DBD2368" }, // Real { "5号螺钉_深度", "DB1001.DBD2372" }, // Real { "5号螺钉_角度", "DB1001.DBD2376" }, // Real { "5号螺钉_拧紧时间", "DB1001.DBD2380" }, // Real // 6号螺钉 { "6号螺钉_结果", "DB1001.DBW2384" }, // Int { "6号螺钉_扭矩", "DB1001.DBD2386" }, // Real { "6号螺钉_深度", "DB1001.DBD2390" }, // Real { "6号螺钉_角度", "DB1001.DBD2394" }, // Real { "6号螺钉_拧紧时间", "DB1001.DBD2398" }, // Real // 7号螺钉 { "7号螺钉_结果", "DB1001.DBW2402" }, // Int { "7号螺钉_扭矩", "DB1001.DBD2404" }, // Real { "7号螺钉_深度", "DB1001.DBD2408" }, // Real { "7号螺钉_角度", "DB1001.DBD2412" }, // Real { "7号螺钉_拧紧时间", "DB1001.DBD2416" }, // Real { "节拍时间", "DB1001.DBD3004" }, // Real }; #endregion /// /// 构造函数(依赖注入获取PLC配置) /// /// 所有PLC的连接配置 /// PLC全局超时配置 public PlcService(IOptions globalConfig) { _globalConfig = globalConfig?.Value ?? throw new ArgumentNullException(nameof(globalConfig), "PLC全局配置不能为空"); //初始化plcConfigs _plcConfigs = initPlcConfigs(_plcConfigs); } #endregion #region 核心业务方法 /// /// 读取PLC生产数据(优化版:连接池+并发控制+OP070-1/OP080专属适配) /// /// PLC IP地址 /// PLC名称(支持OP070-1/OP080) /// 机架号 /// 槽位号 /// PLC型号(默认S7-1500) /// 读取结果(状态+数据+消息) /// /// PLC生产数据读取核心方法(优化版) /// /// 成功状态/生产数据/提示消息 public async Task<(bool Success, PlcProductionData Data, string Message)> ReadProductionDataAsync( string ip, string plcName, short rack, short slot, CpuType cpuType = CpuType.S71500) { // 1. 参数校验(精简返回,直接判空返回) if (string.IsNullOrWhiteSpace(ip)) return (false, null, "PLC IP地址不能为空"); if (string.IsNullOrWhiteSpace(plcName)) return (false, null, "PLC名称不能为空"); if (rack is < 0 or > 10) return (false, null, $"PLC机架号{rack}无效(有效值0-10)"); if (slot is < 0 or > 4) return (false, null, $"PLC槽位号{slot}无效(有效值0-4)"); Plc plc = null; bool isConnReused = false; var poolKey = $"{ip}_{rack}_{slot}_{cpuType}"; PlcProductionData prodData = null; int iQueryRequest = 0; int iSaveRequest = 0; string strSaveRequest = string.Empty; // 2. 并发控制(确保释放,外层无try) await _concurrencySemaphore.WaitAsync(); try { // 3. 连接池复用+有效性校验(精简逻辑,移除冗余变量) if (_plcConnPool.TryGetValue(poolKey, out var poolItem) && poolItem.Client is { IsConnected: true }) { plc = poolItem.Client; _plcConnPool.TryUpdate(poolKey, (plc, DateTime.Now), poolItem); isConnReused = true; } else { plc = CreatePlcClient(cpuType, ip, rack, slot); try { await OpenPlcConnectionAsync(plc); if (!plc.IsConnected) return (false, null, $"{plcName}连接失败(含2次重试)"); _plcConnPool.TryAdd(poolKey, (plc, DateTime.Now)); } catch { ReleasePlcConnection(plc); return (false, null, $"{plcName}连接失败(含2次重试)"); } } // 4. 多工位请求状态读取(精简分支,保留原逻辑,移除冗余注释) iQueryRequest = 0; iSaveRequest = 0; strSaveRequest = string.Empty; switch (plcName) { case "OP020-2": iSaveRequest = await ReadPlcIntAsync(plc, _op020_2IntMap["上传请求"]); break; case "OP020-3": iSaveRequest = 1; break; case "OP020-4": var s4_1 = await ReadPlcIntAsync(plc, _op020_4IntMap["产品1上传请求"]); var s4_2 = await ReadPlcIntAsync(plc, _op020_4IntMap["产品2上传请求"]); var s4_3 = await ReadPlcIntAsync(plc, _op020_4IntMap["产品3上传请求"]); var s4_4 = await ReadPlcIntAsync(plc, _op020_4IntMap["产品4上传请求"]); iSaveRequest = (s4_1 == 1 || s4_2 == 1 || s4_3 == 1 || s4_4 == 1) ? 1 : 0; break; case "OP070-1": case "OP070-2": case "OP070-3": iQueryRequest = await ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]); iSaveRequest = await ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]); break; case "OP075": iQueryRequest = await ReadPlcIntAsync(plc, _op075IntMap["查询请求"]); iSaveRequest = await ReadPlcIntAsync(plc, _op075IntMap["保存请求"]); break; case "OP080-1": iQueryRequest = await ReadPlcIntAsync(plc, _op080_1IntMap["合装工位查询请求"]); var s8_1 = await ReadPlcIntAsync(plc, _op080_1IntMap["合装结果保存请求"]); var s8_2 = await ReadPlcIntAsync(plc, _op080_1IntMap["拧紧结果保存请求"]); if (s8_1 == 1) { iSaveRequest = 1; strSaveRequest = "合装结果保存请求"; } if (s8_2 == 1) { iSaveRequest = 1; strSaveRequest = string.IsNullOrEmpty(strSaveRequest) ? "拧紧结果保存请求" : $"{strSaveRequest},拧紧结果保存请求"; } break; } // 无效请求直接返回(空消息保留原逻辑) if (iQueryRequest != 1 && iSaveRequest != 1) return (false, null, string.Empty); // 处理查询请求(预留扩展,保留原空逻辑) if (iQueryRequest == 1) { // 后续查询请求处理逻辑写此处 } // 处理保存请求(核心数据读取) if (iSaveRequest == 1) { prodData = plcName switch { "OP020-2" => await ReadOP020_2DataAsync(plc, ip, plcName), "OP020-3" => await ReadOP020_3DataAsync(plc, ip, plcName), "OP070-1" or "OP070-2" or "OP070-3" => await ReadOP070_1DataAsync(plc, ip, plcName), "OP075" => await ReadOP075DataAsync(plc, ip, plcName), "OP080-1" => await ReadOP080_1DataAsync(plc, ip, plcName, strSaveRequest), "OP080-2" => await ReadOP080_2DataAsync(plc, ip, plcName), _ => prodData }; // 统一空值兜底(避免空引用,精简判空) if (prodData != null) { prodData.QualificationFlag ??= "0"; prodData.ReworkFlag ??= "0"; prodData.ProductionCycle ??= 0; // 异步保存数据(规范ContinueWith,增加plc非空校验) _ = Task.Run(() => _plcProductionDataService.AddPlcProductionData(prodData)) .ContinueWith(t => { if (t.IsFaulted && plc is { IsConnected: true }) { WritePlcSaveRequestResult(plc, ip, plcName, prodData, "6"); Console.WriteLine($"{plcName}({ip})数据保存失败:{t.Exception?.InnerException?.Message ?? t.Exception?.Message ?? "未知错误"}"); } }, TaskContinuationOptions.OnlyOnFaulted); } } // 个性化返回消息+写入成功返回值 var successMsg = isConnReused ? $"{plcName}生产数据读取成功(复用连接)" : $"{plcName}生产数据读取成功(新建连接)"; WritePlcSaveRequestResult(plc, ip, plcName, prodData, "1"); return (true, prodData, successMsg); } catch (Exception ex) { // 精细化异常日志,保留堆栈 Console.WriteLine($"{plcName}({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}"); return (false, null, $"{plcName}生产数据读取失败:{ex.Message}"); } finally { // 资源绝对释放:防止死锁+连接泄漏,增加多重校验 try { if (!isConnReused && plc != null) { ReleasePlcConnection(plc); } } catch (Exception ex) { Console.WriteLine($"{plcName}({ip})释放PLC连接失败:{ex.Message}"); } finally { // 确保信号量释放,无论任何情况 _concurrencySemaphore.Release(); } } } #region 工位专属读取方法 /// /// 读取OP020-2数据(合盖工位 /// private async Task ReadOP020_2DataAsync(Plc plc, string ip, string workstationCode) { // 前置校验:PLC连接无效直接返回,避免无效操作 if (plc == null || !plc.IsConnected) { Console.WriteLine($"OP020-2({ip})PLC连接无效,跳过数据读取"); return null; } try { // 1. 并行读取所有PLC字段 // 1.1 准备字符串字段读取任务(并行) var taskProductModel = ReadPlcStringAsync(plc, _op020_2StringMap["产品型号"].Addr, _op020_2StringMap["产品型号"].Len); var taskProductName = ReadPlcStringAsync(plc, _op020_2StringMap["产品名称"].Addr, _op020_2StringMap["产品名称"].Len); var taskSN = ReadPlcStringAsync(plc, _op020_2StringMap["SN"].Addr, _op020_2StringMap["SN"].Len); // 1.2 准备整数字段读取任务(并行) var taskRunStatus = ReadPlcIntAsync(plc, _op020_2IntMap["运行状态"]); var taskMachineModel = ReadPlcIntAsync(plc, _op020_2IntMap["设备模式"]); var taskOnlineStatus = ReadPlcIntAsync(plc, _op020_2IntMap["设备在线状态"]); var taskProduceModel = ReadPlcIntAsync(plc, _op020_2IntMap["生产模式"]); var taskTrayNo = ReadPlcIntAsync(plc, _op020_2IntMap["托盘号"]); var taskTotalResult = ReadPlcIntAsync(plc, _op020_2IntMap["总结果"]); // 1.3 等待所有并行任务完成(关键:耗时=最慢的单个读取任务,而非总和) await Task.WhenAll( taskProductModel, taskProductName, taskSN, taskRunStatus, taskMachineModel, taskOnlineStatus, taskProduceModel, taskTrayNo, taskTotalResult); // 2. 获取读取结果(带空值兜底,避免后续空引用) string productModel = await taskProductModel ?? string.Empty; string productName = await taskProductName ?? string.Empty; string sn = await taskSN ?? string.Empty; int runStatus = await taskRunStatus; int machineModel = await taskMachineModel; int onlineStatus = await taskOnlineStatus; int produceModel = await taskProduceModel; int trayNo = await taskTrayNo; int totalResult = await taskTotalResult; // 3. 异步复位上传请求(非阻塞,不影响数据读取效率) _ = ResetUploadRequestAsync(plc, ip, _op020_2IntMap["上传请求"]); // 4. 业务逻辑计算(优化:提前定义常量,减少重复计算) string reworkFlag = produceModel == 4 ? "1" : "0"; string produceModelDesc = produceModel switch { 1 => "正常模式", 2 => "清线模式", 4 => "返工模式", 8 => "换型模式", 16 => "预热模式", _ => $"未知({produceModel})" }; string runStatusDesc = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; 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 { PlcIp = ip, // 提前确保ip非空,无需Trim(外层已校验) OccurTime = DateTime.Now, LineCode = "line2", WorkstationCode = workstationCode, ProductModel = productModel, ProductName = productName, ProductCode = sn, SN1 = sn, SN2 = sn, QualificationFlag = qualificationFlag, ReworkFlag = reworkFlag, Automanual = machineModel, Runstatus = runStatus, OnlineStatus = onlineStatusDesc, ProduceModel = produceModelDesc, TrayNo = trayNo.ToString(), // int转string开销可忽略,无需优化 CreatedBy = "PLC", CreatedTime = DateTime.Now }; } catch (Exception ex) { // 增强异常日志:包含堆栈,便于定位问题 Console.WriteLine($"OP020-2({ip})数据读取异常:{ex.Message}\n{ex.StackTrace}"); return null; } } /// /// 读取OP020-3数据(热铆工位) /// private async Task ReadOP020_3DataAsync(Plc plc, string ip, string workstationCode) { // 前置核心校验:避免无效PLC操作 if (plc == null || !plc.IsConnected) { Console.WriteLine($"OP020-3({ip})PLC连接无效,跳过数据读取"); return null; } try { // 1. 真正的并行读取所有Int字段(核心效率优化) // 步骤1:创建所有异步读取任务(仅发起请求,不等待) var taskRunStatus = ReadPlcIntAsync(plc, _op020_3IntMap["运行状态"]); var taskMachineModel = ReadPlcIntAsync(plc, _op020_3IntMap["设备模式"]); var taskOnlineStatus = ReadPlcIntAsync(plc, _op020_3IntMap["设备在线状态"]); var taskProduceModel = ReadPlcIntAsync(plc, _op020_3IntMap["生产模式"]); // 步骤2:等待所有任务并行完成(耗时=最慢的单个读取任务,而非总和) await Task.WhenAll(taskRunStatus, taskMachineModel, taskOnlineStatus, taskProduceModel); // 步骤3:获取结果(带await确保值已返回,无需额外判空) int runStatus = await taskRunStatus; int machineModel = await taskMachineModel; int onlineStatus = await taskOnlineStatus; int produceModel = await taskProduceModel; // 2. 业务逻辑计算(优化:常量复用+减少冗余计算) var reworkFlag = produceModel == 4 ? "1" : "0"; // 预定义常量减少字符串创建(高频场景累计收益) string produceModelDesc = produceModel switch { 1 => "正常模式", 2 => "清线模式", 4 => "返工模式", 8 => "换型模式", 16 => "预热模式", _ => $"未知({produceModel})" }; string runStatusDesc = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; string onlineStatusDesc = onlineStatus == 1 ? "离线" : "在线"; // 调试日志(优化:减少字符串拼接开销,保留核心信息) Console.WriteLine($"OP020-3({ip})读取结果:运行状态={runStatusDesc},生产模式={produceModelDesc}"); // 3. 构建数据实体(优化:减少冗余操作,提前兜底) return new PlcProductionData { // 基础字段:ip外层已校验非空,无需Trim(减少字符串操作) PlcIp = ip, OccurTime = DateTime.Now, LineCode = "line2", WorkstationCode = workstationCode, // 状态字段:直接赋值,无冗余转换 ReworkFlag = reworkFlag, Automanual = machineModel, Runstatus = runStatus, OnlineStatus = onlineStatusDesc, ProduceModel = produceModelDesc, // 系统字段:固定值直接赋值 CreatedBy = "PLC", CreatedTime = DateTime.Now }; } catch (Exception ex) { // 增强异常日志:包含堆栈,便于定位问题(不影响效率,提升可维护性) Console.WriteLine($"OP020-3({ip})数据读取异常:{ex.Message}\n{ex.StackTrace}"); return null; } } /// /// OP070-1数据读取 - 效率优化版 /// private async Task ReadOP070_1DataAsync(Plc plc, string ip, string workstationCode) { // 前置核心校验:避免无效PLC操作(节省10+ms无效耗时) if (plc == null || !plc.IsConnected) { Console.WriteLine($"OP070-1({ip})PLC连接无效,跳过数据读取"); // 若业务允许,可返回空实体而非抛异常,根据实际场景调整 return new PlcProductionData { PlcIp = ip, WorkstationCode = workstationCode }; } try { // 1. 真正的并行读取所有字段(核心效率优化) // ========== 字符串字段并行任务 ========== var taskProductName = ReadPlcStringAsync(plc, _op070_1StringMap["产品名称"].Addr, _op070_1StringMap["产品名称"].Len); var taskOrderName = ReadPlcStringAsync(plc, _op070_1StringMap["订单名称"].Addr, _op070_1StringMap["订单名称"].Len); var taskProductModel = ReadPlcStringAsync(plc, _op070_1StringMap["产品型号"].Addr, _op070_1StringMap["产品型号"].Len); var taskSN1 = ReadPlcStringAsync(plc, _op070_1StringMap["SN_1"].Addr, _op070_1StringMap["SN_1"].Len); var taskSN2 = ReadPlcStringAsync(plc, _op070_1StringMap["SN_2"].Addr, _op070_1StringMap["SN_2"].Len); // ========== Int字段并行任务 ========== var taskRunStatus = ReadPlcIntAsync(plc, _op070_1IntMap["运行状态"]); var taskMachineModel = ReadPlcIntAsync(plc, _op070_1IntMap["设备模式"]); var taskOnlineStatus = ReadPlcIntAsync(plc, _op070_1IntMap["设备在线状态"]); var taskByPass = ReadPlcIntAsync(plc, _op070_1IntMap["ByPass"]); var taskProduceModel = ReadPlcIntAsync(plc, _op070_1IntMap["生产模式"]); var taskQueryReq = ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]); var taskSaveReq = ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]); var taskTrayNo = ReadPlcIntAsync(plc, _op070_1IntMap["托盘号"]); var taskCameraResult = ReadPlcIntAsync(plc, _op070_1IntMap["相机结果"]); var taskStationResult = ReadPlcIntAsync(plc, _op070_1IntMap["站位结果"]); // ========== Real字段并行任务 ========== var taskCycleTime = ReadPlcRealAsync(plc, _op070_1IntMap["节拍时间"]); // 2. 等待所有并行任务完成(核心:耗时=最慢的单个读取任务,而非总和) await Task.WhenAll( // 字符串任务 taskProductName, taskOrderName, taskProductModel, taskSN1, taskSN2, // Int任务 taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel, taskQueryReq, taskSaveReq, taskTrayNo, taskCameraResult, taskStationResult, // Real任务 taskCycleTime); // 3. 获取并行读取结果(带空值兜底,避免后续空引用) // 字符串字段 string productName = await taskProductName ?? string.Empty; string orderName = await taskOrderName ?? string.Empty; string productModel = await taskProductModel ?? string.Empty; string sn1 = await taskSN1 ?? string.Empty; string sn2 = await taskSN2 ?? string.Empty; // Int字段 int runStatus = await taskRunStatus; int machineModel = await taskMachineModel; int onlineStatus = await taskOnlineStatus; int byPass = await taskByPass; int produceModel = await taskProduceModel; int queryReq = await taskQueryReq; int saveReq = await taskSaveReq; int trayNo = await taskTrayNo; int cameraResult = await taskCameraResult; int stationResult = await taskStationResult; // Real字段 float cycleTime = await taskCycleTime; // 4. 异步复位保存请求(非阻塞,不影响读取效率) //_ = ResetSaveRequestAsync(plc, ip, _op070_1IntMap["保存请求"]); // 5. 业务逻辑计算(优化:减少冗余字符串创建) var reworkFlag = produceModel == 4 ? "1" : "0"; string produceModelDesc = produceModel switch { 1 => "正常模式", 2 => "清线模式", 4 => "返工模式", 8 => "换型模式", 16 => "预热模式", _ => $"未知({produceModel})" }; string runStatusDesc = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; string onlineStatusDesc = onlineStatus == 1 ? "离线" : "在线"; string qualificationFlag = stationResult switch { 1 => "1", 2 => "0", _ => stationResult.ToString() }; string cameraResultDesc = cameraResult switch { 1 => "OK", 2 => "NG", _ => $"未知({cameraResult})" }; // 调试日志:优化字符串拼接,保留核心信息 Console.WriteLine($"OP070-1({ip})读取结果:产品型号={productModel},运行状态={runStatusDesc},订单名称={orderName}"); // 6. 构建数据实体(优化:减少冗余操作,提前兜底) return new PlcProductionData { PlcIp = ip, // 外层已校验ip非空,无需Trim(减少字符串操作) OccurTime = DateTime.Now, LineCode = "line2", WorkstationCode = workstationCode, ProductModel = productModel, ProductName = productName, ProductCode = sn2, SN1 = sn1, SN2 = sn2, QualificationFlag = qualificationFlag, ReworkFlag = reworkFlag, Automanual = machineModel, Runstatus = runStatus, OnlineStatus = onlineStatusDesc, ProduceModel = produceModelDesc, TrayNo = trayNo.ToString(), CameraResult = cameraResultDesc, ProductionCycle = (int?)cycleTime, CreatedBy = "PLC", CreatedTime = DateTime.Now }; } catch (Exception ex) { // 增强异常日志:包含堆栈,便于定位问题 Console.WriteLine($"OP070-1({ip})数据读取异常:{ex.Message}\n{ex.StackTrace}"); return null; } } /// /// OP075数据读取 /// private async Task ReadOP075DataAsync(Plc plc, string ip, string workstationCode) { // 前置核心校验:避免无效PLC操作(节省10+ms无效耗时) if (plc == null || !plc.IsConnected) { Console.WriteLine($"OP075({ip})PLC连接无效,跳过数据读取"); // 返回基础实体避免业务空引用,可根据实际场景调整为return null return new PlcProductionData { PlcIp = ip, WorkstationCode = workstationCode }; } try { // 1. 真正的并行读取所有字段(核心效率优化) // ========== 字符串字段并行任务 ========== var taskProductName = ReadPlcStringAsync(plc, _op075StringMap["产品名称"].Addr, _op075StringMap["产品名称"].Len); var taskOrderName = ReadPlcStringAsync(plc, _op075StringMap["订单名称"].Addr, _op075StringMap["订单名称"].Len); var taskProductModel = ReadPlcStringAsync(plc, _op075StringMap["产品型号"].Addr, _op075StringMap["产品型号"].Len); var taskSN1 = ReadPlcStringAsync(plc, _op075StringMap["SN_1"].Addr, _op075StringMap["SN_1"].Len); var taskSN2 = ReadPlcStringAsync(plc, _op075StringMap["SN_2"].Addr, _op075StringMap["SN_2"].Len); var taskChipSN = ReadPlcStringAsync(plc, _op075StringMap["芯片SN"].Addr, _op075StringMap["芯片SN"].Len); // ========== Int字段并行任务 ========== var taskRunStatus = ReadPlcIntAsync(plc, _op075IntMap["运行状态"]); var taskMachineModel = ReadPlcIntAsync(plc, _op075IntMap["设备模式"]); var taskOnlineStatus = ReadPlcIntAsync(plc, _op075IntMap["设备在线状态"]); var taskByPass = ReadPlcIntAsync(plc, _op075IntMap["ByPass"]); var taskProduceModel = ReadPlcIntAsync(plc, _op075IntMap["生产模式"]); var taskQueryReq = ReadPlcIntAsync(plc, _op075IntMap["查询请求"]); var taskSaveReq = ReadPlcIntAsync(plc, _op075IntMap["保存请求"]); var taskTrayNo = ReadPlcIntAsync(plc, _op075IntMap["托盘号"]); var taskStationResult = ReadPlcIntAsync(plc, _op075IntMap["站位结果"]); // ========== Real字段并行任务 ========== var taskCycleTime = ReadPlcRealAsync(plc, _op075IntMap["节拍时间"]); // 2. 等待所有并行任务完成(核心:耗时=最慢的单个读取任务,而非总和) await Task.WhenAll( // 字符串任务 taskProductName, taskOrderName, taskProductModel, taskSN1, taskSN2, taskChipSN, // Int任务 taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel, taskQueryReq, taskSaveReq, taskTrayNo, taskStationResult, // Real任务 taskCycleTime); // 3. 获取并行读取结果(带空值兜底,避免后续空引用) // 字符串字段 string productName = await taskProductName ?? string.Empty; string orderName = await taskOrderName ?? string.Empty; string productModel = await taskProductModel ?? string.Empty; string sn1 = await taskSN1 ?? string.Empty; string sn2 = await taskSN2 ?? string.Empty; string chipsn = await taskChipSN ?? string.Empty; // Int字段 int runStatus = await taskRunStatus; int machineModel = await taskMachineModel; int onlineStatus = await taskOnlineStatus; int byPass = await taskByPass; int produceModel = await taskProduceModel; int queryReq = await taskQueryReq; int saveReq = await taskSaveReq; int trayNo = await taskTrayNo; int stationResult = await taskStationResult; // Real字段 float cycleTime = await taskCycleTime; // 4. 异步复位保存请求(非阻塞,不影响读取效率) //_ = ResetSaveRequestAsync(plc, ip, _op075IntMap["保存请求"]); // 5. 业务逻辑计算(优化:减少冗余字符串创建) var reworkFlag = produceModel == 4 ? "1" : "0"; string produceModelDesc = produceModel switch { 1 => "正常模式", 2 => "清线模式", 4 => "返工模式", 8 => "换型模式", 16 => "预热模式", _ => $"未知({produceModel})" }; string runStatusDesc = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; string onlineStatusDesc = onlineStatus == 1 ? "离线" : "在线"; string qualificationFlag = stationResult switch { 1 => "1", 2 => "0", _ => stationResult.ToString() }; // 调试日志:优化字符串拼接,保留核心信息 Console.WriteLine($"OP075({ip})读取结果:产品型号={productModel},运行状态={runStatusDesc},订单名称={orderName}"); // 6. 构建数据实体(优化:减少冗余操作,提前兜底) return new PlcProductionData { PlcIp = ip, // 外层已校验ip非空,无需Trim(减少字符串操作) OccurTime = DateTime.Now, LineCode = "line2", WorkstationCode = workstationCode, ProductModel = productModel, ProductName = productName, ProductCode = sn2, SN1 = sn1, SN2 = sn2, ChipSN = chipsn, // 保留芯片SN独有字段 QualificationFlag = qualificationFlag, ReworkFlag = reworkFlag, Automanual = machineModel, Runstatus = runStatus, OnlineStatus = onlineStatusDesc, ProduceModel = produceModelDesc, TrayNo = trayNo.ToString(), ProductionCycle = (int?)cycleTime, CreatedBy = "PLC", CreatedTime = DateTime.Now }; } catch (Exception ex) { // 增强异常日志:包含堆栈,便于定位问题 Console.WriteLine($"OP075({ip})数据读取异常:{ex.Message}\n{ex.StackTrace}"); // 异常时返回基础实体,避免业务空引用 return new PlcProductionData { PlcIp = ip, WorkstationCode = workstationCode, CreatedBy = "PLC", CreatedTime = DateTime.Now, OnlineStatus = "读取异常" }; } } /// /// 读取OP080-1数据 - 效率优化版 /// private async Task ReadOP080_1DataAsync(Plc plc, string ip, string workstationCode, string strSaveRequest) { // 前置核心校验:避免无效PLC操作(节省10+ms无效耗时) if (plc == null || !plc.IsConnected) { Console.WriteLine($"OP080-1({ip})PLC连接无效,跳过数据读取"); // 返回基础实体避免业务空引用 return new PlcProductionData { PlcIp = ip, WorkstationCode = workstationCode }; } try { // 1. 真正的并行读取所有字段(核心效率优化:44个字段并行读取) // ========== 字符串字段并行任务 ========== var taskOrderName = ReadPlcStringAsync(plc, _op080_1StringMap["订单名称"].Addr, _op080_1StringMap["订单名称"].Len); var taskProductName = ReadPlcStringAsync(plc, _op080_1StringMap["产品名称"].Addr, _op080_1StringMap["产品名称"].Len); var taskHzMachineSN = ReadPlcStringAsync(plc, _op080_1StringMap["合装位机壳_SN"].Addr, _op080_1StringMap["合装位机壳_SN"].Len); var taskHzPcbsn = ReadPlcStringAsync(plc, _op080_1StringMap["合装位PCB_SN"].Addr, _op080_1StringMap["合装位PCB_SN"].Len); var taskNjMachineSN = ReadPlcStringAsync(plc, _op080_1StringMap["拧紧位机壳_SN"].Addr, _op080_1StringMap["拧紧位机壳_SN"].Len); var taskNjPcbsn = ReadPlcStringAsync(plc, _op080_1StringMap["拧紧位PCB_SN"].Addr, _op080_1StringMap["拧紧位PCB_SN"].Len); // ========== Int字段并行任务 ========== // 基础状态字段 var taskRunStatus = ReadPlcIntAsync(plc, _op080_1IntMap["运行状态"]); var taskMachineModel = ReadPlcIntAsync(plc, _op080_1IntMap["设备模式"]); var taskOnlineStatus = ReadPlcIntAsync(plc, _op080_1IntMap["设备在线状态"]); var taskByPass = ReadPlcIntAsync(plc, _op080_1IntMap["ByPass"]); var taskProduceModel = ReadPlcIntAsync(plc, _op080_1IntMap["生产模式"]); // 请求/托盘字段 var taskHzQueryReq = ReadPlcIntAsync(plc, _op080_1IntMap["合装工位查询请求"]); var taskHzSaveReq = ReadPlcIntAsync(plc, _op080_1IntMap["合装结果保存请求"]); var taskNjSaveReq = ReadPlcIntAsync(plc, _op080_1IntMap["拧紧结果保存请求"]); var taskHzTrayNo = ReadPlcIntAsync(plc, _op080_1IntMap["合装位托盘号"]); var taskNjTrayNo = ReadPlcIntAsync(plc, _op080_1IntMap["拧紧位托盘号"]); // 螺钉结果字段 var taskScrew1Result = ReadPlcIntAsync(plc, _op080_1IntMap["1号螺钉_结果"]); var taskScrew2Result = ReadPlcIntAsync(plc, _op080_1IntMap["2号螺钉_结果"]); var taskScrew3Result = ReadPlcIntAsync(plc, _op080_1IntMap["3号螺钉_结果"]); var taskScrew4Result = ReadPlcIntAsync(plc, _op080_1IntMap["4号螺钉_结果"]); var taskScrew5Result = ReadPlcIntAsync(plc, _op080_1IntMap["5号螺钉_结果"]); var taskScrew6Result = ReadPlcIntAsync(plc, _op080_1IntMap["6号螺钉_结果"]); var taskScrew7Result = ReadPlcIntAsync(plc, _op080_1IntMap["7号螺钉_结果"]); // ========== Real字段并行任务(7个螺钉×4个维度) ========== // 1号螺钉 var taskScrew1Torque = ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_扭矩"]); var taskScrew1Depth = ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_深度"]); var taskScrew1Angle = ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_角度"]); var taskScrew1Time = ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_拧紧时间"]); // 2号螺钉 var taskScrew2Torque = ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_扭矩"]); var taskScrew2Depth = ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_深度"]); var taskScrew2Angle = ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_角度"]); var taskScrew2Time = ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_拧紧时间"]); // 3号螺钉 var taskScrew3Torque = ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_扭矩"]); var taskScrew3Depth = ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_深度"]); var taskScrew3Angle = ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_角度"]); var taskScrew3Time = ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_拧紧时间"]); // 4号螺钉 var taskScrew4Torque = ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_扭矩"]); var taskScrew4Depth = ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_深度"]); var taskScrew4Angle = ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_角度"]); var taskScrew4Time = ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_拧紧时间"]); // 5号螺钉 var taskScrew5Torque = ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_扭矩"]); var taskScrew5Depth = ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_深度"]); var taskScrew5Angle = ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_角度"]); var taskScrew5Time = ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_拧紧时间"]); // 6号螺钉 var taskScrew6Torque = ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_扭矩"]); var taskScrew6Depth = ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_深度"]); var taskScrew6Angle = ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_角度"]); var taskScrew6Time = ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_拧紧时间"]); // 7号螺钉 var taskScrew7Torque = ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_扭矩"]); var taskScrew7Depth = ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_深度"]); var taskScrew7Angle = ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_角度"]); var taskScrew7Time = ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_拧紧时间"]); // 2. 等待所有并行任务完成(核心:耗时=最慢的单个读取任务,而非44个字段总和) await Task.WhenAll( // 字符串任务 taskOrderName, taskProductName, taskHzMachineSN, taskHzPcbsn, taskNjMachineSN, taskNjPcbsn, // Int任务 taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel, taskHzQueryReq, taskHzSaveReq, taskNjSaveReq, taskHzTrayNo, taskNjTrayNo, taskScrew1Result, taskScrew2Result, taskScrew3Result, taskScrew4Result, taskScrew5Result, taskScrew6Result, taskScrew7Result, // Real任务(7×4) taskScrew1Torque, taskScrew1Depth, taskScrew1Angle, taskScrew1Time, taskScrew2Torque, taskScrew2Depth, taskScrew2Angle, taskScrew2Time, taskScrew3Torque, taskScrew3Depth, taskScrew3Angle, taskScrew3Time, taskScrew4Torque, taskScrew4Depth, taskScrew4Angle, taskScrew4Time, taskScrew5Torque, taskScrew5Depth, taskScrew5Angle, taskScrew5Time, taskScrew6Torque, taskScrew6Depth, taskScrew6Angle, taskScrew6Time, taskScrew7Torque, taskScrew7Depth, taskScrew7Angle, taskScrew7Time); // 3. 获取并行读取结果(带空值兜底,避免后续空引用) // 字符串字段 string orderName = await taskOrderName ?? string.Empty; string productName = await taskProductName ?? string.Empty; string hzMachineSN = await taskHzMachineSN ?? string.Empty; string hzPcbsn = await taskHzPcbsn ?? string.Empty; string njMachineSN = await taskNjMachineSN ?? string.Empty; string njPcbsn = await taskNjPcbsn ?? string.Empty; // Int字段 int runStatus = await taskRunStatus; int machineModel = await taskMachineModel; int onlineStatus = await taskOnlineStatus; int byPass = await taskByPass; int produceModel = await taskProduceModel; int hzQueryReq = await taskHzQueryReq; int hzSaveReq = await taskHzSaveReq; int njSaveReq = await taskNjSaveReq; int hzTrayNo = await taskHzTrayNo; int njTrayNo = await taskNjTrayNo; int screw1Result = await taskScrew1Result; int screw2Result = await taskScrew2Result; int screw3Result = await taskScrew3Result; int screw4Result = await taskScrew4Result; int screw5Result = await taskScrew5Result; int screw6Result = await taskScrew6Result; int screw7Result = await taskScrew7Result; // Real字段 float screw1Torque = await taskScrew1Torque; float screw1Depth = await taskScrew1Depth; float screw1Angle = await taskScrew1Angle; float screw1Time = await taskScrew1Time; float screw2Torque = await taskScrew2Torque; float screw2Depth = await taskScrew2Depth; float screw2Angle = await taskScrew2Angle; float screw2Time = await taskScrew2Time; float screw3Torque = await taskScrew3Torque; float screw3Depth = await taskScrew3Depth; float screw3Angle = await taskScrew3Angle; float screw3Time = await taskScrew3Time; float screw4Torque = await taskScrew4Torque; float screw4Depth = await taskScrew4Depth; float screw4Angle = await taskScrew4Angle; float screw4Time = await taskScrew4Time; float screw5Torque = await taskScrew5Torque; float screw5Depth = await taskScrew5Depth; float screw5Angle = await taskScrew5Angle; float screw5Time = await taskScrew5Time; float screw6Torque = await taskScrew6Torque; float screw6Depth = await taskScrew6Depth; float screw6Angle = await taskScrew6Angle; float screw6Time = await taskScrew6Time; float screw7Torque = await taskScrew7Torque; float screw7Depth = await taskScrew7Depth; float screw7Angle = await taskScrew7Angle; float screw7Time = await taskScrew7Time; // 4. 异步复位保存请求(非阻塞,不影响读取效率) // 异步执行写操作,不阻塞主线程 //await Task.Run(() => //{ // if (strSaveRequest.Contains("合装结果保存请求")) // { // WritePlcValue(plc, _op080_1IntMap["合装结果保存请求"], "0"); // } // if (strSaveRequest.Contains("拧紧结果保存请求")) // { // WritePlcValue(plc, _op080_1IntMap["拧紧结果保存请求"], "0"); // } //}); // 5. 业务逻辑计算(优化:减少冗余计算) var reworkFlag = produceModel == 4 ? "1" : "0"; string produceModelDesc = produceModel switch { 1 => "正常模式", 2 => "清线模式", 4 => "返工模式", 8 => "换型模式", 16 => "预热模式", _ => $"未知({produceModel})" }; string runStatusDesc = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; string onlineStatusDesc = onlineStatus == 1 ? "离线" : "在线"; // 计算整体工位合格标识(优化:数组提前创建,减少GC) var screwResults = new[] { screw1Result, screw2Result, screw3Result, screw4Result, screw5Result, screw6Result, screw7Result }; bool allScrewQualified = screwResults.All(s => s == 1); string qualificationFlag = allScrewQualified ? "1" : "0"; // 调试日志:优化字符串拼接,保留核心信息 Console.WriteLine($"OP080-1({ip})读取结果:产品名称={productName},运行状态={runStatusDesc},订单名称={orderName},螺钉整体合格={allScrewQualified}"); // 6. 构建数据实体(优化:减少冗余字符串操作,提前兜底) var plcData = new PlcProductionData { // 基础字段(移除ip.Trim,外层已校验非空) PlcIp = ip, OccurTime = DateTime.Now, LineCode = "line2", WorkstationCode = workstationCode, ProductName = productName, ProductCode = njPcbsn, // 合格/返工标志 QualificationFlag = qualificationFlag, ReworkFlag = reworkFlag, // 设备状态相关 Automanual = machineModel, Runstatus = runStatus, OnlineStatus = onlineStatusDesc, ProduceModel = produceModelDesc, // 托盘号相关 TrayNo = $"{hzTrayNo}!{njTrayNo}", AssemblyTrayNo = hzTrayNo.ToString(), TightenTrayNo = njTrayNo.ToString(), // SN相关字段(提前兜底,无需重复判空) SN1 = hzMachineSN, SN2 = njMachineSN, AssemblyHousingSN = hzMachineSN, AssemblyPCBSN = hzPcbsn, TightenHousingSN = njMachineSN, TightenPCBSN = njPcbsn, // 螺钉相关字段(优化:ToString("0.00")直接赋值,减少临时变量) Screw1Result = screw1Result.ToString(), Screw1Torque = screw1Torque.ToString("0.00"), Screw1Depth = screw1Depth.ToString("0.00"), Screw1Angle = screw1Angle.ToString("0.00"), Screw1TightenTime = screw1Time.ToString("0.00"), Screw2Result = screw2Result.ToString(), Screw2Torque = screw2Torque.ToString("0.00"), Screw2Depth = screw2Depth.ToString("0.00"), Screw2Angle = screw2Angle.ToString("0.00"), Screw2TightenTime = screw2Time.ToString("0.00"), Screw3Result = screw3Result.ToString(), Screw3Torque = screw3Torque.ToString("0.00"), Screw3Depth = screw3Depth.ToString("0.00"), Screw3Angle = screw3Angle.ToString("0.00"), Screw3TightenTime = screw3Time.ToString("0.00"), Screw4Result = screw4Result.ToString(), Screw4Torque = screw4Torque.ToString("0.00"), Screw4Depth = screw4Depth.ToString("0.00"), Screw4Angle = screw4Angle.ToString("0.00"), Screw4TightenTime = screw4Time.ToString("0.00"), Screw5Result = screw5Result.ToString(), Screw5Torque = screw5Torque.ToString("0.00"), Screw5Depth = screw5Depth.ToString("0.00"), Screw5Angle = screw5Angle.ToString("0.00"), Screw5TightenTime = screw5Time.ToString("0.00"), Screw6Result = screw6Result.ToString(), Screw6Torque = screw6Torque.ToString("0.00"), Screw6Depth = screw6Depth.ToString("0.00"), Screw6Angle = screw6Angle.ToString("0.00"), Screw6TightenTime = screw6Time.ToString("0.00"), Screw7Result = screw7Result.ToString(), Screw7Torque = screw7Torque.ToString("0.00"), Screw7Depth = screw7Depth.ToString("0.00"), Screw7Angle = screw7Angle.ToString("0.00"), Screw7TightenTime = screw7Time.ToString("0.00"), // 系统字段(固定值直接赋值) CreatedBy = "PLC", CreatedTime = DateTime.Now, ParamName = string.Empty, ParamValue = string.Empty, ProductionCycle = null, Remark = string.Empty, UpdatedBy = string.Empty, UpdatedTime = null, ProductModel = string.Empty, WorkstationName = string.Empty, CameraResult = string.Empty }; return plcData; } catch (Exception ex) { // 增强异常日志:包含堆栈,便于定位问题 Console.WriteLine($"OP080-1({ip})数据读取异常:{ex.Message}\n{ex.StackTrace}"); // 异常时返回基础实体,避免业务空引用 return new PlcProductionData { PlcIp = ip, WorkstationCode = workstationCode, CreatedBy = "PLC", CreatedTime = DateTime.Now, OnlineStatus = "读取异常" }; } } /// /// 读取OP080-2 数据 /// /// /// /// /// /// private async Task ReadOP080_2DataAsync(Plc plc, string ip, string plcName) { try { // 1. 批量并行读取所有字段 var (strFields, intFields, realFields) = await Task.Run(async () => ( // 字符串字段(增强空值和异常处理) ( await ReadPlcStringAsync(plc, _op080_2StringMap["产品型号"].Addr, _op080_2StringMap["产品型号"].Len), await ReadPlcStringAsync(plc, _op080_2StringMap["产品名称"].Addr, _op080_2StringMap["产品名称"].Len), await ReadPlcStringAsync(plc, _op080_2StringMap["SN1"].Addr, _op080_2StringMap["SN1"].Len), await ReadPlcStringAsync(plc, _op080_2StringMap["SN2"].Addr, _op080_2StringMap["SN2"].Len) ), // Int字段(使用增强版ReadPlcIntAsync) ( // 基础状态字段 await ReadPlcIntAsync(plc, _op080_2IntMap["运行状态"]), await ReadPlcIntAsync(plc, _op080_2IntMap["设备模式"]), await ReadPlcIntAsync(plc, _op080_2IntMap["设备在线状态"]), //await ReadPlcIntAsync(plc, _op080_2IntMap["ByPass"]), await ReadPlcIntAsync(plc, _op080_2IntMap["生产模式"]), // 托盘号+产品总结果 await ReadPlcIntAsync(plc, _op080_2IntMap["托盘号"]), await ReadPlcIntAsync(plc, _op080_2IntMap["产品总结果"]), // 螺钉结果字段 await ReadPlcIntAsync(plc, _op080_2IntMap["1号螺钉_结果"]), await ReadPlcIntAsync(plc, _op080_2IntMap["2号螺钉_结果"]), await ReadPlcIntAsync(plc, _op080_2IntMap["3号螺钉_结果"]), await ReadPlcIntAsync(plc, _op080_2IntMap["4号螺钉_结果"]), await ReadPlcIntAsync(plc, _op080_2IntMap["5号螺钉_结果"]), await ReadPlcIntAsync(plc, _op080_2IntMap["6号螺钉_结果"]), await ReadPlcIntAsync(plc, _op080_2IntMap["7号螺钉_结果"]) ), // Real字段(螺钉扭矩/深度/角度/时间) ( // 1号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["1号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["1号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["1号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["1号螺钉_拧紧时间"]), // 2号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["2号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["2号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["2号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["2号螺钉_拧紧时间"]), // 3号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["3号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["3号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["3号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["3号螺钉_拧紧时间"]), // 4号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["4号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["4号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["4号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["4号螺钉_拧紧时间"]), // 5号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["5号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["5号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["5号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["5号螺钉_拧紧时间"]), // 6号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["6号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["6号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["6号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["6号螺钉_拧紧时间"]), // 7号螺钉Real字段 await ReadPlcRealAsync(plc, _op080_2IntMap["7号螺钉_扭矩"]), await ReadPlcRealAsync(plc, _op080_2IntMap["7号螺钉_深度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["7号螺钉_角度"]), await ReadPlcRealAsync(plc, _op080_2IntMap["7号螺钉_拧紧时间"]) ) )); // 2. 解构字段 var (productModel, productName, sn1, sn2) = strFields; var (runStatus, machineModel, onlineStatus, produceModel, trayNo, productTotalResult, screw1Result, screw2Result, screw3Result, screw4Result, screw5Result, screw6Result, screw7Result) = intFields; // 解构螺钉Real字段 var ( // 1号螺钉 screw1Torque, screw1Depth, screw1Angle, screw1Time, // 2号螺钉 screw2Torque, screw2Depth, screw2Angle, screw2Time, // 3号螺钉 screw3Torque, screw3Depth, screw3Angle, screw3Time, // 4号螺钉 screw4Torque, screw4Depth, screw4Angle, screw4Time, // 5号螺钉 screw5Torque, screw5Depth, screw5Angle, screw5Time, // 6号螺钉 screw6Torque, screw6Depth, screw6Angle, screw6Time, // 7号螺钉 screw7Torque, screw7Depth, screw7Angle, screw7Time ) = realFields; // 3. 写入保存请求复位 //try //{ // WritePlcValue(plc, _op080_2IntMap["保存请求"], "0"); //} //catch (Exception ex) //{ // Console.WriteLine($"OP080-2({ip})写保存请求失败:{ex.Message}"); //} // 4. 业务逻辑计算 // 返工标志:生产模式=2时为返工 var reworkFlag = produceModel == 2 ? "1" : "0"; // 生产模式描述(OP080-2:1=点检,2=返工,4=样件,5=正常) string produceModelDesc = produceModel switch { 1 => "点检模式", 2 => "返工模式", 4 => "样件模式", 5 => "正常模式", _ => $"未知({produceModel})" }; // 运行状态描述(1=空闲,2=运行中,3=故障) string runStatusDesc = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; // 在线状态描述(1=离线,0=在线) string onlineStatusDesc = onlineStatus == 1 ? "离线" : "在线"; // 合格标识:优先用产品总结果,无则判断所有螺钉结果 string qualificationFlag = productTotalResult switch { 1 => "1", // 总结果OK 2 => "0", // 总结果NG _ => new[] { screw1Result, screw2Result, screw3Result, screw4Result, screw5Result, screw6Result, screw7Result } .All(s => s == 1) ? "1" : "0" // 总结果未知时,判断所有螺钉是否合格 }; // 调试日志:输出关键读取结果 Console.WriteLine($"OP080-2({ip})读取结果:产品型号={productModel},运行状态={runStatusDesc},产品名称={productName},合格标识={qualificationFlag}"); // 5. 构建数据实体(严格匹配PlcProductionData,适配OP080-2字段) var plcData = new PlcProductionData { // 基础字段 PlcIp = ip.Trim(), OccurTime = DateTime.Now, LineCode = "line2", // 按实际产线调整 WorkstationCode = plcName, // 直接使用传入的plcName作为工位编码 ProductModel = productModel ?? string.Empty, // OP080-2有产品型号字段 ProductName = productName ?? string.Empty, ProductCode = sn2 ?? string.Empty, // 结果上传条码作为产品编码 // 合格/返工标志 QualificationFlag = qualificationFlag, ReworkFlag = reworkFlag, // 设备状态相关 Automanual = machineModel, // 设备模式:1=空模式;2=手动;4=初始化;8=自动;16=CycleStop Runstatus = runStatus, // 运行状态 OnlineStatus = onlineStatusDesc, // 在线状态 ProduceModel = produceModelDesc, // 生产模式描述 // 托盘号 TrayNo = trayNo.ToString(), // OP080-2只有单个托盘号 // SN相关字段 SN1 = sn1 ?? string.Empty, // 条码查询SN SN2 = sn2 ?? string.Empty, // 结果上传SN // 螺钉相关字段 Screw1Result = screw1Result.ToString(), Screw1Torque = screw1Torque.ToString("0.00"), Screw1Depth = screw1Depth.ToString("0.00"), Screw1Angle = screw1Angle.ToString("0.00"), Screw1TightenTime = screw1Time.ToString("0.00"), Screw2Result = screw2Result.ToString(), Screw2Torque = screw2Torque.ToString("0.00"), Screw2Depth = screw2Depth.ToString("0.00"), Screw2Angle = screw2Angle.ToString("0.00"), Screw2TightenTime = screw2Time.ToString("0.00"), Screw3Result = screw3Result.ToString(), Screw3Torque = screw3Torque.ToString("0.00"), Screw3Depth = screw3Depth.ToString("0.00"), Screw3Angle = screw3Angle.ToString("0.00"), Screw3TightenTime = screw3Time.ToString("0.00"), Screw4Result = screw4Result.ToString(), Screw4Torque = screw4Torque.ToString("0.00"), Screw4Depth = screw4Depth.ToString("0.00"), Screw4Angle = screw4Angle.ToString("0.00"), Screw4TightenTime = screw4Time.ToString("0.00"), Screw5Result = screw5Result.ToString(), Screw5Torque = screw5Torque.ToString("0.00"), Screw5Depth = screw5Depth.ToString("0.00"), Screw5Angle = screw5Angle.ToString("0.00"), Screw5TightenTime = screw5Time.ToString("0.00"), Screw6Result = screw6Result.ToString(), Screw6Torque = screw6Torque.ToString("0.00"), Screw6Depth = screw6Depth.ToString("0.00"), Screw6Angle = screw6Angle.ToString("0.00"), Screw6TightenTime = screw6Time.ToString("0.00"), Screw7Result = screw7Result.ToString(), Screw7Torque = screw7Torque.ToString("0.00"), Screw7Depth = screw7Depth.ToString("0.00"), Screw7Angle = screw7Angle.ToString("0.00"), Screw7TightenTime = screw7Time.ToString("0.00"), // 系统字段 CreatedBy = "PLC", CreatedTime = DateTime.Now, }; return plcData; } catch (Exception ex) { Console.WriteLine($"OP080-2({ip})数据读取异常:{ex.Message}"); return null; } } #endregion // 可选:添加连接池清理方法(防止长期运行导致连接泄露) private void CleanupExpiredConnections() { var expiredTime = DateTime.Now.AddMinutes(-5); // 5分钟未使用的连接清理 // 先复制所有Key到列表,避免遍历中修改原集合 var allKeys = _plcConnPool.Keys.ToList(); foreach (var key in allKeys) { if (!_plcConnPool.TryGetValue(key, out var poolItem)) continue; if (poolItem.LastUsedTime < expiredTime) { if (_plcConnPool.TryRemove(key, out var removedItem)) { try { if (removedItem.Client != null && removedItem.Client.IsConnected) removedItem.Client.Close(); if (removedItem.Client is IDisposable disposable) disposable.Dispose(); // 可选:置空引用,帮助GC回收 removedItem.Client = null; } catch { // 静默释放,避免清理失败影响主流程 } } } } } // 在类的构造函数中启动定时清理(可选) // public PlcService(IOptions globalConfig) // { // _globalConfig = globalConfig?.Value ?? throw new ArgumentNullException(nameof(globalConfig)); // _plcConfigs = initPlcConfigs(_plcConfigs); // // 每30秒清理一次过期连接 // new Timer(_ => CleanupExpiredConnections(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30)); // } /// /// 测试单个PLC的连接、读、写功能 /// /// 单个PLC的配置参数 /// 取消令牌 /// 测试结果 public async Task TestSinglePlcAsync(PlcConfig config, CancellationToken cancellationToken = default) { // 1. 极简参数校验(提前返回,减少嵌套) if (config == null) throw new ArgumentNullException(nameof(config), "PLC测试配置不能为空"); if (string.IsNullOrWhiteSpace(config.Ip)) return new PlcTestResult { PlcName = config.PlcName, Ip = config.Ip, ConnectSuccess = false, ConnectMessage = "IP地址为空" }; var result = new PlcTestResult { PlcName = config.PlcName, Ip = config.Ip }; Plc plc = null; try { // 2. 初始化PLC客户端 plc = CreatePlcClient(CpuType.S71500, config.Ip, config.Rack, config.Slot); // 3. 带超时的连接测试(精简逻辑) var connectTask = OpenPlcConnectionAsync(plc); if (await Task.WhenAny(connectTask, Task.Delay(_globalConfig.ReadWriteTimeout, cancellationToken)) != connectTask) { result.ConnectSuccess = false; result.ConnectMessage = $"连接超时({_globalConfig.ReadWriteTimeout}ms)"; return result; } await connectTask; // 4. 连接状态校验(精简分支) if (!plc.IsConnected) { result.ConnectSuccess = false; result.ConnectMessage = "连接失败(PLC未返回连接状态)"; return result; } result.ConnectSuccess = true; result.ConnectMessage = "连接成功"; const string TestAddress = "DB1010.DBB238"; if (!string.IsNullOrWhiteSpace(TestAddress)) { // 读取测试 try { var readValue = await Task.Run(() => plc.Read(TestAddress), cancellationToken); result.ReadSuccess = true; result.ReadValue = FormatPlcValue(readValue); result.ReadMessage = "通用地址读取成功"; } catch (Exception ex) { result.ReadSuccess = false; result.ReadMessage = $"通用地址读取失败:{ex.Message}"; } // 写入测试 try { bool writeOk = await Task.Run(() => WritePlcValue(plc, TestAddress, "1"), cancellationToken); result.WriteSuccess = writeOk; result.WriteMessage = writeOk ? "写入成功" : "写入失败(值类型与地址不匹配)"; } catch (Exception ex) { result.WriteSuccess = false; result.WriteMessage = $"写入失败:{ex.Message}"; } } // 6. OP070-1专属状态读取(删除双层判断+内联转换逻辑) if (config.PlcName == "OP070-1") { try { int runStatus = await ReadPlcIntAsync(plc, _op070_1IntMap["运行状态"]); int machineModel = await ReadPlcIntAsync(plc, _op070_1IntMap["设备模式"]); int onlineStatus = await ReadPlcIntAsync(plc, _op070_1IntMap["设备在线状态"]); int produceModel = await ReadPlcIntAsync(plc, _op070_1IntMap["生产模式"]); result.RunStatus = runStatus switch { 1 => "空闲", 2 => "运行中", 3 => "故障", _ => $"未知({runStatus})" }; result.MachineModel = machineModel switch { 1 => "空模式", 2 => "手动", 4 => "初始化", 8 => "自动", 16 => "CycleStop", _ => $"未知({machineModel})" }; result.OnLineStatus = onlineStatus == 1 ? "离线" : (onlineStatus == 0 ? "在线" : $"未知({onlineStatus})"); result.ProduceModel = produceModel switch { 1 => "正常模式", 2 => "清线模式", 4 => "返工模式", 8 => "换型模式", 16 => "预热模式", _ => $"未知({produceModel})" }; result.ReadMessage = $"{result.ReadMessage}; OP070-1状态读取成功"; } catch (Exception ex) { result.RunStatus = result.MachineModel = result.OnLineStatus = result.ProduceModel = "读取失败"; result.ReadMessage = $"{result.ReadMessage}; OP070-1状态读取失败:{ex.Message}"; } } } catch (OperationCanceledException) { result.ConnectSuccess = false; result.ConnectMessage = "测试被取消"; } catch (Exception ex) { result.ConnectSuccess = false; result.ConnectMessage = $"连接异常:{ex.Message}"; } finally { ReleasePlcConnection(plc); } return result; } /// /// 批量测试配置文件中所有PLC的读写功能 /// /// 取消令牌 /// 所有PLC的测试结果列表 public async Task> BatchTestAllPlcAsync(CancellationToken cancellationToken = default) { if (_plcConfigs == null || _plcConfigs.Count == 0) throw new InvalidOperationException("未从配置文件加载到任何PLC参数,请检查PlcConfigs配置"); // 并行测试所有PLC(带最大并行度限制,避免资源耗尽) var testTasks = _plcConfigs .Select(config => TestSinglePlcAsync(config, cancellationToken)) .ToList(); var results = await Task.WhenAll(testTasks); return results.ToList(); } /// /// 单独读取指定PLC的某个地址数据 /// /// PLC的IP地址 /// 机架号 /// 槽位号 /// 读取地址(如DB1.DBD0) /// PLC型号 /// 读取结果(成功状态、值、消息) public async Task<(bool Success, string Value, string Message)> ReadPlcDataAsync( string ip, short rack, short slot, string address, CpuType cpuType = CpuType.S71500) { // 参数校验 if (string.IsNullOrWhiteSpace(ip)) return (false, "", "PLC IP地址不能为空"); if (string.IsNullOrWhiteSpace(address)) return (false, "", "读取地址不能为空"); Plc plc = null; try { plc = CreatePlcClient(cpuType, ip, rack, slot); await OpenPlcConnectionAsync(plc); if (!plc.IsConnected) return (false, "", "PLC连接失败,请检查IP/机架号/槽位号是否正确"); var value = await Task.Run(() => plc.Read(address)); return (true, FormatPlcValue(value), "读取成功"); } catch (Exception ex) { return (false, "", $"读取失败:{ex.Message}"); } finally { ReleasePlcConnection(plc); } } /// /// 单独写入数据到指定PLC的某个地址 /// /// PLC的IP地址 /// 机架号 /// 槽位号 /// 写入地址(如DB1.DBD0) /// 写入的值(字符串格式) /// PLC型号 /// 写入结果(成功状态、消息) public async Task<(bool Success, string Message)> WritePlcDataAsync( string ip, short rack, short slot, string address, string valueStr, CpuType cpuType = CpuType.S71500) { // 参数校验 if (string.IsNullOrWhiteSpace(ip)) return (false, "PLC IP地址不能为空"); if (string.IsNullOrWhiteSpace(address)) return (false, "写入地址不能为空"); if (valueStr == null) // 允许空字符串(如清空值) return (false, "写入值不能为null"); Plc plc = null; try { plc = CreatePlcClient(cpuType, ip, rack, slot); await OpenPlcConnectionAsync(plc); if (!plc.IsConnected) return (false, "PLC连接失败,请检查IP/机架号/槽位号是否正确"); bool writeOk = await Task.Run(() => WritePlcValue(plc, address, valueStr)); return (writeOk, writeOk ? "写入成功" : "写入失败(值类型与地址不匹配)"); } catch (Exception ex) { return (false, $"写入失败:{ex.Message}"); } finally { ReleasePlcConnection(plc); } } #endregion #region 私有辅助方法 /// /// 创建PLC客户端实例(统一配置超时) /// private Plc CreatePlcClient(CpuType cpuType, string ip, short rack, short slot) { var plc = new Plc(cpuType, ip, rack, slot) { ReadTimeout = _globalConfig.ReadWriteTimeout, WriteTimeout = _globalConfig.ReadWriteTimeout }; return plc; } /// /// 异步打开PLC连接(封装同步方法为异步) /// // 优化后的OpenPlcConnectionAsync private async Task OpenPlcConnectionAsync(Plc plc) { if (plc == null) return false; try { // 带超时的连接操作 var openTask = Task.Run(() => plc.Open()); if (await Task.WhenAny(openTask, Task.Delay(_globalConfig.ReadWriteTimeout)) != openTask) { return false; } await openTask; return plc.IsConnected; } catch { return false; } } /// /// 释放PLC连接(安全关闭+资源释放) /// private void ReleasePlcConnection(Plc plc) { if (plc == null) return; try { if (plc.IsConnected) plc.Close(); // 正确释放PLC实例(适配S7.Net显式实现IDisposable) if (plc is IDisposable disposable) disposable.Dispose(); } catch (Exception ex) { Console.WriteLine($"PLC连接关闭失败:{ex.Message}"); } // 移除finally中的Dispose(),避免误释放服务 } /// /// 按业务规则解析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; } } /// /// 修复版:异步读取PLC整数(DBW地址,16位短整型) /// 增强类型兼容、异常日志、地址校验 /// /// PLC客户端实例 /// 读取地址(如DB1010.DBW226) /// 解析后的整数值,读取失败返回0 private async Task ReadPlcIntAsync(Plc plc, string addr) { // 1. 地址和PLC实例有效性校验 if (plc == null) { Console.WriteLine($"PLC整数读取失败:PLC实例为空(地址:{addr})"); return 0; } if (string.IsNullOrWhiteSpace(addr)) { Console.WriteLine("PLC整数读取失败:读取地址为空"); return 0; } try { // 2. 异步读取(避免同步阻塞,适配整体异步上下文) var val = await Task.Run(() => plc.Read(addr)); // 调试日志:输出原始值和类型,便于排查问题 Console.WriteLine($"PLC地址[{addr}]原始读取值:{val ?? "null"},类型:{val?.GetType().Name ?? "未知"}"); // 3. 增强类型兼容(覆盖PLC可能返回的所有整数类型) return val switch { short s => s, // 16位短整型(DBW默认类型) int i => i, // 32位整型(兼容返回) uint ui => (int)ui, // 无符号32位整型 byte b => b, // 8位字节型 ushort us => us, // 无符号16位整型 long l => (int)l, // 64位整型(截断为32位) ulong ul => (int)ul, // 无符号64位整型(截断为32位) float f => (int)f, // 浮点数转整型(兼容PLC数值存储) double d => (int)d, // 双精度浮点数转整型 _ => 0 // 未知类型返回0 }; } catch (Exception ex) { // 4. 输出详细异常日志,便于定位问题 Console.WriteLine($"PLC整数读取失败(地址:{addr}):{ex.Message},堆栈:{ex.StackTrace}"); return 0; } } private async Task ReadPlcDIntAsync(Plc plc, string addr) { if (plc == null || string.IsNullOrWhiteSpace(addr)) { Console.WriteLine($"DInt读取失败:参数无效(地址:{addr})"); return 0; } try { var val = await Task.Run(() => plc.Read(addr)); Console.WriteLine($"DInt地址[{addr}] 原始值:{val},类型:{val?.GetType().Name}"); return val switch { int i => i, long l => (int)l, uint ui => (int)ui, ulong ul => (int)ul, _ => 0 }; } catch (Exception ex) { Console.WriteLine($"DInt读取失败(地址:{addr}):{ex.Message}"); return 0; } } private async Task ReadPlcRealAsync(Plc plc, string addr) { if (plc == null || string.IsNullOrWhiteSpace(addr)) { Console.WriteLine($"Real读取失败:参数无效(地址:{addr})"); return 0.0f; } try { var val = await Task.Run(() => plc.Read(addr)); Console.WriteLine($"Real地址[{addr}] 原始值:{val},类型:{val?.GetType().Name}"); return val switch { float f => f, double d => (float)d, int i => i, _ => 0.0f }; } catch (Exception ex) { Console.WriteLine($"Real读取失败(地址:{addr}):{ex.Message}"); return 0.0f; } } /// /// 格式化PLC读取的值,转为友好的字符串 /// private string FormatPlcValue(object value) { if (value == null) return "空值"; return value switch { float f => f.ToString("0.000"), // 浮点数保留3位小数 double d => d.ToString("0.000"), // 补充双精度浮点数支持 short s => s.ToString(), // 16位整数 byte b => b.ToString(), // 8位整数 bool b => b.ToString(), // 布尔值 int i => i.ToString(), // 32位整数 uint ui => ui.ToString(), // 无符号32位整数 long l => l.ToString(), // 64位整数 ulong ul => ul.ToString(), // 无符号64位整数 _ => value.ToString() // 其他类型直接转字符串 }; } /// /// 根据地址类型转换值并写入PLC /// private bool WritePlcValue(Plc plc, string address, string valueStr) { // 双字(DBD/DD):浮点数或32位整数 if (address.Contains("DBD") || address.Contains("DD")) { if (float.TryParse(valueStr, out float f)) { plc.Write(address, f); return true; } else if (int.TryParse(valueStr, out int i)) { plc.Write(address, i); return true; } } // 字(DBW/DW):16位整数 else if (address.Contains("DBW") || address.Contains("DW")) { if (short.TryParse(valueStr, out short s)) { plc.Write(address, s); return true; } } // 字节(DBB/DB):8位整数 else if (address.Contains("DBB") || address.Contains("DB")) { if (byte.TryParse(valueStr, out byte b)) { plc.Write(address, b); return true; } } // 位(DBX):布尔值(兼容0/1输入) else if (address.Contains("DBX")) { if (bool.TryParse(valueStr, out bool bl)) { plc.Write(address, bl); return true; } else if (valueStr == "0") { plc.Write(address, false); return true; } else if (valueStr == "1") { plc.Write(address, true); return true; } } // 不匹配的类型 return false; } #endregion #region 资源释放 /// /// 实现IDisposable接口,释放资源 /// public void Dispose() { Dispose(true); GC.SuppressFinalize(this); // 通知GC无需调用终结器 } /// /// 实际释放资源的逻辑(区分托管/非托管资源) /// /// 是否释放托管资源 protected virtual void Dispose(bool disposing) { if (_disposed) return; // 避免重复释放 // 释放托管资源 if (disposing) { // 此处无长期持有的PLC连接,若有可在此统一释放 } // 标记为已释放 _disposed = true; } /// /// 终结器(防忘记手动Dispose) /// ~PlcService() { Dispose(false); } #endregion private List initPlcConfigs(List result) { var defaultResult = result ?? new List(); try { List query = _plantWorkstationService.Queryable() .Where(it => it.Status == 1) .Select(it => new PlcConfig { PlcName = it.WorkstationCode, Ip = it.PlcIP, Rack = (short)it.Rack, // 直接强制转换(it.Rack是int,非空) Slot = (short)it.Slot // 同理,it.Slot是int,非空 }) .ToList(); return query.Count > 0 ? query : defaultResult; } catch (Exception ex) { Console.WriteLine($"初始化PLC配置异常:{ex.Message}"); return defaultResult; } } // 提取写PLC返回值的通用方法,统一处理逻辑和日志 private void WritePlcSaveRequestResult(Plc plc, string ip, string plcName, PlcProductionData prodData, string saveResult) { try { WritePlcValue(plc, _mesIntReturnMap["设备使能"], "1"); //WritePlcValue(plc, _mesIntReturnMap["工位开始查询结果"], "1"); WritePlcValue(plc, _mesIntReturnMap["保存结果"], saveResult); WritePlcValue(plc, "产品型号", prodData.ProductModel); WritePlcValue(plc, "订单下发", "");//?? } catch (Exception ex) {; } } /// /// 异步复位上传请求(独立方法,非阻塞) /// private async Task ResetUploadRequestAsync(Plc plc, string ip, string uploadRequestAddr) { try { // 若WritePlcValue无异步版本,用Task.Run包装避免阻塞 await Task.Run(() => WritePlcValue(plc, uploadRequestAddr, "0")); } catch (Exception ex) { Console.WriteLine($"({ip})写上传请求复位失败:{ex.Message}"); } } /// /// 异步复位保存请求(独立方法,非阻塞) /// private async Task ResetSaveRequestAsync(Plc plc, string ip, string saveRequestAddr) { try { // 若WritePlcValue无异步版本,用Task.Run包装避免阻塞 await Task.Run(() => WritePlcValue(plc, saveRequestAddr, "0")); } catch (Exception ex) { Console.WriteLine($"({ip})写保存请求复位失败:{ex.Message}"); } } } }