PLC采集

This commit is contained in:
quowingwang 2026-01-26 19:43:14 +08:00
parent f6e826c2a7
commit 9d60bb5fd1
4 changed files with 149 additions and 39 deletions

View File

@ -420,7 +420,7 @@ namespace RIZO.Admin.WebApi.PLC.Model
/// <summary>
/// 芯片 SNPWM 条码)
/// </summary>
[SugarColumn(ColumnName = "Screw7TightenTime")]
[SugarColumn(ColumnName = "ChipSN")]
public string ChipSN { get; set; }
/// <summary>

View File

@ -123,7 +123,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
Rack = (short)it.Rack, // 空值兜底
Slot = (short)it.Slot // 空值兜底
})
.Where(c => !string.IsNullOrWhiteSpace(c.Ip))
.ToList();
}
catch (Exception ex)

View File

@ -32,6 +32,7 @@ namespace RIZO.Admin.WebApi.PLC.Service
private PlcProductionDataService _plcProductionDataService = new PlcProductionDataService();
private PlantWorkstationService _plantWorkstationService = new PlantWorkstationService();
private PlcOperationResultService _plcOperationResultService = new PlcOperationResultService();
// 先在类中添加2个核心优化字段支撑高频访问
private readonly SemaphoreSlim _concurrencySemaphore = new SemaphoreSlim(15, 50); // 限制20并发适配50台PLC
@ -41,15 +42,15 @@ namespace RIZO.Admin.WebApi.PLC.Service
//MES返回PLC请求映射
private readonly Dictionary<string, string> _mesIntReturnMap = new()
{
{ "设备使能", "DB1050.DBW0" }, // Int
{ "工位开始查询结果", "DB1050.DBW2000" }, // Int
{ "保存结果", "DB1050.DBW2002" }, // Int
{ "设备使能", "DB1101.DBW0" }, // Int
{ "工位开始查询结果", "DB1101.DBW2000" }, // Int
{ "保存结果", "DB1101.DBW2002" }, // Int
};
private readonly Dictionary<string, (string Addr, int Len)> _mesStringReturnMap = new()
{
{ "产品型号", ("DB1001.DBB1016", 14) }, // String[14]
{ "订单下发", ("DB1001.DBB1032", 50) }, // String[50]
{ "产品型号", ("DB1101.DBB1016", 14) }, // String[14]
{ "订单下发", ("DB1101.DBB1032", 50) }, // String[50]
};
// OP020-2 专属地址映射合盖工位DB1001
@ -403,17 +404,9 @@ namespace RIZO.Admin.WebApi.PLC.Service
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次重试");
}
await OpenPlcConnectionAsync(plc);
if (!plc.IsConnected) return (false, null, $"{plcName}连接失败");
_plcConnPool.TryAdd(poolKey, (plc, DateTime.Now));
}
// 4. 多工位请求状态读取(精简分支,保留原逻辑,移除冗余注释)
@ -508,13 +501,12 @@ namespace RIZO.Admin.WebApi.PLC.Service
? $"{plcName}生产数据读取成功(复用连接)"
: $"{plcName}生产数据读取成功(新建连接)";
WritePlcSaveRequestResult(plc, ip, plcName, prodData, "1");
//保存成功到PLC过站表过站状态
RecordPlcOperationResult(plcName, prodData);
return (true, prodData, successMsg);
}
catch (Exception ex)
{
// 精细化异常日志,保留堆栈
Console.WriteLine($"{plcName}({ip})生产数据读取异常:{ex.Message}\n{ex.StackTrace}");
return (false, null, $"{plcName}生产数据读取失败:{ex.Message}");
}
finally
@ -529,7 +521,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
}
catch (Exception ex)
{
Console.WriteLine($"{plcName}({ip})释放PLC连接失败{ex.Message}");
}
finally
{
@ -539,7 +530,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
}
}
#region
/// <summary>
/// 读取OP020-2数据合盖工位
@ -752,8 +742,8 @@ namespace RIZO.Admin.WebApi.PLC.Service
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 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["站位结果"]);
@ -767,7 +757,7 @@ namespace RIZO.Admin.WebApi.PLC.Service
taskProductName, taskOrderName, taskProductModel, taskSN1, taskSN2,
// Int任务
taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel,
taskQueryReq, taskSaveReq, taskTrayNo, taskCameraResult, taskStationResult,
taskTrayNo, taskCameraResult, taskStationResult,
// Real任务
taskCycleTime);
@ -785,8 +775,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
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;
@ -879,8 +867,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
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["站位结果"]);
@ -893,7 +879,7 @@ namespace RIZO.Admin.WebApi.PLC.Service
taskProductName, taskOrderName, taskProductModel, taskSN1, taskSN2, taskChipSN,
// Int任务
taskRunStatus, taskMachineModel, taskOnlineStatus, taskByPass, taskProduceModel,
taskQueryReq, taskSaveReq, taskTrayNo, taskStationResult,
taskTrayNo, taskStationResult,
// Real任务
taskCycleTime);
@ -912,8 +898,6 @@ namespace RIZO.Admin.WebApi.PLC.Service
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;
@ -2134,21 +2118,85 @@ namespace RIZO.Admin.WebApi.PLC.Service
}
}
// 提取写PLC返回值的通用方法统一处理逻辑和日志
// 提取写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["工位开始查询结果"], "1");
WritePlcValue(plc, _mesIntReturnMap["保存结果"], saveResult);
WritePlcValue(plc, "产品型号", prodData.ProductModel);
WritePlcValue(plc, "订单下发", "");//??
if (prodData.ProductModel != null && prodData.ProductModel.Length > 0)
{
WritePlcString(plc, _mesStringReturnMap["产品型号"].Addr, _mesStringReturnMap["产品型号"].Len, prodData.ProductModel);
}
else
{
WritePlcString(plc, _mesStringReturnMap["产品型号"].Addr, _mesStringReturnMap["产品型号"].Len, prodData.ProductModel);
}
WritePlcString(plc, _mesStringReturnMap["订单下发"].Addr, _mesStringReturnMap["订单下发"].Len, "cpxhtest");
}
catch (Exception ex)
{;
}
}
//记录PLC过站状态
private void RecordPlcOperationResult(string plcName, PlcProductionData prodData)
{
if (string.IsNullOrWhiteSpace(plcName))
{
throw new ArgumentNullException(nameof(plcName), "PLC/工位名称不能为空");
}
if (prodData == null)
{
throw new ArgumentNullException(nameof(prodData), "PLC生产数据实体不能为空");
}
string strSN = prodData.SN2?.Trim();
if (string.IsNullOrWhiteSpace(strSN))
{
return;
}
int result = string.Equals(prodData.QualificationFlag, "1", StringComparison.OrdinalIgnoreCase) ? 1 : 0;
try
{
var existingRecord = _plcOperationResultService.Queryable()
.Where(it => it.Sn == strSN && it.Workstationcode == plcName).ToList().FirstOrDefault();
if (existingRecord != null)
{
// 情况1记录存在
if (existingRecord.Result == result)
{
return;
}
else
{
// 情况2记录存在但结果不一致执行修改操作
existingRecord.Result = result;
existingRecord.UpdatedBy = "PLC";
existingRecord.UpdatedTime = DateTime.Now;
_plcOperationResultService.Update(existingRecord);
}
}
else
{
// 情况3记录不存在执行新增操作
PlcOperationResult porNew = new PlcOperationResult();
porNew.Sn = strSN;
porNew.Workstationcode = plcName;
porNew.Result = result;
porNew.CreatedBy = "PLC";
porNew.CreatedTime = DateTime.Now;
_plcOperationResultService.Insert(porNew);
}
}
catch (Exception ex)
{
}
}
/// <summary>
/// 异步复位上传请求(独立方法,非阻塞)
/// </summary>
@ -2180,5 +2228,68 @@ namespace RIZO.Admin.WebApi.PLC.Service
Console.WriteLine($"({ip})写保存请求复位失败:{ex.Message}");
}
}
/// <summary>
/// 向PLC写入字符串字节数组形式
/// </summary>
/// <param name="plc">PLC客户端</param>
/// <param name="startAddress">字符串起始地址如DB1101.DBB1016</param>
/// <param name="strLength">PLC定义的字符串长度如14</param>
/// <param name="value">要写入的字符串</param>
private void WritePlcString(Plc plc, string startAddress, int strLength, string value)
{
// 1. 解析起始地址提取DB块、起始字节
if (!ParsePlcAddress(startAddress, out var dataType, out int dbNumber, out int startByte))
{
throw new ArgumentException($"无效的PLC字符串起始地址{startAddress}");
}
// 2. 处理字符串转字节数组长度不足补0超长截断
byte[] strBytes = Encoding.ASCII.GetBytes(value ?? "");
byte[] plcBytes = new byte[strLength]; // PLC字符串固定长度
// 复制字符串字节,超长则截断
int copyLength = Math.Min(strBytes.Length, strLength);
Array.Copy(strBytes, 0, plcBytes, 0, copyLength);
// 剩余字节填充0清空原有内容
if (copyLength < strLength)
{
Array.Clear(plcBytes, copyLength, strLength - copyLength);
}
// 3. 写入PLC批量写入字节数组
plc.WriteBytes(dataType, dbNumber, startByte, plcBytes);
}
/// </summary>
/// <param name="address">PLC地址如DB1101.DBB1016</param>
/// <param name="dataType">数据类型DB/Input等</param>
/// <param name="dbNumber">DB块编号如1101</param>
/// <param name="startByte">起始字节如1016</param>
/// <returns>是否解析成功</returns>
private bool ParsePlcAddress(string address, out DataType dataType, out int dbNumber, out int startByte)
{
dataType = DataType.DataBlock; // 默认DB块
dbNumber = 0;
startByte = 0;
try
{
// 匹配格式DB1101.DBB1016
var match = System.Text.RegularExpressions.Regex.Match(address, @"DB(\d+)\.DBB(\d+)");
if (match.Success)
{
dbNumber = int.Parse(match.Groups[1].Value);
startByte = int.Parse(match.Groups[2].Value);
return true;
}
return false;
}
catch (Exception ex)
{
return false;
}
}
}
}

View File

@ -97,8 +97,8 @@
"uniappPath": "D:\\Work" //h5
},
"GlobalConfig": {
"ConnectTimeout": 5000, //
"ReadWriteTimeout": 5000 //
"ConnectTimeout": 1000, //
"ReadWriteTimeout": 1000 //
},
"PlcPollingSettings": {
"MaxConcurrentPerPlc": 1,