using JinYuan.Helper; using JinYuan.MES.Enums; using JinYuan.MES.Models; using JinYuan.Models; using JinYuan.VirtualDataLibrary; using JinYuan.VirtualDataLibrary.Utlis; using Language; using PLC; using PLCCommunication; using PLCCommunication.Common; using PLCCommunication.Common.DataConvert; using SqlSugar; using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Diagnostics; using System.Diagnostics.Eventing.Reader; using System.IO; using System.Linq; using System.Reflection; using System.Security.Policy; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using static System.Runtime.CompilerServices.RuntimeHelpers; using static System.Windows.Forms.VisualStyles.VisualStyleElement.StartPanel; namespace JinYuan.ControlCenters { public partial class ControlCenter { private static readonly SemaphoreSlim _plcSemaphore = new SemaphoreSlim(5, 10); // PLC操作并发限制 private static readonly SemaphoreSlim _dbSemaphore = new SemaphoreSlim(10, 20); // 数据库操作并发限制 private static readonly object _fileLock = new object(); // 文件锁 //private static readonly TimeSpan _plcTimeout = TimeSpan.FromSeconds(1); // PLC操作超时 //private static readonly TimeSpan _mesTimeout = TimeSpan.FromSeconds(2); // MES请求超时 // UI同步上下文:避免跨线程阻塞UI private readonly SynchronizationContext _uiSyncContext = SynchronizationContext.Current; #region 保留2位小数 private float SaveTwoNum(float TNum) { return (float)Math.Round(TNum, 2, MidpointRounding.AwayFromZero); } #endregion #region PLC设备安全访问 /// /// 安全获取PLC设备 /// /// PLC编号(从1开始) /// PLC设备实例,若不存在或未连接则返回null private PlcReadWriteBase GetPlcDevice(int plcNum) { try { if (CommonMethods.plcDevices != null && plcNum > 0) { // 根据PLC编号查找设备,而不是依赖列表索引 var plcDevice = CommonMethods.plcDevices.FirstOrDefault(p => p != null && p.Name == $"PLC_{plcNum}" && p.IsConnected); if (plcDevice != null) { return plcDevice; } } } catch { } return null; } #endregion #region 物流线料盘来料 /// /// 物流线料盘来料 /// private async Task TrayFeedingStation(JYResult bArrays, object plcModel) { LogHelper.Instance.WriteLog($"Step 1: 进入 TrayFeedingStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); PlcGroup pf = plcModel as PlcGroup; if (pf == null) { LogHelper.Instance.WriteError("PLC模型转换失败", "WuLiuLog"); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 2); //反馈结果 1OK 2NG await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); //反馈处理完成 return; } using (var timer = new TimedOperation("物流线料盘来料", (elapsedMs, operationName) => { CommonMethods.AddDataMonitorLog(0, $"物流线料盘来料共耗时:{operationName} 执行耗时:{elapsedMs}ms"); })) { if (!bArrays.IsSuccess || bArrays.Content == null) { await HandleFeedingError(pf, "PLC读地址无料框码数据", null); return; } try { LogHelper.Instance.WriteLog($"Step 2: ClearPLCRegistersBatch - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); // 1. 批量清空PLC寄存器 await ClearPLCRegistersBatch(pf, new[] { 2, 3, 4 }, 0).ConfigureAwait(false); #region 数据解析 const int BARCODE_SIZE = 40; byte[] bytes = bArrays.Content.ByteReverse();//高低位转换 string trayID = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding(bytes, 0, BARCODE_SIZE, Encoding.ASCII)); #endregion LogHelper.Instance.WriteLog($"Step 3: 数据解析: 料盘码【{trayID}】", "WuLiuLog"); if (!IsValidTrayID(trayID)) { await HandleFeedingError(pf, $"料框码无效:{trayID}", trayID); return; } var mesResult = await GetTrayInfoWithTimeout(pf, trayID, 500); LogHelper.Instance.WriteLog($"Step 4: GetTrayInfoWithTimeout: 料盘码【{trayID}】", "WuLiuLog"); if (!mesResult.Success || mesResult.TrayInfo == null) { await HandleFeedingError(pf, mesResult.ErrorMessage, trayID); return; } //获取电池型号 var modelType = mesResult.TrayInfo.productCode; // 获取电池档位信息 var gradeResult = await GetBatteryGradesWithTimeout(mesResult.TrayInfo, 500); LogHelper.Instance.WriteLog($"Step 5: 获取电池档位: 料盘码【{trayID}】", "WuLiuLog"); string finalGrade = gradeResult.Grade; string gradeRemark = gradeResult.IsConsistent ? "" : gradeResult.ErrorMessage; LogHelper.Instance.WriteLog($"Step 6:物流线料盘来料: 获取电池型号【{modelType}】,电池档位:【{gradeResult}】{gradeRemark}", "WuLiuLog"); // 构建并保存实体 var mesTrayEntity = BuildTrayEntity(mesResult.TrayInfo, finalGrade, gradeRemark); await SaveTrayDataBatchAsync(new List { mesTrayEntity }).ConfigureAwait(false); LogHelper.Instance.WriteLog($"Step 7: 保存数据: 料盘码【{trayID}】", "WuLiuLog"); // 反馈PLC await FeedBackToPLC(pf, mesResult.TrayInfo, finalGrade); LogHelper.Instance.WriteLog($"Step 8: 写PLC: 料盘码【{trayID}】", "WuLiuLog"); } catch (Exception ex) { LogHelper.Instance.WriteError($"物流线料盘来料异常: {ex}", "WuLiuLog"); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 2); //反馈结果 1 OK 2 NG await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); //反馈处理完成 } } } /// /// 带超时的MES料盘信息获取 /// private async Task<(bool Success, TrayInfo TrayInfo, string ErrorMessage)> GetTrayInfoWithTimeout(PlcGroup pf, string trayID, int timeoutMs) { var task = QueryMesTrayInfos(pf, trayID, CancellationToken.None); if (await Task.WhenAny(task, Task.Delay(timeoutMs)) != task) { return (false, null, "MES请求超时"); } var result = await task; return (result.success, result.trayinfo, result.mesMessage); } /// /// 等级查询结果 /// private class GradeQueryResult { public string Grade { get; set; } public bool IsConsistent { get; set; } public string ErrorMessage { get; set; } } /// /// 带超时的电池等级查询 /// private async Task GetBatteryGradesWithTimeout(TrayInfo trayInfo, int timeoutMs) { var result = new GradeQueryResult { Grade = "", IsConsistent = true, ErrorMessage = "" }; // 构建电池条码列表 var listCode = BuildMaterialLotList(trayInfo); if (listCode.Count == 0) { result.ErrorMessage = "料盘中无电池条码"; CommonMethods.AddDataMonitorLog(0, result.ErrorMessage); return result; } var gradeTask = QueryGrades(listCode, CancellationToken.None); if (await Task.WhenAny(gradeTask, Task.Delay(timeoutMs)) != gradeTask) { result.ErrorMessage = "电池等级查询超时"; result.IsConsistent = false; CommonMethods.AddDataMonitorLog(0, result.ErrorMessage); return result; } var gradeResult = await gradeTask; if (!gradeResult.success || gradeResult.batteryInfo == null || gradeResult.batteryInfo.Count == 0) { result.ErrorMessage = "电池等级查询失败或无数据"; result.IsConsistent = false; CommonMethods.AddDataMonitorLog(0, result.ErrorMessage); return result; } // 检查等级一致性 var grades = gradeResult.batteryInfo.Where(g => !string.IsNullOrEmpty(g?.grade)).Select(g => g.grade).Distinct().ToList(); if (grades.Count == 0) { result.ErrorMessage = "未获取到有效的电池等级"; result.IsConsistent = false; CommonMethods.AddDataMonitorLog(0, result.ErrorMessage); return result; } if (grades.Count == 1) { result.Grade = grades[0]; result.IsConsistent = true; CommonMethods.AddDataMonitorLog(0, $"电池等级查询成功,所有电池等级一致: {result.Grade}"); } else { result.ErrorMessage = $"电池等级不一致!检测到多个等级: {string.Join(",", grades)}"; result.IsConsistent = false; LogHelper.Instance.WriteError(result.ErrorMessage, "WuLiuLog"); CommonMethods.AddDataMonitorLog(0, result.ErrorMessage); } return result; } /// /// 构建MaterialLot列表 /// private List BuildMaterialLotList(TrayInfo trayInfo) { var listCode = new List(); if (trayInfo?.cellList == null) return listCode; foreach (var cell in trayInfo.cellList) { if (!string.IsNullOrEmpty(cell.cellNo)) { listCode.Add(new MaterialLot { identification = cell.cellNo, locationBat = cell.channel.ToString() }); } } return listCode; } /// /// 构建TrayEntity实体 /// private TrayEntity BuildTrayEntity(TrayInfo trayInfo, string grade, string gradeRemark) { return new TrayEntity { trayID = trayInfo?.trayNo ?? "", model = trayInfo?.productCode ?? CommonMethods.mesConfig.ModelName, grade = grade, moreGrade = "", Count = trayInfo?.cellList?.Count ?? 0, GongWei = CommonMethods.sysConfig.GongWei, Time = DateTime.Now, Result = string.IsNullOrEmpty(gradeRemark) ? "OK" : "NG", Remark = string.IsNullOrEmpty(gradeRemark) ? $"本机架电池型号{CommonMethods.mesConfig.ModelName}, 处理档位{string.Join("|", CommonMethods.GetValidGrades())}" : gradeRemark }; } /// /// 反馈结果到PLC /// private async Task FeedBackToPLC(PlcGroup pf, TrayInfo trayInfo, string grade) { short model = (short)CommonMethods.mesConfig.GetModel(trayInfo?.productCode ?? CommonMethods.mesConfig.ModelName); if (model == 0) model = 1; short gradeCode = 99; if (!string.IsNullOrEmpty(grade)) gradeCode = (short)CommonMethods.GetGradeNo(grade); await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, model); await WritePLCInt16Sync(pf, pf.VarList[3].VarAddress, gradeCode); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 1); await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); CommonMethods.AddDataMonitorLog(0, $"物流线料盘来料[{trayInfo.trayNo}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号{model}(mes获取的原型号:{trayInfo?.productCode}); [{pf.VarList[3].VarAddress}]写挡位{gradeCode}({grade})"); LogHelper.Instance.WriteLog($"物流线料盘来料[{trayInfo.trayNo}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号{model}(mes获取的原型号:{trayInfo?.productCode}); [{pf.VarList[3].VarAddress}]写挡位{gradeCode}({grade})", "WuLiuLog"); } /// /// 处理错误并反馈PLC /// private async Task HandleFeedingError(PlcGroup pf, string errorMessage, string trayID) { var mesTrayEntity = new TrayEntity { trayID = trayID ?? "", model = "", grade = "", moreGrade = "", Count = 0, GongWei = CommonMethods.sysConfig.GongWei, Time = DateTime.Now, Result = "NG", Remark = errorMessage }; await SaveTrayDataBatchAsync(new List { mesTrayEntity }); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 2); //反馈结果 1OK 2NG await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); //反馈处理完成 CommonMethods.AddDataMonitorLog(0, $"料盘码[{trayID}]:" + errorMessage); LogHelper.Instance.WriteLog($"料盘码[{trayID}]:" + errorMessage, "WuLiuLog"); } // MES查询方法 private async Task<(bool success, string mesMessage, MesResType mesResType, TrayInfo trayinfo)> QueryMesTrayInfos(PlcGroup plcGroup, string trayID, CancellationToken token) { token.ThrowIfCancellationRequested(); try { var (success, mesMessage, mesResType, trayinfo) = await CommonMethods.hbgMes.QueryMesTrayInfosAsync( CommonMethods.mesConfig.queryTrayUrl, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), trayID, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName ); return (success, mesMessage, mesResType, trayinfo); } catch (OperationCanceledException) { // 超时, 快速返回,不等待 return (false, "MES查询超时", MesResType.B, null); } catch (Exception ex) { // 其他错误 LogHelper.Instance.WriteEX(ex); return (false, "MES查询失败", MesResType.B, null); } } private async Task<(bool success, List batteryInfo)> QueryGrades(List listCode, CancellationToken token) { token.ThrowIfCancellationRequested(); if (listCode == null || listCode.Count == 0) { return (false, null); } try { var (success, batteryInfo) = await CommonMethods.hbgMes.QueryGradeAsync( CommonMethods.mesConfig.queryGradeUrl, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), listCode.Count.ToString(), CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, listCode ); return (success, batteryInfo); } catch (OperationCanceledException) { // 超时, 快速返回,不等待 LogHelper.Instance.WriteError($"查询电池等级超时", "WuLiuLog"); return (false, null); } catch (Exception ex) { LogHelper.Instance.WriteError($"查询电池等级异常: {ex.Message}", "WuLiuLog"); return (false, null); } } #endregion #region 物流线料盘进站 /// /// 物流线料盘进站 /// private async Task CheckTrayStation(JYResult bArrays, object plcModel) { LogHelper.Instance.WriteLog($"Step 1: 进入 CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); PlcGroup pf = plcModel as PlcGroup; if (pf == null) { LogHelper.Instance.WriteError("PLC模型转换失败", "WuLiuLog"); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 2); //反馈结果 1OK 2NG await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); //反馈处理完成 return; } using (var timer = new TimedOperation("物流线料盘进站", (elapsedMs, operationName) => { CommonMethods.AddDataMonitorLog(0, $"物流线料盘进站共耗时:{operationName} 执行耗时:{elapsedMs}ms"); })) { LogHelper.Instance.WriteLog($"Step 2: 判断bArrays CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); if (!bArrays.IsSuccess || bArrays.Content == null) { await HandleCheckError(pf, "PLC读地址无料框码数据", null); return; } try { LogHelper.Instance.WriteLog($"Step 3: ClearPLCRegistersBatch CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); // 批量清空寄存器 await ClearPLCRegistersBatch(pf, new[] { 2, 3, 4 }, 0).ConfigureAwait(false); #region 数据解析 const int BARCODE_SIZE = 40; LogHelper.Instance.WriteLog($"Step 4: 数据解析 CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); byte[] bytes = bArrays.Content.ByteReverse();//高低位转换 // 读取料盘码 string trayID = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding(bytes, 0, BARCODE_SIZE, Encoding.ASCII)); #endregion LogHelper.Instance.WriteLog($"Step 5: 验证料框码ID CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); // 验证卡板ID if (!IsValidTrayID(trayID)) { await HandleCheckError(pf, $"料框码无效:{trayID}", trayID); return; } // 获取MES料盘信息 var mesResult = await GetTrayInfoWithTimeout(pf, trayID, 500); if (!mesResult.Success || mesResult.TrayInfo == null) { await HandleCheckError(pf, mesResult.ErrorMessage, trayID); return; } LogHelper.Instance.WriteLog($"Step 8: 组合TrayEntity CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); // 获取电池等级信息 var gradeResult = await GetBatteryGradesWithTimeout(mesResult.TrayInfo, 500); string finalGrade = gradeResult.IsConsistent ? gradeResult.Grade : CommonMethods.curFdConfig.Grade1; string add = (gradeResult.Grade == null || string.IsNullOrEmpty(gradeResult.Grade)) ? ",临时使用本机架1垛位的挡位" : ""; string gradeRemark = gradeResult.IsConsistent ? "" : gradeResult.ErrorMessage + add; // 反馈电池条码到PLC //await WriteBatteryBarcodesToPLC(pf, mesResult.TrayInfo); // 构建并保存实体 var mesTrayEntity = BuildTrayEntity(mesResult.TrayInfo, finalGrade, gradeRemark); await SaveTrayDataBatchAsync(new List { mesTrayEntity }).ConfigureAwait(false); // 反馈PLC await FeedBackToPLCCheck(pf, mesResult.TrayInfo, finalGrade); LogHelper.Instance.WriteLog($"物流线料盘进站:料框码:{trayID},往PLC地址[{pf.VarList[4].VarAddress}]写1(OK)", "WuLiuLog"); LogHelper.Instance.WriteLog($"Step 11: 结束 CheckTrayStation - {DateTime.Now:HH:mm:ss.fff}", "WuLiuLog"); } catch (Exception ex) { LogHelper.Instance.WriteError($"物流线料盘进站异常: {ex}", "WuLiuLog"); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 2); //反馈结果 1OK 2NG await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); //反馈处理完成 } } } /// /// 进站反馈结果到PLC /// private async Task FeedBackToPLCCheck(PlcGroup pf, TrayInfo trayInfo, string grade) { short model = (short)CommonMethods.mesConfig.GetModel(trayInfo?.productCode ?? CommonMethods.mesConfig.ModelName); if (model == 0) model = 1; short gradeCode = 99; // 默认NG档位 if (!string.IsNullOrEmpty(grade)) gradeCode = (short)CommonMethods.GetGradeNo(grade); await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, model); await WritePLCInt16Sync(pf, pf.VarList[3].VarAddress, gradeCode); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 1); await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); CommonMethods.AddDataMonitorLog(0, $"物流线料盘进站[{trayInfo.trayNo}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号{model}(mes获取的原型号:{trayInfo?.productCode}); [{pf.VarList[3].VarAddress}]写挡位{gradeCode}({grade})"); LogHelper.Instance.WriteError($"物流线料盘进站[{trayInfo.trayNo}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号{model}(mes获取的原型号:{trayInfo?.productCode}); [{pf.VarList[3].VarAddress}]写挡位{gradeCode}({grade})", "WuLiuLog"); LogHelper.Instance.WriteLog($"物流线料盘进站完成", "WuLiuLog"); } /// /// 处理进站错误 /// private async Task HandleCheckError(PlcGroup pf, string errorMessage, string trayID) { var mesTrayEntity = new TrayEntity { trayID = trayID ?? "", model = "", grade = "", moreGrade = "", Count = 0, GongWei = CommonMethods.sysConfig.GongWei, Time = DateTime.Now, Result = "NG", Remark = errorMessage }; await SaveTrayDataBatchAsync(new List { mesTrayEntity }); short model = (short)CommonMethods.mesConfig.GetModel(CommonMethods.mesConfig.ModelName); if (model == 0) model = 1; short gradeCode = 99; // 默认NG档位 await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, model); await WritePLCInt16Sync(pf, pf.VarList[3].VarAddress, gradeCode);//档位NG 99 await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, 2); await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); CommonMethods.AddDataMonitorLog(0, $"物流线料盘进站[{trayID}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号1(mes获取的原型号:无); [{pf.VarList[3].VarAddress}]写挡位99(NG)"); LogHelper.Instance.WriteError($"物流线料盘进站[{trayID}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号1(mes获取的原型号:无); [{pf.VarList[3].VarAddress}]写挡位99(NG)", "WuLiuLog"); CommonMethods.AddDataMonitorLog(0, errorMessage); } /// /// 保存料盘数据 /// /// /// private async Task SaveTrayDataBatchAsync(List list) { if (list == null || list.Count == 0) return; try { foreach (var m in list) { if (string.IsNullOrEmpty(m.trayID)) { m.Remark += " 料盘ID为空,跳过"; continue; } try { int updateCount = await CommonMethods.db.UpdateSingleAsync(m, null, it => it.trayID == m.trayID); if (updateCount > 0) { m.Remark += " 更新Yes"; } else { // 新增时直接调用底层,减少封装开销 bool addSuccess = await Task.Run(() => CommonMethods.db.AddReturnBoolAsync(m) ); m.Remark += addSuccess ? " 新增Yes" : " 新增No"; } } catch (Exception ex) { LogHelper.Instance.WriteError($"物流线进站保存{m.trayID}处理异常:{ex}", "WuLiuLog"); continue; } } _uiSyncContext.Post(_ => CommonMethods.ShowTrayDataDelegate?.Invoke(list), null); } catch (Exception ex) { LogHelper.Instance.WriteError($"物流线进站保存处理异常:{ex}", "WuLiuLog"); throw; } finally { // _dbSemaphore.Release(); } } private bool IsValidTrayID(string trayID) { return !string.IsNullOrWhiteSpace(trayID) && !trayID.ToUpper().Trim().Equals("ERROR", StringComparison.OrdinalIgnoreCase) && trayID.Length >= 3; // 增加字母数字校验,提前过滤无效数据 } #endregion #region 电池进站 /// /// 电池进站 /// private async Task FeedingStation(JYResult bArrays, object plcModel) { LogHelper.Instance.WriteLog($"Step 1: 进入 FeedingStation - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); PlcGroup pf = plcModel as PlcGroup; if (pf == null) { LogHelper.Instance.WriteLog("PLC模型转换失败", "ZuPanLog"); WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 2); return; } Stopwatch sw = Stopwatch.StartNew(); try { LogHelper.Instance.WriteLog($"Step 2: 判断bArrays FeedingStation - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); if (!bArrays.IsSuccess || bArrays.Content == null) { await HandleFeedingStationError(pf, "PLC读地址无料框码数据,不存库", null, null); return; } LogHelper.Instance.WriteLog($"Step 3.1: ClearPLCRegistersBatch FeedingStation - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); #region 清空上一次寄存器数据 // 批量清空寄存器 await ClearPLCRegistersBatch(pf, new[] { 3, 4, 5 }, 0); LogHelper.Instance.WriteLog($"Step 3.2: Clear电池条码 FeedingStation - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); // 清空电池条码区域 await ClearBatteryBarcodeArea(pf); #endregion #region 数据解析 const int BARCODE_SIZE = 40; byte[] bytes = bArrays.Content.ByteReverse(); string trayID = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding(bytes, 0, BARCODE_SIZE, Encoding.ASCII)); #endregion if (!IsValidTrayID(trayID)) { await HandleFeedingStationError(pf, "料框码无效,不存库", trayID, null); return; } CommonMethods.AddDataMonitorLog(0, $"PLC读地址料框码数据,PLC地址[{pf.VarList[1].VarAddress}]:料框码{trayID}"); // 获取MES料盘信息 var mesResult = await GetTrayInfoWithTimeout(pf, trayID, 500); if (!mesResult.Success || mesResult.TrayInfo == null) { await HandleFeedingStationError(pf, mesResult.ErrorMessage, trayID, mesResult.TrayInfo); return; } // 获取电池等级信息 var gradeResult = await GetBatteryGradesWithTimeout(mesResult.TrayInfo, 500); string finalGrade = gradeResult.IsConsistent ? gradeResult.Grade : CommonMethods.curFdConfig.Grade1; string add = (gradeResult.Grade == null || string.IsNullOrEmpty(gradeResult.Grade) )? ",临时使用本机架1垛位的挡位" : ""; string gradeRemark = gradeResult.IsConsistent ? "" : gradeResult.ErrorMessage + add; // 反馈电池条码到PLC await WriteBatteryBarcodesToPLC(pf, mesResult.TrayInfo); // 构建保存数据 var feedingList = BuildFeedingEntityList(mesResult.TrayInfo, finalGrade, gradeRemark); await SaveFeedingDataBatchAsync(feedingList); // 反馈结果到PLC await FeedBackToPLCLayer(pf, mesResult.TrayInfo, finalGrade); LogHelper.Instance.WriteLog($"Step 12: 结束 FeedingStation - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); } catch (Exception ex) { LogHelper.Instance.WriteLog($"电池上料组盘线程处理异常:{ex}", "ZuPanLog"); await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 2); } finally { sw.Stop(); CommonMethods.AddDataMonitorLog(0, "组盘上料共耗时".Translated() + $":{sw.Elapsed.TotalMilliseconds}ms"); } } /// /// 清空电池条码区域 /// private async Task ClearBatteryBarcodeArea(PlcGroup pf) { const int BARCODE_SIZE = 40; int trayCapacity = 40; string strBatteryIDBar = ""; for (int i = 0; i < trayCapacity; i++) { strBatteryIDBar += GetString_0(BARCODE_SIZE); } byte[] codeBytes = Encoding.Default.GetBytes(strBatteryIDBar).ByteReverse(); var plcDevice = GetPlcDevice(pf.PlcNum); if (plcDevice != null) { plcDevice.WriteValue(pf.VarList[2].VarAddress, codeBytes, PLC.DataType.ArrByte); } } /// /// 写入电池条码到PLC /// private async Task WriteBatteryBarcodesToPLC(PlcGroup pf, TrayInfo trayInfo) { const int BARCODE_SIZE = 40; string batteryIDBar = string.Concat(trayInfo.cellList.Select(cell => cell.cellNo + GetString_0(BARCODE_SIZE - cell.cellNo.Length))); byte[] codeBytes = Encoding.Default.GetBytes(batteryIDBar).ByteReverse(); await WritePLCByteArray(pf, pf.VarList[2].VarAddress, codeBytes); } /// /// 构建上料实体列表 /// private List BuildFeedingEntityList(TrayInfo trayInfo, string grade, string gradeRemark) { return trayInfo.cellList.Select((cell, i) => new AGearEntity { Location = cell.channel.ToString(), Time = DateTime.Now, BarCode = cell.cellNo, TrayID = trayInfo.trayNo, Result = string.IsNullOrEmpty(gradeRemark) ? "OK" : "NG", Remark = gradeRemark, Grade = grade, Model = trayInfo.productCode ?? CommonMethods.mesConfig.ModelName, GongWei = CommonMethods.sysConfig.GongWei, }).ToList(); } /// /// 反馈上料结果到PLC /// private async Task FeedBackToPLCLayer(PlcGroup pf, TrayInfo trayInfo, string grade) { short model = (short)CommonMethods.mesConfig.GetModel(trayInfo?.productCode ?? CommonMethods.mesConfig.ModelName); if (model == 0) model = 1; short gradeCode = 99; if (!string.IsNullOrEmpty(grade)) gradeCode = (short)CommonMethods.GetGradeNo(grade); await WritePLCInt16Sync(pf, pf.VarList[3].VarAddress, model); await WritePLCInt16Sync(pf, pf.VarList[4].VarAddress, gradeCode); await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 1); CommonMethods.AddDataMonitorLog(0, $"电池进站[{trayInfo.trayNo}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号{model}(mes获取的原型号:{trayInfo?.productCode}); [{pf.VarList[3].VarAddress}]写挡位{gradeCode}({grade})"); LogHelper.Instance.WriteError($"电池进站[{trayInfo.trayNo}]:反馈PLC:[{pf.VarList[2].VarAddress}]写型号{model}(mes获取的原型号:{trayInfo?.productCode}); [{pf.VarList[3].VarAddress}]写挡位{gradeCode}({grade})", "ZuPanLog"); } /// /// 处理上料错误 /// private async Task HandleFeedingStationError(PlcGroup pf, string errorMessage, string trayID, TrayInfo trayInfo) { var entity = new AGearEntity { Time = DateTime.Now, BarCode = "", TrayID = trayID ?? "", Result = "NG", Remark = errorMessage, Grade = "", Model = trayInfo?.productCode ?? "", GongWei = CommonMethods.sysConfig.GongWei, }; _uiSyncContext.Post(_ => CommonMethods.ShowFeedingDelegate?.Invoke(new List { entity }), null); await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 2); } #region 保存上料数据 /// /// 保存上料数据 /// /// private async Task SaveFeedingDataBatchAsync(List list) { if (list == null || list.Count == 0) return; try { var barcodes = list.Select(x => x.BarCode).ToList(); var existing = CommonMethods.db.QueryWhereList() .Where(x => barcodes.Contains(x.BarCode)) .Select(x => x.BarCode) .ToList(); var toUpdate = list.Where(x => existing.Contains(x.BarCode)).ToList(); var toInsert = list.Where(x => !existing.Contains(x.BarCode)).ToList(); // 批量更新、批量插入 if (toUpdate.Any()) await CommonMethods.db.UpdateAsync(toUpdate); if (toInsert.Any()) await CommonMethods.db.AddReturnBoolAsync(toInsert); // UI更新(主线程) _uiSyncContext.Post(_ => CommonMethods.ShowFeedingDelegate?.Invoke(list), null); } catch (Exception ex) { LogHelper.Instance.WriteError($"组盘工位保存处理异常:{ex}", "SysErrorLog"); throw; // 重新抛出异常,让上层知道保存失败 } finally { //_dbSemaphore.Release(); } } #endregion #endregion #region 满箱上传 /// /// 组盘 /// /// /// /// private async Task PackStation(JYResult bArrays, object plcModel) { LogHelper.Instance.WriteLog($"Step 1: 进入 PackStation - {DateTime.Now:HH:mm:ss.fff}"); PlcGroup pf = plcModel as PlcGroup; if (pf == null) { LogHelper.Instance.WriteError("PLC模型转换失败", "ZuPanLog"); UpdatePLCStatus(pf, 999, 2, 2, 2, 999); return; } using (var timer = new TimedOperation("电池满层绑定", (elapsedMs, operationName) => { LogHelper.Instance.WriteLog($"电池满层绑定共耗时:{elapsedMs}ms", "ZuPanLog"); CommonMethods.AddDataMonitorLog(0, $"电池满层绑定共耗时:{operationName} 执行耗时:{elapsedMs}ms"); })) { if (bArrays.IsSuccess && bArrays.Content != null) { await ProcessPackStation(bArrays, pf).ConfigureAwait(false); } } } private async Task ProcessPackStation(JYResult bArrays, PlcGroup pf) { try { LogHelper.Instance.WriteLog($"Step 2: PackStation ParseAndValidateData - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); var parseTask = ParseAndValidateData(bArrays, pf); int timeout = 200; if (await Task.WhenAny(parseTask, Task.Delay(timeout)) == parseTask) { LogHelper.Instance.WriteLog($"Step 3: PackStation ParseAndValidateData完成 - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); // 正常返回 var parsedData = await parseTask; if (!parsedData.IsValid) { LogHelper.Instance.WriteLog($"Step 4: PackStation HandleInvalidData - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); await HandleInvalidData(parsedData, pf); return; } LogHelper.Instance.WriteLog($"Step 4: PackStation ProcessBinding - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); // 处理绑定逻辑 await ProcessBinding(parsedData, pf); } else { LogHelper.Instance.WriteLog($"Step 3: PackStation ParseAndValidateData超时 - {DateTime.Now:HH:mm:ss.fff}", "ZuPanLog"); // 超时,直接返回, LogHelper.Instance.WriteError($"PLC解析超时({timeout}ms)", "ZuPanLog"); UpdatePLCStatus(pf, 999, 2, 2, 2, 999); return; } } catch (Exception ex) { LogHelper.Instance.WriteError($"电池满层绑定线程处理异常:{ex}", "ZuPanLog"); // 异常时反馈NG状态 UpdatePLCStatus(pf, 999, 2, 2, 2, 999); } } #endregion #region 保存叠盘数据 /// /// 保存叠盘工位数据 /// /// private async Task SaveBlankingDataBatchAsync(List list) { if (list == null || list.Count == 0) return; await _dbSemaphore.WaitAsync().ConfigureAwait(false); //数据的保存 try { if (list != null && list.Count > 0) { //数据显示 foreach (BGearEntity m in list) { string strRes = ""; if (m.VassoioID != "NG") { bool b = await CommonMethods.db.AddReturnBoolAsync(m); } else { strRes = ",不存储"; } m.Remark = m.Remark + strRes; } //数据显示 //CommonMethods.ShowBlankingDelegate.Invoke(list); // UI更新 _uiSyncContext.Post(_ => CommonMethods.ShowBlankingDelegate?.Invoke(list), null); } else { LogHelper.Instance.WriteError($"组盘工位数据List为空", "SysErrorLog"); } //// 过滤有效数据 //var validList = list.Where(m => m.VassoioID != "NG").ToList(); //if (validList.Any()) //{ // await CommonMethods.db.Insertable(validList).ExecuteCommandAsync().ConfigureAwait(false); //} } catch (Exception ex) { LogHelper.Instance.WriteError($"组盘数据保存异常:{ex}", "SysErrorLog"); } finally { //_dbSemaphore.Release(); } } #endregion #region 初始化方法 // 初始化PLC状态 private void InitializePLCStatus(PlcGroup pf) { ////电池条码读取结果 //CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[4].VarAddress, 0); ////卡板码读取结果反馈 //CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[5].VarAddress, 0); ////绑定结果反馈 //CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[6].VarAddress, 0); ////绑定处理完成反馈 //CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16(pf.VarList[7].VarAddress, 0); _ = ClearPLCRegistersBatch(pf, new[] { 4, 5, 6, 7 }, 0); } #endregion #region 数据解析和验证 private async Task ParseAndValidateData(JYResult bArrays, PlcGroup pf) { var parsedData = new ParsedData(); const int BARCODE_SIZE = 40; string containerCode = ""; var plcDevice = GetPlcDevice(pf.PlcNum); // 读取跺号 int stationNo = 0; if (plcDevice != null) { stationNo = plcDevice.ReadInt16(pf.VarList[4].VarAddress); } if (stationNo != 0 ) { JYResult kbArrays = plcDevice.ReadValue>( $"{pf.VarList[3].VarAddress}", PLC.DataType.ArrByte, Convert.ToUInt16(pf.VarList[3].OffsetOrLength)); // 从3个可能的地址读取卡板码 //for (int i = 0; i < 3; i++) { string code = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding( kbArrays.Content.ByteReverse(), (stationNo - 1) * BARCODE_SIZE, BARCODE_SIZE, Encoding.ASCII)); if (!string.IsNullOrEmpty(code) && code.ToUpper() != "ERROR" && code.Length >= 8) { containerCode = code; //stationNo = i + 1; //break; } } } // 读取层数 short layerNum = 0; if (plcDevice != null) { layerNum = plcDevice.ReadInt16(pf.VarList[2].VarAddress); } parsedData.ContainerCode = (string)containerCode; parsedData.StationNo = stationNo; parsedData.LayerNum = layerNum; // 验证基础数据 var (ret, errType, errMessage) = ValidateBasicData(containerCode, stationNo, layerNum); if (!ret) { parsedData.ErrorType = errType; parsedData.ErrorMessage = errMessage; parsedData.IsValid = ret; return parsedData; } // 解析电池条码 var batteryInfos = ParseBatteryBarcodes(bArrays, layerNum); var validBarcodeCount = batteryInfos.Count(b => !string.IsNullOrEmpty(b.identification) && b.identification.ToUpper() != "ERROR"); parsedData.BatteryInfos = batteryInfos; parsedData.ValidBarcodeCount = validBarcodeCount; // 验证电池数据 var (batteryRet, batErrType, batErrMessage) = ValidateBatteryData(validBarcodeCount); if (!batteryRet) { parsedData.ErrorType = batErrType; parsedData.ErrorMessage = batErrMessage; parsedData.IsValid = batteryRet; return parsedData; } string grade = ""; // 读取电池等级(从第一个有效条码获取) if (validBarcodeCount > 0) { grade = await GetBatteryGrade(batteryInfos.First(b => !string.IsNullOrEmpty(b.identification) && b.identification.ToUpper() != "ERROR").identification).ConfigureAwait(false); parsedData.Grade = grade; } if(parsedData.Grade == "") { parsedData.Grade = MesConfig.GetLocGrade(stationNo, CommonMethods.curFdConfig);//CommonMethods.mesConfig.GetGradeNo("A").ToString(); } parsedData.IsValid = true; return parsedData; } private async Task<(string, int)> ParseContainerCode(PlcGroup pf) { //await _plcSemaphore.WaitAsync().ConfigureAwait(false); try { // 将PLC读取和解析放到后台线程执行 //var cts = new CancellationTokenSource(_plcTimeout); return await Task.Run(() => { const int BARCODE_SIZE = 40; const int BARCODE_ACTUAL_LENGTH = 39; string containerCode = ""; int stationNo = 0; var plcDevice = GetPlcDevice(pf.PlcNum); if (plcDevice != null) { stationNo = plcDevice.ReadInt16(pf.VarList[4].VarAddress); JYResult kbArrays = plcDevice.ReadValue>( $"{pf.VarList[3].VarAddress}", PLC.DataType.ArrByte, Convert.ToUInt16(pf.VarList[3].OffsetOrLength)); // 从3个可能的地址读取卡板码 if(stationNo != 0) { string code = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding( kbArrays.Content.ByteReverse(), (stationNo - 1) * BARCODE_SIZE, BARCODE_ACTUAL_LENGTH, Encoding.ASCII)); if (!string.IsNullOrEmpty(code) && code.ToUpper() != "ERROR" && code.Length >= 8) { containerCode = code; //stationNo = i + 1; //break; } } } return (containerCode, stationNo); }); } catch (TaskCanceledException) { LogHelper.Instance.WriteError($"PLC读取容器码超时", "SysErrorLog"); return ("", 0); } finally { _plcSemaphore.Release(); } } private List ParseBatteryBarcodes(JYResult bArrays, short layerNum) { const int BARCODE_SIZE = 40; int maxBatteryNum = CommonMethods.mesConfig.rowsPerLayer * CommonMethods.mesConfig.colsPerLayer; var batteryInfos = new List(maxBatteryNum); for (int i = 0; i < maxBatteryNum; i++) { string location = ParseLocation(layerNum, i); string barcode = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding( bArrays.Content.ByteReverse(), i * BARCODE_SIZE, BARCODE_SIZE, Encoding.ASCII)); LogHelper.Instance.WriteLog($"PLC读取绑盘条码{i}-{barcode}-{location}", "绑盘条码"); if (string.IsNullOrEmpty(barcode) || barcode.Length < 8) { LogHelper.Instance.WriteLog($"PLC读取绑盘条码{i}-{barcode}-{location}异常!!!", "绑盘条码"); continue; } batteryInfos.Add(new BatteryInfo { identification = barcode, locationRow = location }); if (!string.IsNullOrEmpty(barcode) && barcode.ToUpper() != "ERROR") { LogHelper.Instance.WriteLog($"电池条码:{barcode},序号:{i + 1}, 位置:{location},层数:{layerNum}"); } } return batteryInfos; } private (bool, ErrorType, string) ValidateBasicData(string containerCode, int stationNo, int layerNum) { string errorMessage = ""; ErrorType errorType = ErrorType.None; // 验证卡板码 if (string.IsNullOrEmpty(containerCode) || containerCode.ToUpper().Trim() == "ERROR" || containerCode.ToUpper().Trim() == "NG" || containerCode.Length < 8) { errorType = ErrorType.ContainerCodeEmpty; errorMessage = "箱唛码NG"; return (false, errorType, errorMessage); } // 验证 机架号 if (stationNo < 1 || stationNo > 3) { errorType = ErrorType.InvalidStation; errorMessage = $"机架号NG{stationNo}"; return (false, errorType, errorMessage); } // 验证层数范围 if (layerNum < 1 || layerNum > CommonMethods.mesConfig.packLayers) { errorType = ErrorType.InvalidLayer; errorMessage = $"层数NG{layerNum}"; return (false, errorType, errorMessage); } return (true, errorType, errorMessage); } private (bool, ErrorType, string) ValidateBatteryData(int validBarcodeCount) { string errMessage = ""; ErrorType errorType = ErrorType.None; int maxBatteryNum = CommonMethods.mesConfig.rowsPerLayer * CommonMethods.mesConfig.colsPerLayer; // 验证是否有有效条码 if (validBarcodeCount == 0) { errorType = ErrorType.NoValidBarcodes; errMessage = "电池信息列表为空"; return (false, errorType, errMessage); } // 验证是否满盘 if (validBarcodeCount != maxBatteryNum) { errorType = ErrorType.NotFullPack; errMessage = $"电池未满盘,有效条码:{validBarcodeCount},应满盘:{maxBatteryNum}"; return (false, errorType, errMessage); } return (true, errorType, errMessage); } private async Task GetBatteryGrade(string barcode) { try { await _dbSemaphore.WaitAsync(); var entities = await Task.Run(() => CommonMethods.db.QueryWhereList(it => it.BarCode == barcode)).ConfigureAwait(false); if (entities == null || entities.Count == 0) { LogHelper.Instance.WriteError($"本地数据库无该电池条码{barcode}", "SysErrorLog"); return string.Empty; } return entities[0].Grade; } catch (Exception ex) { LogHelper.Instance.WriteError($"本地数据库无该电池条码{barcode}:{ex}", "SysErrorLog"); return string.Empty; } finally { _dbSemaphore.Release(); } } #endregion #region 数据处理方法 private async Task HandleInvalidData(ParsedData data, PlcGroup pf) { // 创建基础实体 var bGearEntity = CreateBaseBGearEntity(data); short batteryReadResult = 0; short containerReadResult = 0; short bindingResult = 0; short processCompleteLayer = 999; switch (data.ErrorType) { case ErrorType.ContainerCodeEmpty: bGearEntity.Result = "NG"; bGearEntity.Remark = data.ErrorMessage; batteryReadResult = 2; containerReadResult = 2; bindingResult = 2; processCompleteLayer = data.LayerNum; break; case ErrorType.InvalidLayer: bGearEntity.Result = "NG"; bGearEntity.Remark = data.ErrorMessage; batteryReadResult = 2; containerReadResult = 2; bindingResult = 2; processCompleteLayer = data.LayerNum; break; case ErrorType.NoValidBarcodes: bGearEntity.Result = "NG"; bGearEntity.Remark = data.ErrorMessage; batteryReadResult = 2; containerReadResult = 1; bindingResult = 2; processCompleteLayer = data.LayerNum; break; case ErrorType.NotFullPack: bGearEntity.Result = "NG"; bGearEntity.Remark = data.ErrorMessage ?? "电池未满盘,不绑定"; batteryReadResult = 1; containerReadResult = 1; bindingResult = 2; processCompleteLayer = data.LayerNum; break; } // 显示结果 var bGearlist = new List { bGearEntity }; CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); UpdatePLCStatus(pf, (short)(data.LayerNum == 0? 999: data.LayerNum), batteryReadResult, containerReadResult, bindingResult, processCompleteLayer); // 记录日志 CommonMethods.AddDataMonitorLog(1, data.ErrorMessage); } private async Task ProcessBinding(ParsedData data, PlcGroup pf) { // 创建基础实体 var bGearEntity = CreateBaseBGearEntity(data); bGearEntity.Total = data.ValidBarcodeCount; bGearEntity.Qty = data.Grade; LogHelper.Instance.WriteLog($"Step 5: PackStation ProcessMESBinding - {DateTime.Now:HH:mm:ss.fff}"); await ProcessMESBinding(data, pf, bGearEntity); } private async Task ProcessMESBinding(ParsedData data, PlcGroup pf, BGearEntity bGearEntity) { try { // 合并电池信息 var totalBatteryInfos = new List(); totalBatteryInfos.AddRange(data.BatteryInfos); if (data.LayerNum == CommonMethods.mesConfig.packLayers) { LogHelper.Instance.WriteLog($"Step 6: PackStation QueryWhereList - {DateTime.Now:HH:mm:ss.fff}"); // 查询数据库中已存在的绑定记录 var existingBatteries = CommonMethods.db.QueryWhereList( it => it.containerCode == data.ContainerCode) .Select(a => new BatteryInfo { identification = a.identification, locationRow = a.location //(a.location).Replace("-", "") }) .GroupBy(b => new { b.identification, b.locationRow }) .Select(g => g.First()) // 保留每组的第一条记录 .ToList(); totalBatteryInfos.AddRange(existingBatteries); totalBatteryInfos = totalBatteryInfos .GroupBy(b => new { b.identification, b.locationRow }) .Select(g => g.First()) .ToList(); bGearEntity.Total = totalBatteryInfos.Count; LogHelper.Instance.WriteLog($"Step 7: PackStation CallMESPackLoadAsync - {DateTime.Now:HH:mm:ss.fff}"); // 调用MES接口 var mesResult = await CallMESPackLoadAsync(data, totalBatteryInfos); if (!mesResult.Success) { bGearEntity.OutSuccTotal = 0; LogHelper.Instance.WriteLog($"Step 8: PackStation HandleMESRequestFailed - {DateTime.Now:HH:mm:ss.fff}"); await HandleMESRequestFailed(data, pf, bGearEntity, mesResult.ErrorMessage); return; } LogHelper.Instance.WriteLog($"Step 8: PackStation ProcessMESResult - {DateTime.Now:HH:mm:ss.fff}"); // 处理MES返回结果 await ProcessMESResult(data, pf, bGearEntity, mesResult).ConfigureAwait(false); //string palletId = ""; //var upParamResult = await CallMESUpResultParamAsync(data.ContainerCode, palletId, totalBatteryInfos); } else { LogHelper.Instance.WriteLog($"Step 6: PackStation ProcessTempResult - {DateTime.Now:HH:mm:ss.fff}"); // 处理MES返回结果 await ProcessTempResult(data, pf, bGearEntity).ConfigureAwait(false); } } catch (Exception ex) { LogHelper.Instance.WriteError($"MES绑定处理异常:{ex}", "SysErrorLog"); UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); } } private async Task CallMESPackLoadAsync(ParsedData data, List totalBatteryInfos) { if (!CommonMethods.sysConfig.FinishSwitching) { var result = await CommonMethods.hbgMes.PackLoadAsync( CommonMethods.mesConfig.packLoadUrl, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, data.ContainerCode, totalBatteryInfos.Count, data.Grade, CommonMethods.mesConfig.MaterialCode, totalBatteryInfos ); return new MESPackLoadResult { Success = result.Item1, ErrorMessage = result.Item2, ResType = (short)result.Item3, MaterialPackInfos = result.Item4 }; } else { var result = await CommonMethods.hbgMes.PackLoadFinishAsync( CommonMethods.mesConfig.finishedPackUrl, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, data.ContainerCode, totalBatteryInfos.Count, data.Grade, CommonMethods.mesConfig.MaterialCode, totalBatteryInfos ); return new MESPackLoadResult { Success = result.Item1, ErrorMessage = result.Item2, ResType = (short)result.Item3, MaterialPackInfos = result.Item4 }; } } private async Task ProcessMESResult(ParsedData data, PlcGroup pf, BGearEntity bGearEntity, MESPackLoadResult mesResult) { var batteryEntities = new List(); bool allSuccess = true; LogHelper.Instance.WriteLog($"Step 9: PackStation 整合 - {DateTime.Now:HH:mm:ss.fff}"); // 处理每个电池的绑定结果 var matpackDict = mesResult.MaterialPackInfos.ToDictionary(m => m.identification, StringComparer.OrdinalIgnoreCase); foreach (var batteryInfo in data.BatteryInfos) { if (matpackDict.TryGetValue(batteryInfo.identification, out var matpackInfo)) { batteryEntities.Add(new BatteryEntity { identification = matpackInfo.identification, location = $"{matpackInfo.locationRow[0]}-{matpackInfo.locationRow[1]}-{matpackInfo.locationRow.Substring(2)}", packResult = mesResult.Success, packFalseReason = mesResult.ErrorMessage, containerCode = data.ContainerCode, grade = data.Grade, outStationType = 1 }); if (!matpackInfo.packResult) { allSuccess = false; } } } LogHelper.Instance.WriteLog($"Step 10: PackStation SaveBatteryEntitiesBatchAsync 开始 - {DateTime.Now:HH:mm:ss.fff}"); // 保存到数据库 await SaveBatteryEntitiesBatchAsync(batteryEntities); LogHelper.Instance.WriteLog($"Step 11: PackStation SaveBatteryEntitiesBatchAsync 结束 - {DateTime.Now:HH:mm:ss.fff}"); // 更新UI //CommonMethods.ShowBatteryNGRsnDelegate?.Invoke(batteryEntities); //CommonMethods.ShowBatteryStatusDelegate?.Invoke(data.StationNo, batteryEntities); //CommonMethods.ShowBatteryQRCoderDelegate?.Invoke(batteryEntities); // 设置结果 if (allSuccess) { bGearEntity.Result = "OK"; bGearEntity.Remark = "电芯绑定成功"; UpdatePLCStatus(pf, data.LayerNum, 1, 1, 1, data.LayerNum); } else { bGearEntity.Result = "绑定_NG"; bGearEntity.Remark = "部分电芯绑定失败"; UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); } bGearEntity.OutSuccTotal = batteryEntities.Where(m => m.packResult).Count(); LogHelper.Instance.WriteLog($"Step 12: PackStation ShowBlankingDelegate 开始 - {DateTime.Now:HH:mm:ss.fff}"); // 显示结果 var bGearlist = new List { bGearEntity }; CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); LogHelper.Instance.WriteLog($"Step 13: PackStation ShowBlankingDelegate 结束 - {DateTime.Now:HH:mm:ss.fff}"); // 保存到本地数据库 if (bGearEntity.Result == "OK") { LogHelper.Instance.WriteLog($"Step 14: PackStation AddReturnBoolAsync 开始 - {DateTime.Now:HH:mm:ss.fff}"); bool saveSuccess = await CommonMethods.db.AddReturnBoolAsync(bGearlist); CommonMethods.AddDataMonitorLog(1, $"电池满层绑定本地存储{(saveSuccess ? "成功" : "失败")}"); LogHelper.Instance.WriteLog($"Step 15: PackStation AddReturnBoolAsync 结束 - {DateTime.Now:HH:mm:ss.fff}"); } else { LogHelper.Instance.WriteLog($"Step 14: PackStation ng 结果不存库 - {DateTime.Now:HH:mm:ss.fff}"); } } private async Task CallMESUpResultParamAsync(string containerCode, string palletId, List totalBatteryInfos) { var result = await CommonMethods.hbgMes.ProducResultParamAsync( CommonMethods.mesConfig.upResultParamUrl, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, containerCode, palletId, totalBatteryInfos, MesUploadType.DZ ); return result; } private async Task ProcessTempResult(ParsedData data, PlcGroup pf, BGearEntity bGearEntity) { var batteryEntities = new List(); bool allSuccess = true; foreach (var batteryInfo in data.BatteryInfos) { batteryEntities.Add(new BatteryEntity { identification = batteryInfo.identification, location = batteryInfo.locationRow, //$"{batteryInfo.locationRow[0]}-{batteryInfo.locationRow[1]}-{batteryInfo.locationRow.Substring(2)}", packResult = true, packFalseReason = "", containerCode = data.ContainerCode, grade = data.Grade, outStationType = 1 }); } LogHelper.Instance.WriteLog($"Step 7: PackStation SaveBatteryEntitiesBatchAsync 开始 - {DateTime.Now:HH:mm:ss.fff}"); // 保存到数据库 await SaveBatteryEntitiesBatchAsync(batteryEntities); // 更新UI //CommonMethods.ShowBatteryNGRsnDelegate?.Invoke(batteryEntities); //CommonMethods.ShowBatteryStatusDelegate?.Invoke(data.StationNo, batteryEntities); //CommonMethods.ShowBatteryQRCoderDelegate?.Invoke(batteryEntities); LogHelper.Instance.WriteLog($"Step 8: PackStation SaveBatteryEntitiesBatchAsync 完成 - {DateTime.Now:HH:mm:ss.fff}"); // 设置结果 //if (allSuccess) { bGearEntity.Result = "OK"; bGearEntity.Remark = "待整垛电芯一起绑定"; UpdatePLCStatus(pf, data.LayerNum, 1, 1, 1, data.LayerNum); } //else //{ // bGearEntity.Result = "绑定_NG"; // bGearEntity.Remark = "待整垛电芯绑定"; // UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); //} bGearEntity.OutSuccTotal = batteryEntities.Where(m => !m.packResult).Count(); LogHelper.Instance.WriteLog($"Step 9: PackStation ShowBlankingDelegate - {DateTime.Now:HH:mm:ss.fff}"); // 显示结果 var bGearlist = new List { bGearEntity }; CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); // 保存到本地数据库 if (bGearEntity.Result == "OK") { LogHelper.Instance.WriteLog($"Step 10: PackStation AddReturnBoolAsync 开始 - {DateTime.Now:HH:mm:ss.fff}"); bool saveSuccess = await CommonMethods.db.AddReturnBoolAsync(bGearlist).ConfigureAwait(false); CommonMethods.AddDataMonitorLog(1, $"电池满层绑定本地存储{(saveSuccess ? "成功" : "失败")}"); LogHelper.Instance.WriteLog($"Step 11: PackStation AddReturnBoolAsync 结束 - {DateTime.Now:HH:mm:ss.fff}"); } else { LogHelper.Instance.WriteLog($"Step 10: PackStation ng结果不保存 - {DateTime.Now:HH:mm:ss.fff}"); } } private async Task ProcessDebugBinding(ParsedData data, PlcGroup pf, BGearEntity bGearEntity) { // 创建模拟的绑定结果 var batteryEntities = new List(); for (int i = 0; i < data.BatteryInfos.Count; i++) { var batteryInfo = data.BatteryInfos[i]; bool packResult = true; //(i % 3) != 1; // 模拟部分失败 string packFalseReason = packResult ? null : "模拟失败原因"; batteryEntities.Add(new BatteryEntity { identification = string.IsNullOrEmpty(batteryInfo.identification) ? $"DC_{i}" : batteryInfo.identification, location = batteryInfo.locationRow, packResult = packResult, packFalseReason = packFalseReason, containerCode = data.ContainerCode, grade = data.Grade, outStationType = 1 }); } // 保存到数据库 await SaveBatteryEntitiesBatchAsync(batteryEntities).ConfigureAwait(false); // 更新UI //CommonMethods.ShowBatteryNGRsnDelegate?.Invoke(batteryEntities); //CommonMethods.ShowBatteryStatusDelegate?.Invoke(data.StationNo, batteryEntities); //CommonMethods.ShowBatteryQRCoderDelegate?.Invoke(batteryEntities); // 设置结果 bGearEntity.Result = "OK"; bGearEntity.Remark = "调试模式绑定成功"; bGearEntity.OutSuccTotal = batteryEntities.Count; UpdatePLCStatus(pf, data.LayerNum, 0, 0, 1, data.LayerNum); // 显示结果 var bGearlist = new List { bGearEntity }; CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); } private async Task ProcessDebugMode(ParsedData data, PlcGroup pf, BGearEntity bGearEntity) { // 调试模式模拟数据 data.StationNo = data.StationNo == 0 ? 1 : data.StationNo; data.LayerNum = data.LayerNum == (short)0 ? (short)1 : data.LayerNum; data.Grade = data.Grade ?? MesConfig.GetLocGrade(data.StationNo, CommonMethods.curFdConfig); // 生成模拟电池信息 int maxBatteryNum = CommonMethods.mesConfig.rowsPerLayer * CommonMethods.mesConfig.colsPerLayer; var batteryEntities = new List(maxBatteryNum); for (int i = 0; i < maxBatteryNum; i++) { bool packResult = (i % 5) != 1; // 模拟部分失败 string packFalseReason = packResult ? null : $"错误NG{(i % 5)}"; batteryEntities.Add(new BatteryEntity { identification = $"DC_{i}", location = ParseLocation(data.LayerNum, i), packResult = packResult, packFalseReason = packFalseReason, containerCode = "DEBUG_XM_CODE123", grade = data.Grade, outStationType = 1 }); } // 保存到数据库 await SaveBatteryEntitiesBatchAsync(batteryEntities).ConfigureAwait(false); // 更新UI //CommonMethods.ShowBatteryNGRsnDelegate?.Invoke(batteryEntities); //CommonMethods.ShowBatteryStatusDelegate?.Invoke(data.StationNo, batteryEntities); //CommonMethods.ShowBatteryQRCoderDelegate?.Invoke(batteryEntities); // 设置结果 bGearEntity.Result = "NG"; bGearEntity.Remark = "电芯绑定失败(调试模式)"; bGearEntity.OutSuccTotal = batteryEntities.Where(x => x.packResult == true).Count(); bGearEntity.Qty = data.Grade; bGearEntity.Total = batteryEntities.Count; // 显示结果 var bGearlist = new List { bGearEntity }; CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); UpdatePLCStatus(pf, data.LayerNum, 0, 0, 2, data.LayerNum); } #endregion #region 辅助方法 private BGearEntity CreateBaseBGearEntity(ParsedData data) { return new BGearEntity { TestTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), VassoioID = data.ContainerCode ?? string.Empty, GongWei = CommonMethods.sysConfig.GongWei, StackingLayer = data.LayerNum }; } private async Task SaveBatteryEntitiesBatchAsync(List batteryEntities) { if (batteryEntities == null || !batteryEntities.Any()) return; try { var containerCodes = batteryEntities.Select(m => m.containerCode).Distinct().ToList(); bool b = await CommonMethods.db.AddReturnBoolAsync(batteryEntities); CommonMethods.AddDataMonitorLog(0, $"电池绑定数据批量保存成功"); } catch (Exception ex) { LogHelper.Instance.WriteError($"电池绑定保存异常:{ex}", "SysErrorLog"); } finally { } } private void UpdatePLCStatus(PlcGroup pf, short layerNum, short batteryReadResult, short containerReadResult, short bindingResult, short processCompleteLayer) { var dict = new Dictionary(); if (batteryReadResult != 0) dict[pf.VarList[5].VarAddress] = batteryReadResult; // 电池条码读取结果反馈 if (containerReadResult != 0) dict[pf.VarList[6].VarAddress] = containerReadResult; // 卡板码读取结果反馈 if (bindingResult != 0) dict[pf.VarList[7].VarAddress] = bindingResult; // 绑定结果反馈 if (processCompleteLayer != 0) dict[pf.VarList[8].VarAddress] = processCompleteLayer; // 绑定处理完成反馈 WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, batteryReadResult); WritePLCInt16Sync(pf, pf.VarList[6].VarAddress, containerReadResult); WritePLCInt16Sync(pf, pf.VarList[7].VarAddress, bindingResult); WritePLCInt16Sync(pf, pf.VarList[8].VarAddress, processCompleteLayer); } private async Task HandleMESDisconnected(ParsedData data, PlcGroup pf, BGearEntity bGearEntity) { await Task.Run(() => { bGearEntity.Result = "MES_NG"; bGearEntity.Remark = "MES连接中断"; //var bGearlist = new List { bGearEntity }; //CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); //CommonMethods.AddDataMonitorLog(1, "MES连接中断,停止上传"); //CommonMethods.plcDevices[pf.PlcNum - 1].WriteInt16("D80", 1); //UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); var bGearlist = new List { bGearEntity }; _uiSyncContext.Post(_ => CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist), null); CommonMethods.AddDataMonitorLog(1, "MES连接中断,停止上传"); _ = WritePLCInt16Sync(pf, "D80", 1); UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); }); } private async Task HandleMESRequestFailed(ParsedData data, PlcGroup pf, BGearEntity bGearEntity, string errorMessage) { await Task.Run(() => { bGearEntity.Result = "MES_NG"; bGearEntity.Remark = $"MES请求失败:{errorMessage}"; var bGearlist = new List { bGearEntity }; _uiSyncContext.Post(_ => CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist), null); CommonMethods.AddDataMonitorLog(1, $"MES请求失败:{errorMessage}"); _ = WritePLCInt16Sync(pf, "D80", 1); UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); }); } private async Task HandleMESDataMismatch(ParsedData data, PlcGroup pf, BGearEntity bGearEntity, MESPackLoadResult mesResult) { await Task.Run(() => { CommonMethods.AddDataMonitorLog(1, $"返回数据数量不匹配({mesResult.MaterialPackInfos?.Count ?? 0} != {data.BatteryInfos.Count})"); bGearEntity.Result = "绑定_NG"; bGearEntity.Remark = "部分电芯没有返回绑定结果"; bGearEntity.OutSuccTotal = mesResult.MaterialPackInfos?.Where(x => x.packResult == true).Count() ?? 0; //var bGearlist = new List { bGearEntity }; //CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist); //UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); var bGearlist = new List { bGearEntity }; _uiSyncContext.Post(_ => CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist), null); UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum); }); } #endregion #region 数据模型 private class ParsedData { public bool IsValid { get; set; } public string ContainerCode { get; set; } public short LayerNum { get; set; } public List BatteryInfos { get; set; } public int ValidBarcodeCount { get; set; } public string Grade { get; set; } public string ErrorMessage { get; set; } public ErrorType ErrorType { get; set; } public int StationNo { get; set; } } private class MESPackLoadResult { public bool Success { get; set; } public string ErrorMessage { get; set; } public short ResType { get; set; } public List MaterialPackInfos { get; set; } } private enum ErrorType { None, ContainerCodeEmpty, InvalidStation, InvalidLayer, NoValidBarcodes, NotFullPack } #endregion #region 强制出站 private Task ForceOutStation(JYResult bArrays, object plcModel) { PlcGroup pf = plcModel as PlcGroup; if (pf == null) { LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog"); return Task.CompletedTask; } using (var timer = new TimedOperation("强制出站", (elapsedMs, operationName) => { CommonMethods.AddDataMonitorLog(0, $"强制出站共耗时:{operationName} 执行耗时:{elapsedMs}ms"); })) { // 空方法直接返回已完成任务,避免异步开销 return Task.CompletedTask; } } /// /// 解析位置编码 /// /// 位置编码(0-99999) /// 包含层号和位置信息的元组 public string ParseLocation(int layerNum, int number) { if (CommonMethods.mesConfig.colsPerLayer <= 0) throw new InvalidOperationException("列数配置无效"); Dictionary dictRow = new Dictionary() { { 1, "A" }, { 2, "B" }, { 3, "C" }, { 4, "D" }, { 5, "E" }, }; if (CommonMethods.mesConfig.ModelName == "LF350S") { int row = number / CommonMethods.mesConfig.colsPerLayer + 1; string rowStr = dictRow[row]; int col = (number % CommonMethods.mesConfig.colsPerLayer) + 1; return $"{rowStr}{col}"; } else { int row = number / CommonMethods.mesConfig.colsPerLayer + 1; int col = (number % CommonMethods.mesConfig.colsPerLayer) + 1; return $"{layerNum}{row}{col}"; } } #endregion #region 料框码解绑 /// /// 料框码解绑 /// /// /// /// private async Task UnBindTrayStation(JYResult bArrays, object plcModel) { PlcGroup pf = plcModel as PlcGroup; if (pf == null) { LogHelper.Instance.WriteError("PLC模型转换失败", "SysErrorLog"); return; } CommonMethods.AddDataMonitorLog(0, $"料框码解绑开始"); int _mesTimeout = 1000; using (var cts = new CancellationTokenSource(_mesTimeout)) { if (bArrays.IsSuccess && bArrays.Content != null) { byte[] bytes = bArrays.Content.ByteReverse(); //料框码 string trayCode = FormatBarcode(StringLib.GetStringFromByteArrayByEncoding(bytes, 0, 40, System.Text.Encoding.ASCII)); CommonMethods.AddDataMonitorLog(0, $"料框码解绑料况码{trayCode}"); if (string.IsNullOrEmpty(trayCode) || trayCode.ToUpper().Contains("ERROR") || trayCode.ToUpper().Contains("NG")) { await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, 2); return; } try { var (success, mesMessage) = await UnbindTrayInfo(trayCode); { await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, success ? (short)1 : (short)2); LogHelper.Instance.WriteError($"料框码解绑完成:{trayCode},解绑结果:{success},{mesMessage}", "ZuPanLog"); CommonMethods.AddDataMonitorLog(0, $"料框码解绑完成,料框码:{trayCode},解绑结果:{success},{mesMessage}"); } } catch (OperationCanceledException) { await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, 2); LogHelper.Instance.WriteError($"料框码解绑被取消(超时):{trayCode}", "ZuPanLog"); CommonMethods.AddDataMonitorLog(0, $"料框码解绑被取消(超时):{trayCode}"); } catch (Exception ex) { await WritePLCInt16Sync(pf, pf.VarList[2].VarAddress, 2); LogHelper.Instance.WriteError($"料框码解绑异常:{ex.Message},料框码:{trayCode}", "ZuPanLog"); CommonMethods.AddDataMonitorLog(0, $"料框码解绑异常:{ex.Message},料框码:{trayCode}"); } } } } // MES查询方法 private async Task<(bool success, string mesMessage)> UnbindTrayInfo(string trayID) { //if (!CommonMethods.mesConfig.isConnected) //{ // //CommonMethods.AddDataMonitorLog(1, "MES连接中断,停止上传"); // return (false, "MES连接中断", MesResType.B, null); //} try { var (success, mesMessage) = await CommonMethods.hbgMes.UnbindTrayInfosAsync( CommonMethods.mesConfig.unbindTrayUrl, CommonMethods.mesConfig.equipNum, trayID ).ConfigureAwait(false); return (success, mesMessage); } catch (Exception ex) { return (false, $"MES解绑失败{ex.Message}"); } } #endregion #region 创建空的数组List private List GetList(int count, T defaultValue) { if (count <= 0) { return new List(); } // Enumerable.Repeat比循环Add更高效,尤其是大数据量 return Enumerable.Repeat(defaultValue, count).ToList(); } public List GetMesType(int Num) { List mesResTypes = new List(); for (int i = 0; i < Num; i++) { mesResTypes.Add(MesResType.UnKnow); } return mesResTypes; } /// /// PLC内批量写\0 /// /// /// public string GetString_0(int Num) { string ff = ""; for (int i = 0; i < Num; i++) { ff = ff.Insert(ff.Length, "\0"); } return ff; } #endregion #region 辅助方法 /// /// 批量清空PLC寄存器 /// private async Task ClearPLCRegistersBatch(PlcGroup pf, int[] varIndexes, short value) { var dict = varIndexes.ToDictionary(i => pf.VarList[i].VarAddress, _ => value); await WritePLCInt16Batch(pf, dict); } private async Task WritePLCInt16BatchWithTimeout(PlcGroup pf, Dictionary addressValues) { try { // 给PLC写入加超时,避免卡慢 var writeTask = WritePLCInt16Batch(pf, addressValues); //var timeoutTask = Task.Delay(_plcTimeout); //var completedTask = await Task.WhenAny(writeTask, timeoutTask).ConfigureAwait(false); //if (completedTask == timeoutTask) //{ // //LogHelper.Instance.WriteError($"PLC批量写入超时({_plcTimeout.TotalSeconds}s)", "WuLiuLog"); // throw new TimeoutException("PLC写入超时"); //} //await writeTask.ConfigureAwait(false); } catch (Exception ex) { LogHelper.Instance.WriteError($"PLC批量写入异常:{ex.Message}", "WuLiuLog"); // 可考虑重试1次(可选) // await Task.Delay(100); // await WritePLCInt16Batch(pf, addressValues).ConfigureAwait(false); } } /// /// 批量写入Int16到PLC /// private async Task WritePLCInt16Batch(PlcGroup pf, Dictionary addressValueDict) { //if (addressValueDict.Count == 0) return; //var tasks = addressValueDict.Select(kv => WritePLCInt16Sync(pf, kv.Key, kv.Value)); //await Task.WhenAll(tasks); if (addressValueDict == null || addressValueDict.Count == 0) return; var plc = GetPlcDevice(pf.PlcNum); if (plc == null) return; //await _plcSemaphore.WaitAsync().ConfigureAwait(false); try { // 【关键优化】批量写入,一次通讯完成 foreach (var kv in addressValueDict) { plc.WriteInt16(kv.Key, kv.Value); } } finally { //_plcSemaphore.Release(); } } /// /// 写入Int16到PLC /// private async Task WritePLCInt16Sync(PlcGroup pf, string address, short value) { try { var plc = GetPlcDevice(pf.PlcNum); if (plc == null) return; //// 加信号量 + 超时保护 //await _plcSemaphore.WaitAsync(100); //try { plc.WriteInt16(address, value); } //finally //{ // _plcSemaphore.Release(); //} } catch (Exception ex) { LogHelper.Instance.WriteError($"PLC写入失败(地址:{address}):{ex.Message}", "SysErrorLog"); } } /// /// 写入字节数组到PLC(带超时) /// private async Task WritePLCByteArray(PlcGroup pf, string address, byte[] data) { await _plcSemaphore.WaitAsync(); try { //var cts = new CancellationTokenSource(_plcTimeout); //await Task.Run(() => // CommonMethods.plcDevices[pf.PlcNum - 1].WriteValue(address.ToString(), data, PLC.DataType.ArrByte), cts.Token); await Task.Run(() => GetPlcDevice(pf.PlcNum).WriteValue(address.ToString(), data, PLC.DataType.ArrByte)); } finally { _plcSemaphore.Release(); } } /// /// 检测数据是否只包含字母加数字 /// /// /// private static readonly Regex _alphaNumericRegex = new Regex("^[a-zA-Z0-9]+$", RegexOptions.Compiled); public bool IsAlphaNumeric(string input) { return !string.IsNullOrEmpty(input) && _alphaNumericRegex.IsMatch(input); } /// /// 格式化条码 /// private string FormatBarcode(string barcode) { if (string.IsNullOrEmpty(barcode)) return string.Empty; int index = barcode.IndexOf('\0'); if (index >= 0) barcode = barcode.Substring(0, index); return barcode.Replace("\0", "").Replace("\r", "").Replace(" ", "").Trim(); } #endregion } } //http://10.22.160.3/core/api/public/product/process/param/new/result //{ // "equipNum": "60PLR03", // "type": "DZ", // "payload": "{\"siteCode\":\"60J\",\"lineCode\":\"KW-602\",\"userName\":\"admin\",\"materialCode\":\"81044802\",\"carCode\":\"\",\"recordDate\":\"2026/06/24 12:01:00\",\"qty\":2,\"containerCode\":\"ETE\",\"identificationList\":[{\"identification\":\"ML-10085826-001\",\"qualityStatus\":\"\",\"tagDataVOList\":[{\"tagCode\":\"TRAY_NUMBER_DBJJG\",\"tagValue\":\"\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"托盘号\"},{\"tagCode\":\"PALLET_NUMBER_DBJJG\",\"tagValue\":\"\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"栈板号\"},{\"tagCode\":\"BATTERY_LOCATION_DBJJG\",\"tagValue\":\"111\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"电池位置\"},{\"tagCode\":\"TOP_COVER_CODE_DBJJG\",\"tagValue\":\"\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"电芯顶盖码\"}]},{\"identification\":\"ML-10085826-002\",\"qualityStatus\":\"\",\"tagDataVOList\":[{\"tagCode\":\"TRAY_NUMBER_DBJJG\",\"tagValue\":\"\",\"tagTime\":\"2026/06/2412:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"托盘号\"},{\"tagCode\":\"PALLET_NUMBER_DBJJG\",\"tagValue\":\"\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"栈板号\"},{\"tagCode\":\"BATTERY_LOCATION_DBJJG\",\"tagValue\":\"4512\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"电池位置\"},{\"tagCode\":\"TOP_COVER_CODE_DBJJG\",\"tagValue\":\"\",\"tagTime\":\"2026/06/24 12:01:00\",\"tagCalculateResult\":\"\",\"tagRemark\":\"电芯顶盖码\"}]}]}" //}