PLC数据读取
This commit is contained in:
parent
8deb03bcc6
commit
96abcd2232
@ -27,18 +27,16 @@ namespace RIZO.Admin.WebApi.PLC.Model.Dto
|
||||
public int Id { get; set; }
|
||||
|
||||
public string? LineCode { get; set; }
|
||||
|
||||
public string? WorkstationCode { get; set; }
|
||||
public string? PlcIp { get; set; }
|
||||
|
||||
public string? ProductCode { get; set; }
|
||||
|
||||
public string? ProductName { get; set; }
|
||||
|
||||
public string? PartCode { get; set; }
|
||||
public string? ProductModel { get; set; }
|
||||
|
||||
public string? PartName { get; set; }
|
||||
|
||||
public string? ProcessName { get; set; }
|
||||
public string? WorkstationName { get; set; }
|
||||
|
||||
public string? ParamName { get; set; }
|
||||
|
||||
@ -53,9 +51,17 @@ namespace RIZO.Admin.WebApi.PLC.Model.Dto
|
||||
public int? ProductionCycle { get; set; }
|
||||
public int? AutoManual { get; set; }
|
||||
public int? RunStatus { get; set; }
|
||||
public string? TrayNo { get; set; }
|
||||
public string? ProduceModel { get; set; }
|
||||
|
||||
public string? OnlineStatus { get; set; }
|
||||
|
||||
public string? Remark { get; set; }
|
||||
|
||||
public string? SN1 { get; set; }
|
||||
public string? SN2 { get; set; }
|
||||
public string? CameraResult { get; set; }
|
||||
|
||||
public string? CreatedBy { get; set; }
|
||||
|
||||
public DateTime? CreatedTime { get; set; }
|
||||
|
||||
@ -26,6 +26,16 @@
|
||||
public string ReadMessage { get; set; }
|
||||
public bool WriteSuccess { get; set; }
|
||||
public string WriteMessage { get; set; }
|
||||
|
||||
//设备运行状态
|
||||
public string RunStatus { get; set; }
|
||||
//设备模式
|
||||
public string MachineModel { get; set; }
|
||||
//设备在线模式
|
||||
public string OnLineStatus { get; set; }
|
||||
//设备生产模式
|
||||
public string ProduceModel { get; set; }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -21,6 +21,18 @@ namespace RIZO.Admin.WebApi.PLC.Model
|
||||
[SugarColumn(ColumnName = "line_code")]
|
||||
public string LineCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 工站编码
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "workstationCode")]
|
||||
public string WorkstationCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 工站名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "workstationName")]
|
||||
public string WorkstationName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// PLC IP地址
|
||||
/// </summary>
|
||||
@ -40,22 +52,10 @@ namespace RIZO.Admin.WebApi.PLC.Model
|
||||
public string ProductName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 零件编码
|
||||
/// 产品型号
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "part_code")]
|
||||
public string PartCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 零件名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "part_name")]
|
||||
public string PartName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 工序名称
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "process_name")]
|
||||
public string ProcessName { get; set; }
|
||||
[SugarColumn(ColumnName = "product_model")]
|
||||
public string ProductModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 参数名称
|
||||
@ -100,11 +100,28 @@ namespace RIZO.Admin.WebApi.PLC.Model
|
||||
public int? AutoManual { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 运行状态:1正常,2异常
|
||||
/// 运行状态:1=空闲,2=运行中,3=故障;
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "runstatus")]
|
||||
public int? RunStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 托盘号
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "trayNo")]
|
||||
public string TrayNo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 生产模式
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "produceModel")]
|
||||
public string ProduceModel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 设备在线状态1=离线,0=在线
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "onlineStatus")]
|
||||
public string OnlineStatus { get; set; }
|
||||
/// <summary>
|
||||
/// 备注
|
||||
/// </summary>
|
||||
@ -134,5 +151,23 @@ namespace RIZO.Admin.WebApi.PLC.Model
|
||||
[SugarColumn(ColumnName = "uPDATED_TIME")]
|
||||
public DateTime? UpdatedTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 条码查询
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "SN_1")]
|
||||
public string SN1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 结果上传条码
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "SN_2")]
|
||||
public string SN2 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 相机结果1,OK,2,NG
|
||||
/// </summary>
|
||||
[SugarColumn(ColumnName = "cameraResult")]
|
||||
public string CameraResult { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
@ -33,14 +33,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
{
|
||||
predicate = predicate.And(x => x.ProductCode == parm.ProductCode);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(parm.PartName))
|
||||
{
|
||||
predicate = predicate.And(x => x.PartName == parm.PartName);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(parm.PartCode))
|
||||
{
|
||||
predicate = predicate.And(x => x.PartCode == parm.PartCode);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(parm.ReworkFlag))
|
||||
{
|
||||
predicate = predicate.And(x => x.ReworkFlag == parm.ReworkFlag);
|
||||
@ -78,14 +70,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
{
|
||||
predicate = predicate.And(x => x.ProductCode == parm.ProductCode);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(parm.PartName))
|
||||
{
|
||||
predicate = predicate.And(x => x.PartName == parm.PartName);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(parm.PartCode))
|
||||
{
|
||||
predicate = predicate.And(x => x.PartCode == parm.PartCode);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(parm.ReworkFlag))
|
||||
{
|
||||
predicate = predicate.And(x => x.ReworkFlag == parm.ReworkFlag);
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
// 统一引入所有必要命名空间
|
||||
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;
|
||||
@ -62,28 +64,34 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
|
||||
#region PLC地址块儿映射
|
||||
// OP070-1 专属地址映射
|
||||
private readonly Dictionary<string, string> _op070_1IntMap = new Dictionary<string, string>
|
||||
private readonly Dictionary<string, (string Addr, int Len)> _op070_1StringMap = new()
|
||||
{
|
||||
{ "运行状态", "DB1001.DBW0" }, // 运行状态
|
||||
{ "设备模式", "DB1001.DBW2" }, // 设备模式
|
||||
{ "设备在线状态", "DB1001.DBW4" },// 设备在线状态
|
||||
{ "ByPass", "DB1001.DBW6" }, // ByPass
|
||||
{ "生产模式", "DB1001.DBW8" }, // 生产模式
|
||||
{ "查询请求", "DB1001.DBW2000" }, // 查询请求
|
||||
{ "保存请求", "DB1001.DBW2002" }, // 保存请求
|
||||
{ "托盘号", "DB1001.DBW2004" }, // 托盘号
|
||||
{ "相机结果", "DB1001.DBW2164" }, // 相机结果
|
||||
{ "站位结果", "DB1001.DBW2166" } // 站位结果
|
||||
{ "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte
|
||||
{ "产品型号_48", ("DB1001.DBB1000", 48) }, // String[48]
|
||||
{ "产品名称", ("DB1001.DBB1054", 48) }, // String[48]
|
||||
{ "产品型号_28", ("DB1001.DBB2006", 28) }, // String[28]
|
||||
{ "SN_1", ("DB1001.DBB2100", 28) }, // String[28]
|
||||
{ "SN_2", ("DB1001.DBB2134", 28) } // String[28]
|
||||
};
|
||||
|
||||
private readonly Dictionary<string, (string Addr, int Len)> _op070_1StringMap = new Dictionary<string, (string Addr, int Len)>
|
||||
private readonly Dictionary<string, string> _op070_1IntMap = new()
|
||||
{
|
||||
{ "报警信息", ("DB1001.DBB58", 48) }, // Array[1..48] of Byte
|
||||
{ "产品名称", ("DB1001.DBB1054", 48) }, // String[48]
|
||||
{ "产品型号", ("DB1001.DBB2006", 28) }, // String[28]
|
||||
{ "SN_1", ("DB1001.DBB2100", 28) }, // 条码查询
|
||||
{ "SN_2", ("DB1001.DBB2134", 28) } // 结果上传条码
|
||||
{ "运行状态", "DB1001.DBW0" }, // Int
|
||||
{ "设备模式", "DB1001.DBW2" }, // Int
|
||||
{ "设备在线状态", "DB1001.DBW4" }, // Int
|
||||
{ "ByPass", "DB1001.DBW6" }, // Int
|
||||
{ "生产模式", "DB1001.DBW8" }, // Int
|
||||
{ "实际产量", "DB1001.DBD1104" }, // DInt
|
||||
{ "合格数量", "DB1001.DBD1108" }, // DInt
|
||||
{ "失败数量", "DB1001.DBD1112" }, // DInt
|
||||
{ "查询请求", "DB1001.DBW2000" }, // Int
|
||||
{ "保存请求", "DB1001.DBW2002" }, // Int
|
||||
{ "托盘号", "DB1001.DBW2004" }, // Int
|
||||
{ "相机结果", "DB1001.DBW2164" }, // Int
|
||||
{ "站位结果", "DB1001.DBW2166" }, // Int
|
||||
{ "节拍时间", "DB1001.DBD2168" } // Real
|
||||
};
|
||||
|
||||
#endregion
|
||||
/// <summary>
|
||||
/// 构造函数(依赖注入获取PLC配置)
|
||||
@ -99,83 +107,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
#endregion
|
||||
|
||||
#region 核心业务方法
|
||||
///// <summary>
|
||||
///// 读取PLC生产数据(严格匹配业务地址和解析规则)
|
||||
///// </summary>
|
||||
///// <param name="ip">PLC IP地址</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,
|
||||
// short rack,
|
||||
// short slot,
|
||||
// CpuType cpuType = CpuType.S71500)
|
||||
//{
|
||||
// // 参数校验
|
||||
// if (string.IsNullOrWhiteSpace(ip))
|
||||
// return (false, null, "PLC IP地址不能为空");
|
||||
|
||||
// Plc plc = null;
|
||||
// try
|
||||
// {
|
||||
// // 初始化PLC连接
|
||||
// plc = CreatePlcClient(cpuType, ip, rack, slot);
|
||||
// await OpenPlcConnectionAsync(plc);
|
||||
|
||||
// if (!plc.IsConnected)
|
||||
// return (false, null, "PLC连接失败");
|
||||
|
||||
// // 构建生产数据实体
|
||||
// var prodData = new PlcProductionData
|
||||
// {
|
||||
// PlcIp = ip.Trim(),
|
||||
// OccurTime = DateTime.Now, // MES自配时间
|
||||
|
||||
// // 字符串字段(按规则解析)
|
||||
// LineCode = await ReadPlcStringAsync(plc, _plcStringMap["LineCode"].Addr, _plcStringMap["LineCode"].Len),
|
||||
// ProductCode = await ReadPlcStringAsync(plc, _plcStringMap["ProductCode"].Addr, _plcStringMap["ProductCode"].Len),
|
||||
// ProductName = await ReadPlcStringAsync(plc, _plcStringMap["ProductName"].Addr, _plcStringMap["ProductName"].Len),
|
||||
// PartCode = await ReadPlcStringAsync(plc, _plcStringMap["PartCode"].Addr, _plcStringMap["PartCode"].Len),
|
||||
// PartName = await ReadPlcStringAsync(plc, _plcStringMap["PartName"].Addr, _plcStringMap["PartName"].Len),
|
||||
// ProcessName = await ReadPlcStringAsync(plc, _plcStringMap["ProcessName"].Addr, _plcStringMap["ProcessName"].Len),
|
||||
// ParamName = await ReadPlcStringAsync(plc, _plcStringMap["ParamName"].Addr, _plcStringMap["ParamName"].Len),
|
||||
// ParamValue = await ReadPlcStringAsync(plc, _plcStringMap["ParamValue"].Addr, _plcStringMap["ParamValue"].Len),
|
||||
|
||||
// // 修复:改为异步读取整数,确保类型兼容
|
||||
// //合格标志(0默认,1合格,2不合格)
|
||||
// QualificationFlag = (await ReadPlcIntAsync(plc, _plcIntMap["QualificationFlag"])).ToString(),
|
||||
// //返工标志(0正常,1返工)
|
||||
// ReworkFlag = (await ReadPlcIntAsync(plc, _plcIntMap["ReworkFlag"])).ToString(),
|
||||
// //设备自动手动:0-自动,1-手动
|
||||
// AutoManual = (await ReadPlcIntAsync(plc, _plcIntMap["AutoManual"])),
|
||||
// //运行状态:1正常,2异常
|
||||
// RunStatus = (await ReadPlcIntAsync(plc, _plcIntMap["RunStatus"])),
|
||||
// //生产节拍秒
|
||||
// ProductionCycle = await ReadPlcIntAsync(plc, _plcIntMap["ProductionCycle"])
|
||||
// };
|
||||
|
||||
// // 空值兜底(避免入库报错)
|
||||
// prodData.QualificationFlag ??= "0";
|
||||
// prodData.ReworkFlag ??= "0";
|
||||
// prodData.ProductionCycle ??= 0;
|
||||
|
||||
// // 保存生产数据到MES数据库
|
||||
// _plcProductionDataService.AddPlcProductionData(prodData);
|
||||
// return (true, prodData, "生产数据读取成功");
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// return (false, null, $"生产数据读取失败:{ex.Message}");
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// ReleasePlcConnection(plc);
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 读取PLC生产数据(优化版:连接池+并发控制+批量读取+异常防护)
|
||||
@ -185,6 +116,173 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
/// <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. 强化参数校验(源头避免无效请求)
|
||||
// if (string.IsNullOrWhiteSpace(ip))
|
||||
// return (false, null, "PLC IP地址不能为空");
|
||||
// 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}"; // 连接池唯一标识
|
||||
|
||||
// // 2. 并发控制(防止50台PLC同时访问导致端口/线程耗尽)
|
||||
// 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);
|
||||
// // 连接重试(应对临时网络波动)
|
||||
// int retryCount = 2;
|
||||
// bool isConnected = false;
|
||||
// while (retryCount-- > 0 && !isConnected)
|
||||
// {
|
||||
// try
|
||||
// {
|
||||
// await OpenPlcConnectionAsync(plc);
|
||||
// isConnected = plc.IsConnected;
|
||||
// }
|
||||
// catch
|
||||
// {
|
||||
// if (retryCount > 0) await Task.Delay(100); // 重试间隔
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (!isConnected)
|
||||
// return (false, null, "PLC连接失败(含2次重试)");
|
||||
|
||||
// // 新连接加入池
|
||||
// _plcConnPool.TryAdd(poolKey, (plc, DateTime.Now));
|
||||
// }
|
||||
|
||||
// //先读取查询请求标志,判断是否需要读取生产数据
|
||||
// int iQueryRequest = await ReadPlcIntAsync(plc, _plcIntMap["QueryRequest"]);
|
||||
// if (iQueryRequest != 1)
|
||||
// {
|
||||
// return (false, null, "PLC查询请求标志未设置,跳过生产数据读取");
|
||||
// }
|
||||
// if (plcName == "OP070-1")
|
||||
// {
|
||||
|
||||
|
||||
// }
|
||||
// // 4. 批量并行读取(核心优化:替代串行await,提升效率30%+)
|
||||
// // 4.1 字符串字段批量读取
|
||||
// var stringTasks = new Dictionary<string, Task<string>>
|
||||
// {
|
||||
// { "LineCode", ReadPlcStringAsync(plc, _plcStringMap["LineCode"].Addr, _plcStringMap["LineCode"].Len) },
|
||||
// { "ProductCode", ReadPlcStringAsync(plc, _plcStringMap["ProductCode"].Addr, _plcStringMap["ProductCode"].Len) },
|
||||
// { "ProductName", ReadPlcStringAsync(plc, _plcStringMap["ProductName"].Addr, _plcStringMap["ProductName"].Len) },
|
||||
// { "PartCode", ReadPlcStringAsync(plc, _plcStringMap["PartCode"].Addr, _plcStringMap["PartCode"].Len) },
|
||||
// { "PartName", ReadPlcStringAsync(plc, _plcStringMap["PartName"].Addr, _plcStringMap["PartName"].Len) },
|
||||
// { "ProcessName", ReadPlcStringAsync(plc, _plcStringMap["ProcessName"].Addr, _plcStringMap["ProcessName"].Len) },
|
||||
// { "ParamName", ReadPlcStringAsync(plc, _plcStringMap["ParamName"].Addr, _plcStringMap["ParamName"].Len) },
|
||||
// { "ParamValue", ReadPlcStringAsync(plc, _plcStringMap["ParamValue"].Addr, _plcStringMap["ParamValue"].Len) }
|
||||
// };
|
||||
|
||||
// // 4.2 整数字段批量读取
|
||||
// var intTasks = new Dictionary<string, Task<int>>
|
||||
// {
|
||||
// { "QualificationFlag", ReadPlcIntAsync(plc, _plcIntMap["QualificationFlag"]) },
|
||||
// { "ReworkFlag", ReadPlcIntAsync(plc, _plcIntMap["ReworkFlag"]) },
|
||||
// { "AutoManual", ReadPlcIntAsync(plc, _plcIntMap["AutoManual"]) },
|
||||
// { "RunStatus", ReadPlcIntAsync(plc, _plcIntMap["RunStatus"]) },
|
||||
// { "ProductionCycle", ReadPlcIntAsync(plc, _plcIntMap["ProductionCycle"]) }
|
||||
// };
|
||||
|
||||
// // 等待所有读取完成(并行执行,减少等待时间)
|
||||
// IEnumerable<Task> allTasks = stringTasks.Values.Cast<Task>()
|
||||
// .Concat(intTasks.Values.Cast<Task>());
|
||||
// await Task.WhenAll(allTasks);
|
||||
|
||||
// // 5. 构建数据实体(空值安全处理,单个字段失败不影响整体)
|
||||
// var prodData = new PlcProductionData
|
||||
// {
|
||||
// PlcIp = ip.Trim(),
|
||||
// OccurTime = DateTime.Now,
|
||||
|
||||
// // 字符串字段(空值兜底)
|
||||
// LineCode = await stringTasks["LineCode"] ?? string.Empty,
|
||||
// ProductCode = await stringTasks["ProductCode"] ?? string.Empty,
|
||||
// ProductName = await stringTasks["ProductName"] ?? string.Empty,
|
||||
// PartCode = await stringTasks["PartCode"] ?? string.Empty,
|
||||
// PartName = await stringTasks["PartName"] ?? string.Empty,
|
||||
// ProcessName = await stringTasks["ProcessName"] ?? string.Empty,
|
||||
// ParamName = await stringTasks["ParamName"] ?? string.Empty,
|
||||
// ParamValue = await stringTasks["ParamValue"] ?? string.Empty,
|
||||
|
||||
// // 整数字段(读取失败返回0,不中断流程)
|
||||
// QualificationFlag = (await intTasks["QualificationFlag"]).ToString(),
|
||||
// ReworkFlag = (await intTasks["ReworkFlag"]).ToString(),
|
||||
// AutoManual = await intTasks["AutoManual"],
|
||||
// RunStatus = await intTasks["RunStatus"],
|
||||
// ProductionCycle = await intTasks["ProductionCycle"]
|
||||
// };
|
||||
|
||||
// // 空值兜底(保留你的原始逻辑)
|
||||
// prodData.QualificationFlag ??= "0";
|
||||
// prodData.ReworkFlag ??= "0";
|
||||
// prodData.ProductionCycle ??= 0;
|
||||
|
||||
// // 6. 异步保存数据(非阻塞,提升响应速度)
|
||||
// _ = Task.Run(() => _plcProductionDataService.AddPlcProductionData(prodData))
|
||||
// .ContinueWith(t =>
|
||||
// {
|
||||
// if (t.IsFaulted)
|
||||
// {
|
||||
// // 记录保存失败日志(建议替换为ILogger)
|
||||
// Console.WriteLine($"PLC({ip})数据保存失败:{t.Exception?.InnerException?.Message}");
|
||||
// }
|
||||
// });
|
||||
|
||||
// var successMsg = isConnReused ? "生产数据读取成功(复用连接)" : "生产数据读取成功(新建连接)";
|
||||
// return (true, prodData, successMsg);
|
||||
// }
|
||||
// catch (Exception ex)
|
||||
// {
|
||||
// // 精细化异常日志(便于定位具体PLC故障)
|
||||
// Console.WriteLine($"PLC({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}");
|
||||
// return (false, null, $"生产数据读取失败:{ex.Message}");
|
||||
// }
|
||||
// finally
|
||||
// {
|
||||
// // 7. 资源安全释放(核心:复用连接不释放,仅新建连接释放;必须释放并发信号量)
|
||||
// if (!isConnReused)
|
||||
// {
|
||||
// ReleasePlcConnection(plc);
|
||||
// }
|
||||
// _concurrencySemaphore.Release(); // 防止死锁的关键
|
||||
// }
|
||||
//}
|
||||
|
||||
/// <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,
|
||||
@ -192,157 +290,311 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
short slot,
|
||||
CpuType cpuType = CpuType.S71500)
|
||||
{
|
||||
// 1. 强化参数校验(源头避免无效请求)
|
||||
// 1. 强化参数校验(补充PLC名称校验+白名单控制)
|
||||
if (string.IsNullOrWhiteSpace(ip))
|
||||
return (false, null, "PLC IP地址不能为空");
|
||||
if (rack < 0 || rack > 10) // 工业场景机架号常规范围
|
||||
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) // 工业场景槽位号常规范围
|
||||
if (slot < 0 || slot > 4)
|
||||
return (false, null, $"PLC槽位号{slot}无效(有效值0-4)");
|
||||
|
||||
Plc plc = null;
|
||||
bool isConnReused = false; // 标记是否复用连接
|
||||
var poolKey = $"{ip}_{rack}_{slot}_{cpuType}"; // 连接池唯一标识
|
||||
// 仅放行指定工位,防止非法请求
|
||||
var supportedPlcs = new HashSet<string> { "OP070-1", "OP080" };
|
||||
if (!supportedPlcs.Contains(plcName))
|
||||
return (false, null, $"仅支持OP070-1/OP080,当前工位:{plcName}");
|
||||
|
||||
// 2. 并发控制(防止50台PLC同时访问导致端口/线程耗尽)
|
||||
Plc plc = null;
|
||||
bool isConnReused = false;
|
||||
var poolKey = $"{ip}_{rack}_{slot}_{cpuType}";
|
||||
PlcProductionData prodData = null;
|
||||
|
||||
// 2. 并发控制(核心保留,防止端口/线程耗尽)
|
||||
await _concurrencySemaphore.WaitAsync();
|
||||
|
||||
try
|
||||
{
|
||||
// 3. 连接池复用(核心优化:避免高频创建/销毁连接)
|
||||
// 3. 连接池复用+精简重试逻辑(提升可读性)
|
||||
if (_plcConnPool.TryGetValue(poolKey, out var poolItem) && poolItem.Client.IsConnected)
|
||||
{
|
||||
plc = poolItem.Client;
|
||||
_plcConnPool.TryUpdate(poolKey, (plc, DateTime.Now), poolItem); // 更新最后使用时间
|
||||
_plcConnPool.TryUpdate(poolKey, (plc, DateTime.Now), poolItem);
|
||||
isConnReused = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// 初始化新连接(保留你的原始逻辑,增加重试)
|
||||
plc = CreatePlcClient(cpuType, ip, rack, slot);
|
||||
// 连接重试(应对临时网络波动)
|
||||
int retryCount = 2;
|
||||
bool isConnected = false;
|
||||
while (retryCount-- > 0 && !isConnected)
|
||||
// for循环替代while,逻辑更直观
|
||||
for (int retry = 0; retry < 2; retry++)
|
||||
{
|
||||
try
|
||||
{
|
||||
await OpenPlcConnectionAsync(plc);
|
||||
isConnected = plc.IsConnected;
|
||||
if (isConnected) break;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (retryCount > 0) await Task.Delay(100); // 重试间隔
|
||||
if (retry < 1) await Task.Delay(100); // 仅第一次失败后重试
|
||||
}
|
||||
}
|
||||
|
||||
if (!isConnected)
|
||||
return (false, null, "PLC连接失败(含2次重试)");
|
||||
return (false, null, $"{plcName}连接失败(含2次重试)");
|
||||
|
||||
// 新连接加入池
|
||||
_plcConnPool.TryAdd(poolKey, (plc, DateTime.Now));
|
||||
}
|
||||
|
||||
//先读取查询请求标志,判断是否需要读取生产数据
|
||||
int iQueryRequest = await ReadPlcIntAsync(plc, _plcIntMap["QueryRequest"]);
|
||||
if (iQueryRequest != 1)
|
||||
// 4. 多工位查询请求标志适配(核心:区分不同工位的寄存器地址)
|
||||
int iQueryRequest = 0;
|
||||
int iSaveRequest = 0;
|
||||
if (plcName == "OP070-1" || plcName == "OP070-2" || plcName == "OP070-3" || plcName == "OP075")
|
||||
{
|
||||
return (false, null, "PLC查询请求标志未设置,跳过生产数据读取");
|
||||
iQueryRequest = await ReadPlcIntAsync(plc, _op070_1IntMap["查询请求"]);
|
||||
iSaveRequest = await ReadPlcIntAsync(plc, _op070_1IntMap["保存请求"]);
|
||||
}
|
||||
// 无效请求直接返回(避免无谓资源消耗)
|
||||
if (iQueryRequest != 1 && iSaveRequest != 1)
|
||||
{
|
||||
return (false, null, $"");
|
||||
}
|
||||
|
||||
if (iQueryRequest == 1)
|
||||
{
|
||||
|
||||
}
|
||||
// 5. 多工位专属数据读取(预留扩展,逻辑隔离)
|
||||
if (plcName == "OP070-1")
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
// 4. 批量并行读取(核心优化:替代串行await,提升效率30%+)
|
||||
// 4.1 字符串字段批量读取
|
||||
var stringTasks = new Dictionary<string, Task<string>>
|
||||
{
|
||||
{ "LineCode", ReadPlcStringAsync(plc, _plcStringMap["LineCode"].Addr, _plcStringMap["LineCode"].Len) },
|
||||
{ "ProductCode", ReadPlcStringAsync(plc, _plcStringMap["ProductCode"].Addr, _plcStringMap["ProductCode"].Len) },
|
||||
{ "ProductName", ReadPlcStringAsync(plc, _plcStringMap["ProductName"].Addr, _plcStringMap["ProductName"].Len) },
|
||||
{ "PartCode", ReadPlcStringAsync(plc, _plcStringMap["PartCode"].Addr, _plcStringMap["PartCode"].Len) },
|
||||
{ "PartName", ReadPlcStringAsync(plc, _plcStringMap["PartName"].Addr, _plcStringMap["PartName"].Len) },
|
||||
{ "ProcessName", ReadPlcStringAsync(plc, _plcStringMap["ProcessName"].Addr, _plcStringMap["ProcessName"].Len) },
|
||||
{ "ParamName", ReadPlcStringAsync(plc, _plcStringMap["ParamName"].Addr, _plcStringMap["ParamName"].Len) },
|
||||
{ "ParamValue", ReadPlcStringAsync(plc, _plcStringMap["ParamValue"].Addr, _plcStringMap["ParamValue"].Len) }
|
||||
};
|
||||
|
||||
// 4.2 整数字段批量读取
|
||||
var intTasks = new Dictionary<string, Task<int>>
|
||||
{
|
||||
{ "QualificationFlag", ReadPlcIntAsync(plc, _plcIntMap["QualificationFlag"]) },
|
||||
{ "ReworkFlag", ReadPlcIntAsync(plc, _plcIntMap["ReworkFlag"]) },
|
||||
{ "AutoManual", ReadPlcIntAsync(plc, _plcIntMap["AutoManual"]) },
|
||||
{ "RunStatus", ReadPlcIntAsync(plc, _plcIntMap["RunStatus"]) },
|
||||
{ "ProductionCycle", ReadPlcIntAsync(plc, _plcIntMap["ProductionCycle"]) }
|
||||
};
|
||||
|
||||
// 等待所有读取完成(并行执行,减少等待时间)
|
||||
IEnumerable<Task> allTasks = stringTasks.Values.Cast<Task>()
|
||||
.Concat(intTasks.Values.Cast<Task>());
|
||||
await Task.WhenAll(allTasks);
|
||||
|
||||
// 5. 构建数据实体(空值安全处理,单个字段失败不影响整体)
|
||||
var prodData = new PlcProductionData
|
||||
{
|
||||
PlcIp = ip.Trim(),
|
||||
OccurTime = DateTime.Now,
|
||||
|
||||
// 字符串字段(空值兜底)
|
||||
LineCode = await stringTasks["LineCode"] ?? string.Empty,
|
||||
ProductCode = await stringTasks["ProductCode"] ?? string.Empty,
|
||||
ProductName = await stringTasks["ProductName"] ?? string.Empty,
|
||||
PartCode = await stringTasks["PartCode"] ?? string.Empty,
|
||||
PartName = await stringTasks["PartName"] ?? string.Empty,
|
||||
ProcessName = await stringTasks["ProcessName"] ?? string.Empty,
|
||||
ParamName = await stringTasks["ParamName"] ?? string.Empty,
|
||||
ParamValue = await stringTasks["ParamValue"] ?? string.Empty,
|
||||
|
||||
// 整数字段(读取失败返回0,不中断流程)
|
||||
QualificationFlag = (await intTasks["QualificationFlag"]).ToString(),
|
||||
ReworkFlag = (await intTasks["ReworkFlag"]).ToString(),
|
||||
AutoManual = await intTasks["AutoManual"],
|
||||
RunStatus = await intTasks["RunStatus"],
|
||||
ProductionCycle = await intTasks["ProductionCycle"]
|
||||
};
|
||||
|
||||
// 空值兜底(保留你的原始逻辑)
|
||||
prodData.QualificationFlag ??= "0";
|
||||
prodData.ReworkFlag ??= "0";
|
||||
prodData.ProductionCycle ??= 0;
|
||||
|
||||
// 6. 异步保存数据(非阻塞,提升响应速度)
|
||||
_ = Task.Run(() => _plcProductionDataService.AddPlcProductionData(prodData))
|
||||
.ContinueWith(t =>
|
||||
if (iSaveRequest == 1)
|
||||
{
|
||||
if (t.IsFaulted)
|
||||
{
|
||||
// 记录保存失败日志(建议替换为ILogger)
|
||||
Console.WriteLine($"PLC({ip})数据保存失败:{t.Exception?.InnerException?.Message}");
|
||||
}
|
||||
});
|
||||
prodData = await ReadOP070_1DataAsync(plc, ip, plcName);
|
||||
}
|
||||
}
|
||||
else if (plcName == "OP080")
|
||||
{
|
||||
//prodData = await ReadOP080DataAsync(plc, ip);
|
||||
}
|
||||
|
||||
var successMsg = isConnReused ? "生产数据读取成功(复用连接)" : "生产数据读取成功(新建连接)";
|
||||
// 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($"PLC({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}");
|
||||
return (false, null, $"生产数据读取失败:{ex.Message}");
|
||||
// 精细化异常日志(包含PLC名称,快速定位故障)
|
||||
Console.WriteLine($"{plcName}({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}");
|
||||
return (false, null, $"{plcName}生产数据读取失败:{ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
// 7. 资源安全释放(核心:复用连接不释放,仅新建连接释放;必须释放并发信号量)
|
||||
// 资源安全释放(核心:防止死锁/连接泄漏)
|
||||
if (!isConnReused)
|
||||
{
|
||||
ReleasePlcConnection(plc);
|
||||
}
|
||||
_concurrencySemaphore.Release(); // 防止死锁的关键
|
||||
_concurrencySemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
#region 工位专属读取方法(扩展友好,逻辑隔离)
|
||||
/// <summary>
|
||||
/// OP070-1专属数据读取(精简版:高效+易读)
|
||||
/// </summary>
|
||||
//private async Task<PlcProductionData> ReadOP070_1DataAsync(Plc plc, string ip)
|
||||
//{
|
||||
// // 1. 批量并行读取字符串字段
|
||||
// // 合并并行读取
|
||||
// var (strFields, intFields) = 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["SN_1"].Addr, _op070_1StringMap["SN_1"].Len),
|
||||
// await ReadPlcStringAsync(plc, _op070_1StringMap["SN_2"].Addr, _op070_1StringMap["SN_2"].Len)
|
||||
// ),
|
||||
// // 整数字段
|
||||
// (
|
||||
// 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["站位结果"])
|
||||
// )
|
||||
// ));
|
||||
|
||||
// // 2.解构字段(保持原有逻辑)
|
||||
// var (alarmMsg, productName, product_model, sn1, product_code) = strFields;
|
||||
// var (runStatus, machineModel, onlineStatus, byPass, produceModel, trayNo, stationResult) = intFields;
|
||||
|
||||
// // 3. 写入保存请求(极简异常防护,不影响主流程)
|
||||
// try { WritePlcValue(plc, _op070_1IntMap["保存请求"], "1"); }
|
||||
// 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})"
|
||||
// };
|
||||
|
||||
// // 5. 构建数据实体(极简空值兜底,逻辑清晰)
|
||||
// return new PlcProductionData
|
||||
// {
|
||||
// //加一个产品型号
|
||||
// PlcIp = ip.Trim(),
|
||||
// OccurTime = DateTime.Now,
|
||||
// LineCode = "line2",
|
||||
// WorkstationCode = "OP070-1",
|
||||
// ProductModel = product_model ?? "",
|
||||
// ProductName = productName ?? "",
|
||||
// ProductCode = product_code,
|
||||
// QualificationFlag = stationResult.ToString(),
|
||||
// ReworkFlag = reworkFlag,
|
||||
// AutoManual = machineModel,
|
||||
// RunStatus = runStatus,
|
||||
// OnlineStatus = onlineStatus.ToString(),
|
||||
// TrayNo = trayNo.ToString(),
|
||||
// ProduceModel = produceModelDesc,
|
||||
// CreatedBy = "PLC",
|
||||
// CreatedTime = DateTime.Now
|
||||
// };
|
||||
//}
|
||||
|
||||
/// <summary>
|
||||
/// 优化版OP070-1数据读取(集成增强型PLC读写方法)
|
||||
/// </summary>
|
||||
private async Task<PlcProductionData> ReadOP070_1DataAsync(Plc plc, string ip,string workstationCode)
|
||||
{
|
||||
// 1. 批量并行读取所有字段(沿用原有结构+增强型读取方法)
|
||||
var (strFields, intFields, dintFields, 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["产品型号_48"].Addr, _op070_1StringMap["产品型号_48"].Len),
|
||||
await ReadPlcStringAsync(plc, _op070_1StringMap["产品型号_28"].Addr, _op070_1StringMap["产品型号_28"].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 (alarmMsg, productName, productModel48, productModel28, 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["保存请求"], "1"); }
|
||||
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})读取结果:产品型号={productModel28 ?? productModel48},运行状态={runStatusDesc},产量={actualOutput}");
|
||||
|
||||
// 5. 构建数据实体(增强空值处理)
|
||||
return new PlcProductionData
|
||||
{
|
||||
PlcIp = ip.Trim(),
|
||||
OccurTime = DateTime.Now,
|
||||
LineCode = "line2",
|
||||
WorkstationCode = workstationCode,
|
||||
ProductModel = productModel28 ?? productModel48 ?? 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
|
||||
};
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// 可选:添加连接池清理方法(防止长期运行导致连接泄露)
|
||||
private void CleanupExpiredConnections()
|
||||
{
|
||||
@ -394,25 +646,21 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
/// <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
|
||||
};
|
||||
|
||||
var result = new PlcTestResult { PlcName = config.PlcName, Ip = config.Ip };
|
||||
Plc plc = null;
|
||||
|
||||
try
|
||||
{
|
||||
// 初始化PLC客户端
|
||||
// 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)
|
||||
{
|
||||
@ -422,41 +670,34 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
}
|
||||
await connectTask;
|
||||
|
||||
// 检查连接状态
|
||||
if (plc.IsConnected)
|
||||
{
|
||||
result.ConnectSuccess = true;
|
||||
result.ConnectMessage = "连接成功";
|
||||
}
|
||||
else
|
||||
// 4. 连接状态校验(精简分支)
|
||||
if (!plc.IsConnected)
|
||||
{
|
||||
result.ConnectSuccess = false;
|
||||
result.ConnectMessage = "连接失败(PLC未返回连接状态)";
|
||||
return result;
|
||||
}
|
||||
result.ConnectSuccess = true;
|
||||
result.ConnectMessage = "连接成功";
|
||||
|
||||
string TestAddress = "DB1010.DBB238"; // 测试地址
|
||||
|
||||
// 读取测试
|
||||
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 = "读取成功";
|
||||
result.ReadMessage = "通用地址读取成功";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.ReadSuccess = false;
|
||||
result.ReadMessage = $"读取失败:{ex.Message}";
|
||||
result.ReadMessage = $"通用地址读取失败:{ex.Message}";
|
||||
}
|
||||
}
|
||||
|
||||
// 写入测试
|
||||
if (!string.IsNullOrWhiteSpace(TestAddress) && !string.IsNullOrWhiteSpace(TestAddress))
|
||||
{
|
||||
// 写入测试
|
||||
try
|
||||
{
|
||||
bool writeOk = await Task.Run(() => WritePlcValue(plc, TestAddress, "1"), cancellationToken);
|
||||
@ -469,6 +710,30 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
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)
|
||||
{
|
||||
@ -617,10 +882,25 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
/// <summary>
|
||||
/// 异步打开PLC连接(封装同步方法为异步)
|
||||
/// </summary>
|
||||
private async Task OpenPlcConnectionAsync(Plc plc)
|
||||
// 优化后的OpenPlcConnectionAsync
|
||||
private async Task<bool> OpenPlcConnectionAsync(Plc plc)
|
||||
{
|
||||
if (plc == null) return;
|
||||
await Task.Run(() => plc.Open());
|
||||
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>
|
||||
@ -736,7 +1016,58 @@ namespace RIZO.Admin.WebApi.PLC.Service
|
||||
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>
|
||||
|
||||
@ -50,6 +50,7 @@ namespace RIZO.Service.MES.alarm
|
||||
/// <param name="model"></param>
|
||||
/// <returns></returns>
|
||||
public AlarmInfo AddAlarmInfo(AlarmInfo model)
|
||||
|
||||
{
|
||||
return Insertable(model).ExecuteReturnEntity();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user