2026-01-26 14:46:56 +08:00

2184 lines
108 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

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

// 统一引入所有必要命名空间
using 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
{
/// <summary>
/// PLC通信服务封装连接、读写、生产数据采集等功能
/// </summary>
public class PlcService : IDisposable
{
#region
// 标记是否已释放资源,避免重复释放
private bool _disposed = false;
// PLC配置参数从配置文件注入
private readonly List<PlcConfig> _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<string, (Plc Client, DateTime LastUsedTime)> _plcConnPool = new(); // 连接池
#region PLC地址块儿映射
//MES返回PLC请求映射
private readonly Dictionary<string, string> _mesIntReturnMap = new()
{
{ "设备使能", "DB1050.DBW0" }, // Int
{ "工位开始查询结果", "DB1050.DBW2000" }, // Int
{ "保存结果", "DB1050.DBW2002" }, // Int
};
private readonly Dictionary<string, (string Addr, int Len)> _mesStringReturnMap = new()
{
{ "产品型号", ("DB1001.DBB1016", 14) }, // String[14]
{ "订单下发", ("DB1001.DBB1032", 50) }, // String[50]
};
// OP020-2 专属地址映射合盖工位DB1001
private readonly Dictionary<string, (string Addr, int Len)> _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<string, string> _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<string, string> _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<string, (string Addr, int Len)> _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<string, string> _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<string, (string Addr, int Len)> _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<string, string> _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<string, (string Addr, int Len)> _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<string, string> _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<string, (string Addr, int Len)> _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<string, string> _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<string, (string Addr, int Len)> _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<string, string> _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
/// <summary>
/// 构造函数依赖注入获取PLC配置
/// </summary>
/// <param name="plcConfigs">所有PLC的连接配置</param>
/// <param name="globalConfig">PLC全局超时配置</param>
public PlcService(IOptions<GlobalPlcConfig> globalConfig)
{
_globalConfig = globalConfig?.Value ?? throw new ArgumentNullException(nameof(globalConfig), "PLC全局配置不能为空");
//初始化plcConfigs
_plcConfigs = initPlcConfigs(_plcConfigs);
}
#endregion
#region
/// <summary>
/// 读取PLC生产数据优化版连接池+并发控制+OP070-1/OP080专属适配
/// </summary>
/// <param name="ip">PLC IP地址</param>
/// <param name="plcName">PLC名称支持OP070-1/OP080</param>
/// <param name="rack">机架号</param>
/// <param name="slot">槽位号</param>
/// <param name="cpuType">PLC型号默认S7-1500</param>
/// <returns>读取结果(状态+数据+消息)</returns>
/// <summary>
/// PLC生产数据读取核心方法优化版
/// </summary>
/// <returns>成功状态/生产数据/提示消息</returns>
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
/// <summary>
/// 读取OP020-2数据合盖工位
/// </summary>
private async Task<PlcProductionData> 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;
}
}
/// <summary>
/// 读取OP020-3数据热铆工位
/// </summary>
private async Task<PlcProductionData?> 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;
}
}
/// <summary>
/// OP070-1数据读取 - 效率优化版
/// </summary>
private async Task<PlcProductionData> 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;
}
}
/// <summary>
/// OP075数据读取
/// </summary>
private async Task<PlcProductionData> 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 = "读取异常"
};
}
}
/// <summary>
/// 读取OP080-1数据 - 效率优化版
/// </summary>
private async Task<PlcProductionData> 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 = "读取异常"
};
}
}
/// <summary>
/// 读取OP080-2 数据
/// </summary>
/// <param name="plc"></param>
/// <param name="ip"></param>
/// <param name="workstationCode"></param>
/// <returns></returns>
/// <summary>
private async Task<PlcProductionData> 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-21=点检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<GlobalPlcConfig> globalConfig)
// {
// _globalConfig = globalConfig?.Value ?? throw new ArgumentNullException(nameof(globalConfig));
// _plcConfigs = initPlcConfigs(_plcConfigs);
// // 每30秒清理一次过期连接
// new Timer(_ => CleanupExpiredConnections(), null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
// }
/// <summary>
/// 测试单个PLC的连接、读、写功能
/// </summary>
/// <param name="config">单个PLC的配置参数</param>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>测试结果</returns>
public async Task<PlcTestResult> 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;
}
/// <summary>
/// 批量测试配置文件中所有PLC的读写功能
/// </summary>
/// <param name="cancellationToken">取消令牌</param>
/// <returns>所有PLC的测试结果列表</returns>
public async Task<List<PlcTestResult>> 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();
}
/// <summary>
/// 单独读取指定PLC的某个地址数据
/// </summary>
/// <param name="ip">PLC的IP地址</param>
/// <param name="rack">机架号</param>
/// <param name="slot">槽位号</param>
/// <param name="address">读取地址如DB1.DBD0</param>
/// <param name="cpuType">PLC型号</param>
/// <returns>读取结果(成功状态、值、消息)</returns>
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);
}
}
/// <summary>
/// 单独写入数据到指定PLC的某个地址
/// </summary>
/// <param name="ip">PLC的IP地址</param>
/// <param name="rack">机架号</param>
/// <param name="slot">槽位号</param>
/// <param name="address">写入地址如DB1.DBD0</param>
/// <param name="valueStr">写入的值(字符串格式)</param>
/// <param name="cpuType">PLC型号</param>
/// <returns>写入结果(成功状态、消息)</returns>
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
/// <summary>
/// 创建PLC客户端实例统一配置超时
/// </summary>
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;
}
/// <summary>
/// 异步打开PLC连接封装同步方法为异步
/// </summary>
// 优化后的OpenPlcConnectionAsync
private async Task<bool> 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;
}
}
/// <summary>
/// 释放PLC连接安全关闭+资源释放)
/// </summary>
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(),避免误释放服务
}
/// <summary>
/// 按业务规则解析PLC字符串支持中文GBK编码
/// </summary>
private async Task<string> ReadPlcStringAsync(Plc plc, string addr, int maxLen)
{
try
{
// 解析地址DB1010.DBB50 → DB编号1010 + 起始字节50
var parts = addr.Split('.', StringSplitOptions.RemoveEmptyEntries);
if (parts.Length != 2)
return string.Empty;
if (!int.TryParse(parts[0].Replace("DB", ""), out int dbNum) || dbNum <= 0)
return string.Empty;
if (!int.TryParse(parts[1].Replace("DBB", ""), out int startByte) || startByte < 0)
return string.Empty;
// 读取字节数组
var bytes = await Task.Run(() => plc.ReadBytes(DataType.DataBlock, dbNum, startByte, maxLen));
if (bytes == null || bytes.Length < 3)
return string.Empty;
// 严格按规则解析bytes[0]=总长度(备用)、bytes[1]=有效长度、bytes[2+]=内容
int validLen = Math.Clamp(bytes[1], 0, bytes.Length - 2);
if (validLen <= 0)
return string.Empty;
// 提取有效内容并转字符串GBK编码支持中文
var contentBytes = new byte[validLen];
Array.Copy(bytes, 2, contentBytes, 0, validLen);
return Encoding.GetEncoding("GBK").GetString(contentBytes).Trim('\0', ' ', '\t');
}
catch
{
return string.Empty;
}
}
/// <summary>
/// 修复版异步读取PLC整数DBW地址16位短整型
/// 增强类型兼容、异常日志、地址校验
/// </summary>
/// <param name="plc">PLC客户端实例</param>
/// <param name="addr">读取地址如DB1010.DBW226</param>
/// <returns>解析后的整数值读取失败返回0</returns>
private async Task<int> 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<int> 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<float> 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;
}
}
/// <summary>
/// 格式化PLC读取的值转为友好的字符串
/// </summary>
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() // 其他类型直接转字符串
};
}
/// <summary>
/// 根据地址类型转换值并写入PLC
/// </summary>
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/DW16位整数
else if (address.Contains("DBW") || address.Contains("DW"))
{
if (short.TryParse(valueStr, out short s))
{
plc.Write(address, s);
return true;
}
}
// 字节DBB/DB8位整数
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
/// <summary>
/// 实现IDisposable接口释放资源
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 通知GC无需调用终结器
}
/// <summary>
/// 实际释放资源的逻辑(区分托管/非托管资源)
/// </summary>
/// <param name="disposing">是否释放托管资源</param>
protected virtual void Dispose(bool disposing)
{
if (_disposed) return; // 避免重复释放
// 释放托管资源
if (disposing)
{
// 此处无长期持有的PLC连接若有可在此统一释放
}
// 标记为已释放
_disposed = true;
}
/// <summary>
/// 终结器防忘记手动Dispose
/// </summary>
~PlcService()
{
Dispose(false);
}
#endregion
private List<PlcConfig> initPlcConfigs(List<PlcConfig> result)
{
var defaultResult = result ?? new List<PlcConfig>();
try
{
List<PlcConfig> 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)
{;
}
}
/// <summary>
/// 异步复位上传请求(独立方法,非阻塞)
/// </summary>
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}");
}
}
/// <summary>
/// 异步复位保存请求(独立方法,非阻塞)
/// </summary>
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}");
}
}
}
}