2026-01-24 17:28:23 +08:00

1439 lines
67 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地址块儿映射
// OP070-1 专属地址映射
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 专属地址映射
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 专属地址映射
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
};
#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>
public async Task<(bool Success, PlcProductionData Data, string Message)> ReadProductionDataAsync(
string ip,
string plcName,
short rack,
short slot,
CpuType cpuType = CpuType.S71500)
{
// 1. 强化参数校验补充PLC名称校验+白名单控制)
if (string.IsNullOrWhiteSpace(ip))
return (false, null, "PLC IP地址不能为空");
if (string.IsNullOrWhiteSpace(plcName))
return (false, null, "PLC名称不能为空");
if (rack < 0 || rack > 10)
return (false, null, $"PLC机架号{rack}无效有效值0-10");
if (slot < 0 || slot > 4)
return (false, null, $"PLC槽位号{slot}无效有效值0-4");
Plc plc = null;
bool isConnReused = false;
var poolKey = $"{ip}_{rack}_{slot}_{cpuType}";
PlcProductionData prodData = null;
// 2. 并发控制(核心保留,防止端口/线程耗尽)
await _concurrencySemaphore.WaitAsync();
try
{
// 3. 连接池复用+精简重试逻辑(提升可读性)
if (_plcConnPool.TryGetValue(poolKey, out var poolItem) && poolItem.Client.IsConnected)
{
plc = poolItem.Client;
_plcConnPool.TryUpdate(poolKey, (plc, DateTime.Now), poolItem);
isConnReused = true;
}
else
{
plc = CreatePlcClient(cpuType, ip, rack, slot);
bool isConnected = false;
// for循环替代while逻辑更直观
try
{
await OpenPlcConnectionAsync(plc);
isConnected = plc.IsConnected;
}
catch
{
}
if (!isConnected)
return (false, null, $"{plcName}连接失败含2次重试");
_plcConnPool.TryAdd(poolKey, (plc, DateTime.Now));
}
// 4. 多工位查询请求标志适配(核心:区分不同工位的寄存器地址)
int iQueryRequest = 0;
int iSaveRequest = 0;
string strSaveRequest = "";
if (plcName == "OP070-1" || plcName == "OP070-2" || plcName == "OP070-3")
{
iQueryRequest = await ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]);
iSaveRequest = await ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]);
}
else if (plcName == "OP075")
{
iQueryRequest = await ReadPlcIntAsync(plc, _op075IntMap["查询请求"]);
iSaveRequest = await ReadPlcIntAsync(plc, _op075IntMap["保存请求"]);
}
else if (plcName == "OP080-1")
{
iQueryRequest = await ReadPlcIntAsync(plc, _op080_1IntMap["合装工位查询请求"]);
//考虑一条数据更新两次,还是生成两条数据
int iSaveRequest1 = await ReadPlcIntAsync(plc, _op080_1IntMap["合装结果保存请求"]);
int iSaveRequest2 = await ReadPlcIntAsync(plc, _op080_1IntMap["拧紧结果保存请求"]);
if (iSaveRequest1 == 1)
{
iSaveRequest = 1;
strSaveRequest = "合装结果保存请求";
}
if (iSaveRequest2 == 1)
{
iSaveRequest = 1;
if (strSaveRequest.Length > 0)
{
strSaveRequest += ",拧紧结果保存请求";
}
else
{
strSaveRequest = "拧紧结果保存请求";
}
}
} // 无效请求直接返回(避免无谓资源消耗)
if (iQueryRequest != 1 && iSaveRequest != 1)
{
return (false, null, $"");
}
//给PLC返回数据
if (iQueryRequest == 1)
{
}
if (iSaveRequest == 1)
{
// 5. 多工位专属数据读取(预留扩展,逻辑隔离)
if (plcName == "OP070-1" || plcName == "OP070-2" || plcName == "OP070-3")
{
prodData = await ReadOP070_1DataAsync(plc, ip, plcName);
}
else if (plcName == "OP075")
{
prodData = await ReadOP075DataAsync(plc, ip, plcName);
}
else if (plcName == "OP080-1" || plcName == "OP080-2")
{
int iSaveRequest1 = await ReadPlcIntAsync(plc, _op080_1IntMap["合装结果保存请求"]);
int iSaveRequest2 = await ReadPlcIntAsync(plc, _op080_1IntMap["拧紧结果保存请求"]);
prodData = await ReadOP080_1DataAsync(plc, ip, plcName, strSaveRequest);
}
// 6. 统一空值兜底(避免空引用)
if (prodData != null)
{
prodData.QualificationFlag ??= "0";
prodData.ReworkFlag ??= "0";
prodData.ProductionCycle ??= 0;
// 7. 异步保存数据日志补充PLC名称便于定位
_ = Task.Run(() => _plcProductionDataService.AddPlcProductionData(prodData))
.ContinueWith(t =>
{
if (t.IsFaulted)
{
Console.WriteLine($"{plcName}({ip})数据保存失败:{t.Exception?.InnerException?.Message}");
}
});
}
}
// 8. 个性化返回消息(区分工位和连接类型)
var successMsg = isConnReused
? $"{plcName}生产数据读取成功(复用连接)"
: $"{plcName}生产数据读取成功(新建连接)";
return (true, prodData, successMsg);
}
catch (Exception ex)
{
// 精细化异常日志包含PLC名称快速定位故障
Console.WriteLine($"{plcName}({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}");
return (false, null, $"{plcName}生产数据读取失败:{ex.Message}");
}
finally
{
// 资源安全释放(核心:防止死锁/连接泄漏)
if (!isConnReused)
{
ReleasePlcConnection(plc);
}
_concurrencySemaphore.Release();
}
}
#region
/// <summary>
/// OP070-1数据读取
/// </summary>
private async Task<PlcProductionData> ReadOP070_1DataAsync(Plc plc, string ip,string workstationCode)
{
// 1. 批量并行读取所有字段(沿用原有结构+增强型读取方法)
var (strFields, intFields, realFields) = await Task.Run(async () => (
// 字符串字段(增强空值和异常处理)
(
//await ReadPlcStringAsync(plc, _op070_1StringMap["报警信息"].Addr, _op070_1StringMap["报警信息"].Len),
await ReadPlcStringAsync(plc, _op070_1StringMap["产品名称"].Addr, _op070_1StringMap["产品名称"].Len),
await ReadPlcStringAsync(plc, _op070_1StringMap["订单名称"].Addr, _op070_1StringMap["订单名称"].Len),
await ReadPlcStringAsync(plc, _op070_1StringMap["产品型号"].Addr, _op070_1StringMap["产品型号"].Len),
await ReadPlcStringAsync(plc, _op070_1StringMap["SN_1"].Addr, _op070_1StringMap["SN_1"].Len),
await ReadPlcStringAsync(plc, _op070_1StringMap["SN_2"].Addr, _op070_1StringMap["SN_2"].Len)
),
// Int字段使用增强版ReadPlcIntAsync
(
await ReadPlcIntAsync(plc, _op070_1IntMap["运行状态"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["设备模式"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["设备在线状态"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["ByPass"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["生产模式"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["托盘号"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["相机结果"]),
await ReadPlcIntAsync(plc, _op070_1IntMap["站位结果"])
),
// DInt字段增强版读取方法
//(
// await ReadPlcDIntAsync(plc, _op070_1IntMap["实际产量"]),
// await ReadPlcDIntAsync(plc, _op070_1IntMap["合格数量"]),
// await ReadPlcDIntAsync(plc, _op070_1IntMap["失败数量"])
//),
// Real字段增强版读取方法
(
await ReadPlcRealAsync(plc, _op070_1IntMap["节拍时间"])
)
));
// 2. 解构字段(保持原有逻辑,空值兜底)
var (productName,orderName, productModel, sn1, sn2) = strFields;
var (runStatus, machineModel, onlineStatus, byPass, produceModel, queryReq, saveReq, trayNo, cameraResult, stationResult) = intFields;
//var (actualOutput, qualifiedQty, failedQty) = dintFields;
float cycleTime = realFields;
// 3. 写入保存请求(异步+增强异常日志)
try
{
WritePlcValue(plc, _op070_1IntMap["保存请求"], "0");
}
catch (Exception ex)
{
Console.WriteLine($"OP070-1({ip})写保存请求失败:{ex.Message}");
}
// 4. 极简条件计算(增强类型安全)
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 byPassDesc = byPass == 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}");
// 5. 构建数据实体(增强空值处理)
return new PlcProductionData
{
PlcIp = ip.Trim(),
OccurTime = DateTime.Now,
LineCode = "line2",
WorkstationCode = workstationCode,
ProductModel = productModel ?? string.Empty,
ProductName = productName ?? string.Empty,
ProductCode = sn2 ?? string.Empty,
SN1 = sn1 ?? string.Empty,
SN2 = sn2 ?? string.Empty,
QualificationFlag = qualificationFlag,
ReworkFlag = reworkFlag,
Automanual = machineModel,
Runstatus = runStatus,
OnlineStatus = onlineStatusDesc,
//ByPass = byPassDesc,
ProduceModel = produceModelDesc,
TrayNo = trayNo.ToString(),
CameraResult = cameraResultDesc,
//ActualOutput = actualOutput,
//QualifiedQuantity = qualifiedQty,
//FailedQuantity = failedQty,
ProductionCycle = (int?)cycleTime,
//AlarmMsg = alarmMsg ?? string.Empty,
CreatedBy = "PLC",
CreatedTime = DateTime.Now
};
}
/// <summary>
/// OP075数据读取
/// </summary>
private async Task<PlcProductionData> ReadOP075DataAsync(Plc plc, string ip, string workstationCode)
{
// 1. 批量并行读取所有字段(沿用原有结构+增强型读取方法)
var (strFields, intFields, realFields) = await Task.Run(async () => (
// 字符串字段(增强空值和异常处理)
(
//await ReadPlcStringAsync(plc, _op075StringMap["报警信息"].Addr, _op075StringMap["报警信息"].Len),
await ReadPlcStringAsync(plc, _op075StringMap["产品名称"].Addr, _op075StringMap["产品名称"].Len),
await ReadPlcStringAsync(plc, _op075StringMap["订单名称"].Addr, _op075StringMap["订单名称"].Len),
await ReadPlcStringAsync(plc, _op075StringMap["产品型号"].Addr, _op075StringMap["产品型号"].Len),
await ReadPlcStringAsync(plc, _op075StringMap["SN_1"].Addr, _op075StringMap["SN_1"].Len),
await ReadPlcStringAsync(plc, _op075StringMap["SN_2"].Addr, _op075StringMap["SN_2"].Len),
await ReadPlcStringAsync(plc, _op075StringMap["芯片SN"].Addr, _op075StringMap["芯片SN"].Len)
),
// Int字段使用增强版ReadPlcIntAsync
(
await ReadPlcIntAsync(plc, _op075IntMap["运行状态"]),
await ReadPlcIntAsync(plc, _op075IntMap["设备模式"]),
await ReadPlcIntAsync(plc, _op075IntMap["设备在线状态"]),
await ReadPlcIntAsync(plc, _op075IntMap["ByPass"]),
await ReadPlcIntAsync(plc, _op075IntMap["生产模式"]),
await ReadPlcIntAsync(plc, _op075IntMap["查询请求"]),
await ReadPlcIntAsync(plc, _op075IntMap["保存请求"]),
await ReadPlcIntAsync(plc, _op075IntMap["托盘号"]),
await ReadPlcIntAsync(plc, _op075IntMap["站位结果"])
),
// DInt字段增强版读取方法
//(
// await ReadPlcDIntAsync(plc, _op075IntMap["实际产量"]),
// await ReadPlcDIntAsync(plc, _op075IntMap["合格数量"]),
// await ReadPlcDIntAsync(plc, _op075IntMap["失败数量"])
//),
// Real字段增强版读取方法
(
await ReadPlcRealAsync(plc, _op075IntMap["节拍时间"])
)
));
// 2. 解构字段(保持原有逻辑,空值兜底)
var (productName, orderName, productModel, sn1, sn2,chipsn) = strFields;
var (runStatus, machineModel, onlineStatus, byPass, produceModel, queryReq, saveReq, trayNo, stationResult) = intFields;
//var (actualOutput, qualifiedQty, failedQty) = dintFields;
float cycleTime = realFields;
// 3. 写入保存请求(异步+增强异常日志)
try
{
WritePlcValue(plc, _op075IntMap["保存请求"], "0");
}
catch (Exception ex)
{
Console.WriteLine($"OP075({ip})写保存请求失败:{ex.Message}");
}
// 4. 极简条件计算(增强类型安全)
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 byPassDesc = byPass == 1 ? "旁路模式" : "正常模式";
string qualificationFlag = stationResult switch { 1 => "1", 2 => "0", _ => stationResult.ToString() };
// 调试日志:输出关键读取结果
Console.WriteLine($"OP075({ip})读取结果:产品型号={productModel},运行状态={runStatusDesc},订单名称={orderName}");
// 5. 构建数据实体(增强空值处理)
return new PlcProductionData
{
PlcIp = ip.Trim(),
OccurTime = DateTime.Now,
LineCode = "line2",
WorkstationCode = workstationCode,
ProductModel = productModel ?? string.Empty,
ProductName = productName ?? string.Empty,
ProductCode = sn2 ?? string.Empty,
SN1 = sn1 ?? string.Empty,
SN2 = sn2 ?? string.Empty,
QualificationFlag = qualificationFlag,
ReworkFlag = reworkFlag,
Automanual = machineModel,
Runstatus = runStatus,
OnlineStatus = onlineStatusDesc,
//ByPass = byPassDesc,
ProduceModel = produceModelDesc,
TrayNo = trayNo.ToString(),
ChipSN = chipsn,
//ActualOutput = actualOutput,
//QualifiedQuantity = qualifiedQty,
//FailedQuantity = failedQty,
ProductionCycle = (int?)cycleTime,
//AlarmMsg = alarmMsg ?? string.Empty,
CreatedBy = "PLC",
CreatedTime = DateTime.Now
};
}
/// <summary>
/// 读取OP080-1数据
/// </summary>
/// <param name="plc"></param>
/// <param name="ip"></param>
/// <param name="workstationCode"></param>
/// <returns></returns>
private async Task<PlcProductionData> ReadOP080_1DataAsync(Plc plc, string ip, string workstationCode,string strSaveRequest)
{
// 1. 批量并行读取所有字段(沿用原有结构+增强型读取方法)
var (strFields, intFields, realFields) = await Task.Run(async () => (
// 字符串字段(增强空值和异常处理)
(
await ReadPlcStringAsync(plc, _op080_1StringMap["订单名称"].Addr, _op080_1StringMap["订单名称"].Len),
await ReadPlcStringAsync(plc, _op080_1StringMap["产品名称"].Addr, _op080_1StringMap["产品名称"].Len),
await ReadPlcStringAsync(plc, _op080_1StringMap["合装位机壳_SN"].Addr, _op080_1StringMap["合装位机壳_SN"].Len),
await ReadPlcStringAsync(plc, _op080_1StringMap["合装位PCB_SN"].Addr, _op080_1StringMap["合装位PCB_SN"].Len),
await ReadPlcStringAsync(plc, _op080_1StringMap["拧紧位机壳_SN"].Addr, _op080_1StringMap["拧紧位机壳_SN"].Len),
await ReadPlcStringAsync(plc, _op080_1StringMap["拧紧位PCB_SN"].Addr, _op080_1StringMap["拧紧位PCB_SN"].Len)
),
// Int/DInt字段使用增强版ReadPlcIntAsync/ReadPlcDIntAsync
(
// 基础状态字段
await ReadPlcIntAsync(plc, _op080_1IntMap["运行状态"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["设备模式"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["设备在线状态"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["ByPass"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["生产模式"]),
// 产量统计字段DInt类型需用ReadPlcDIntAsync
//await ReadPlcDIntAsync(plc, _op080_1IntMap["实际产量"]),
//await ReadPlcDIntAsync(plc, _op080_1IntMap["合格数量"]),
//await ReadPlcDIntAsync(plc, _op080_1IntMap["失败数量"]),
// 请求/托盘字段
await ReadPlcIntAsync(plc, _op080_1IntMap["合装工位查询请求"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["合装结果保存请求"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["拧紧结果保存请求"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["合装位托盘号"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["拧紧位托盘号"]),
// 螺钉结果字段Int类型
await ReadPlcIntAsync(plc, _op080_1IntMap["1号螺钉_结果"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["2号螺钉_结果"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["3号螺钉_结果"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["4号螺钉_结果"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["5号螺钉_结果"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["6号螺钉_结果"]),
await ReadPlcIntAsync(plc, _op080_1IntMap["7号螺钉_结果"])
),
// Real字段螺钉扭矩/深度/角度/时间,增强版读取方法)
(
// 1号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["1号螺钉_拧紧时间"]),
// 2号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["2号螺钉_拧紧时间"]),
// 3号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["3号螺钉_拧紧时间"]),
// 4号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["4号螺钉_拧紧时间"]),
// 5号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["5号螺钉_拧紧时间"]),
// 6号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["6号螺钉_拧紧时间"]),
// 7号螺钉Real字段
await ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_扭矩"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_深度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_角度"]),
await ReadPlcRealAsync(plc, _op080_1IntMap["7号螺钉_拧紧时间"])
)
));
// 2. 解构字段(保持原有逻辑,空值兜底)
var (orderName, productName, hzMachineSN, hzPcbsn, njMachineSN, njPcbsn) = strFields;
var (runStatus, machineModel, onlineStatus, byPass, produceModel,
hzQueryReq, hzSaveReq, njSaveReq,
hzTrayNo, njTrayNo, 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. 写入保存请求(异步+增强异常日志),收完数据给这个数据位置0
try
{
if (strSaveRequest.Contains("合装结果保存请求"))
{
WritePlcValue(plc, _op080_1IntMap["合装结果保存请求"], "0");
}
if (strSaveRequest.Contains("拧紧结果保存请求"))
{
WritePlcValue(plc, _op080_1IntMap["拧紧结果保存请求"], "0");
}
}
catch (Exception ex)
{
Console.WriteLine($"OP080-1({ip})写保存请求失败:{ex.Message}");
}
// 4. 极简条件计算(增强类型安全)
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 ? "离线" : "在线";
// 计算整体工位合格标识(所有螺钉合格则工位合格)
var allScrewQualified = new[] { screw1Result, screw2Result, screw3Result, screw4Result, screw5Result, screw6Result, screw7Result }
.All(s => s == 1);
string qualificationFlag = allScrewQualified ? "1" : "0";
// 调试日志:输出关键读取结果
Console.WriteLine($"OP080-1({ip})读取结果:产品名称={productName},运行状态={runStatusDesc},订单名称={orderName},螺钉整体合格={allScrewQualified}");
// 5. 构建数据实体严格匹配PlcProductionData字段定义
var plcData = new PlcProductionData
{
// 基础字段
PlcIp = ip.Trim(),
OccurTime = DateTime.Now,
LineCode = "line2", // 可根据实际产线调整
WorkstationCode = workstationCode,
ProductName = productName ?? string.Empty,
ProductCode = njPcbsn ?? string.Empty, // 拧紧位PCB SN作为产品编码
// 合格/返工标志
QualificationFlag = qualificationFlag,
ReworkFlag = reworkFlag,
// 设备状态相关
Automanual = machineModel, // 设备模式0-自动1-手动
Runstatus = runStatus, // 运行状态:1=空闲2=运行中3=故障
OnlineStatus = onlineStatusDesc, // 设备在线状态1=离线,0=在线
ProduceModel = produceModelDesc, // 生产模式描述
// 托盘号相关(拆分合装/拧紧位托盘号)
TrayNo = $"{hzTrayNo}!{njTrayNo}", // 合并显示
AssemblyTrayNo = hzTrayNo.ToString(), // 合装位托盘号
TightenTrayNo = njTrayNo.ToString(), // 拧紧位托盘号
// SN相关字段
SN1 = hzMachineSN ?? string.Empty, // 合装位机壳SN
SN2 = njMachineSN ?? string.Empty, // 拧紧位机壳SN
AssemblyHousingSN = hzMachineSN ?? string.Empty, // 合装位机壳SN
AssemblyPCBSN = hzPcbsn ?? string.Empty, // 合装位PCB SN
TightenHousingSN = njMachineSN ?? string.Empty, // 拧紧位机壳SN
TightenPCBSN = njPcbsn ?? string.Empty, // 拧紧位PCB 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,
// 可选字段(无值时置空)
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;
}
#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;
}
}
}
}