2267 lines
94 KiB
C#
2267 lines
94 KiB
C#
|
||
using JinYuan.DAL.Models;
|
||
using JinYuan.DAL.Service;
|
||
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设备安全访问
|
||
/// <summary>
|
||
/// 安全获取PLC设备
|
||
/// </summary>
|
||
/// <param name="plcNum">PLC编号(从1开始)</param>
|
||
/// <returns>PLC设备实例,若不存在或未连接则返回null</returns>
|
||
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 物流线料盘来料
|
||
/// <summary>
|
||
/// 物流线料盘来料
|
||
/// </summary>
|
||
private async Task TrayFeedingStation(JYResult<byte[]> 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<TrayEntity> { 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); //反馈处理完成
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 带超时的MES料盘信息获取
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 等级查询结果
|
||
/// </summary>
|
||
private class GradeQueryResult
|
||
{
|
||
public string Grade { get; set; }
|
||
public bool IsConsistent { get; set; }
|
||
public string ErrorMessage { get; set; }
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 带超时的电池等级查询
|
||
/// </summary>
|
||
private async Task<GradeQueryResult> 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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建MaterialLot列表
|
||
/// </summary>
|
||
private List<MaterialLot> BuildMaterialLotList(TrayInfo trayInfo)
|
||
{
|
||
var listCode = new List<MaterialLot>();
|
||
|
||
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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建TrayEntity实体
|
||
/// </summary>
|
||
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
|
||
};
|
||
}
|
||
|
||
/// <summary>
|
||
/// 反馈结果到PLC
|
||
/// </summary>
|
||
private async Task FeedBackToPLC(PlcGroup pf, TrayInfo trayInfo, string grade)
|
||
{
|
||
MaterialFrameRuleService service = new MaterialFrameRuleService(CommonMethods.db.Db);
|
||
MaterialFrameRule rule = service.GetRuleByCode(trayInfo.trayNo);
|
||
|
||
short model = (short)CommonMethods.mesConfig.GetModel(trayInfo?.productCode ?? CommonMethods.mesConfig.ModelName);
|
||
if (model == 0) model = 1;
|
||
|
||
// TODO 注释电池型号自动选择逻辑 by clm 20260831
|
||
//string modelName = trayInfo?.productCode;
|
||
//short model = CommonMethods.mesConfig.GetModelNum(modelName, rule?.Workshop_Code ?? string.Empty);
|
||
|
||
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");
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 处理错误并反馈PLC
|
||
/// </summary>
|
||
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<TrayEntity> { 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
|
||
{
|
||
MesUrlService service = new MesUrlService(CommonMethods.db.Db);
|
||
string url = service.GetMesUrl(trayID, ConstValue.INTER_QueryTray);
|
||
if (string.IsNullOrEmpty(url))
|
||
{
|
||
throw new ArgumentException($"未找到{ConstValue.INTER_QueryTray}对应的MES接口地址");
|
||
}
|
||
|
||
var (success, mesMessage, mesResType, trayinfo) = await CommonMethods.hbgMes.QueryMesTrayInfosAsync(
|
||
url,
|
||
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<MaterialGrade> batteryInfo)> QueryGrades(List<MaterialLot> 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 物流线料盘进站
|
||
/// <summary>
|
||
/// 物流线料盘进站
|
||
/// </summary>
|
||
private async Task CheckTrayStation(JYResult<byte[]> 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<TrayEntity> { 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); //反馈处理完成
|
||
}
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 进站反馈结果到PLC
|
||
/// </summary>
|
||
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");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理进站错误
|
||
/// </summary>
|
||
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<TrayEntity> { 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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 保存料盘数据
|
||
/// </summary>
|
||
/// <param name="list"></param>
|
||
///
|
||
private async Task SaveTrayDataBatchAsync(List<TrayEntity> 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 电池进站
|
||
|
||
/// <summary>
|
||
/// 电池进站
|
||
/// </summary>
|
||
private async Task FeedingStation(JYResult<byte[]> 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");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 清空电池条码区域
|
||
/// </summary>
|
||
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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入电池条码到PLC
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 构建上料实体列表
|
||
/// </summary>
|
||
private List<AGearEntity> 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();
|
||
}
|
||
|
||
/// <summary>
|
||
/// 反馈上料结果到PLC
|
||
/// </summary>
|
||
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");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理上料错误
|
||
/// </summary>
|
||
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<AGearEntity> { entity }), null);
|
||
await WritePLCInt16Sync(pf, pf.VarList[5].VarAddress, 2);
|
||
}
|
||
|
||
#region 保存上料数据
|
||
/// <summary>
|
||
/// 保存上料数据
|
||
/// </summary>
|
||
/// <param name="list"></param>
|
||
private async Task SaveFeedingDataBatchAsync(List<AGearEntity> list)
|
||
{
|
||
if (list == null || list.Count == 0)
|
||
return;
|
||
|
||
try
|
||
{
|
||
var barcodes = list.Select(x => x.BarCode).ToList();
|
||
|
||
var existing = CommonMethods.db.QueryWhereList<AGearEntity>()
|
||
.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<AGearEntity>(toUpdate);
|
||
if (toInsert.Any())
|
||
await CommonMethods.db.AddReturnBoolAsync<AGearEntity>(toInsert);
|
||
|
||
// UI更新(主线程)
|
||
_uiSyncContext.Post(_ => CommonMethods.ShowFeedingDelegate?.Invoke(list), null);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
LogHelper.Instance.WriteError($"组盘工位保存处理异常:{ex}", "SysErrorLog");
|
||
throw; // 重新抛出异常,让上层知道保存失败
|
||
}
|
||
finally
|
||
{
|
||
//_dbSemaphore.Release();
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
#endregion
|
||
|
||
#region 满箱上传
|
||
/// <summary>
|
||
/// 组盘
|
||
/// </summary>
|
||
/// <param name="bArrays"></param>
|
||
/// <param name="plcModel"></param>
|
||
/// <returns></returns>
|
||
private async Task PackStation(JYResult<byte[]> 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<byte[]> 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 保存叠盘数据
|
||
/// <summary>
|
||
/// 保存叠盘工位数据
|
||
/// </summary>
|
||
/// <param name="list"></param>
|
||
private async Task SaveBlankingDataBatchAsync(List<BGearEntity> 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<BGearEntity>(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<ParsedData> ParseAndValidateData(JYResult<byte[]> 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<byte[]> kbArrays = plcDevice.ReadValue<JYResult<byte[]>>(
|
||
$"{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<byte[]> kbArrays = plcDevice.ReadValue<JYResult<byte[]>>(
|
||
$"{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<BatteryInfo> ParseBatteryBarcodes(JYResult<byte[]> bArrays, short layerNum)
|
||
{
|
||
const int BARCODE_SIZE = 40;
|
||
|
||
int maxBatteryNum = CommonMethods.mesConfig.rowsPerLayer * CommonMethods.mesConfig.colsPerLayer;
|
||
var batteryInfos = new List<BatteryInfo>(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<string> GetBatteryGrade(string barcode)
|
||
{
|
||
try
|
||
{
|
||
await _dbSemaphore.WaitAsync();
|
||
var entities = await Task.Run(() => CommonMethods.db.QueryWhereList<AGearEntity>(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> { 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<BatteryInfo>();
|
||
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<BatteryEntity>(
|
||
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<MESPackLoadResult> CallMESPackLoadAsync(ParsedData data, List<BatteryInfo> 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<BatteryEntity>();
|
||
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> { 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<bool> CallMESUpResultParamAsync(string containerCode, string palletId, List<BatteryInfo> 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<BatteryEntity>();
|
||
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> { 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<BatteryEntity>();
|
||
|
||
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> { 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<BatteryEntity>(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> { 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<BatteryEntity> batteryEntities)
|
||
{
|
||
if (batteryEntities == null || !batteryEntities.Any()) return;
|
||
|
||
try
|
||
{
|
||
var containerCodes = batteryEntities.Select(m => m.containerCode).Distinct().ToList();
|
||
bool b = await CommonMethods.db.AddReturnBoolAsync<BatteryEntity>(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<string, short>();
|
||
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> { 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> { 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> { 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> { bGearEntity };
|
||
//CommonMethods.ShowBlankingDelegate?.Invoke(bGearlist);
|
||
|
||
//UpdatePLCStatus(pf, data.LayerNum, 1, 1, 2, data.LayerNum);
|
||
var bGearlist = new List<BGearEntity> { 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<BatteryInfo> 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<MaterialPackInfo> MaterialPackInfos { get; set; }
|
||
}
|
||
|
||
private enum ErrorType
|
||
{
|
||
None,
|
||
ContainerCodeEmpty,
|
||
InvalidStation,
|
||
InvalidLayer,
|
||
NoValidBarcodes,
|
||
NotFullPack
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region 强制出站
|
||
private Task ForceOutStation(JYResult<byte[]> 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;
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 解析位置编码
|
||
/// </summary>
|
||
/// <param name="number">位置编码(0-99999)</param>
|
||
/// <returns>包含层号和位置信息的元组</returns>
|
||
public string ParseLocation(int layerNum, int number)
|
||
{
|
||
if (CommonMethods.mesConfig.colsPerLayer <= 0)
|
||
throw new InvalidOperationException("列数配置无效");
|
||
|
||
Dictionary<int, string> dictRow = new Dictionary<int, string>() { { 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 料框码解绑
|
||
/// <summary>
|
||
/// 料框码解绑
|
||
/// </summary>
|
||
/// <param name="bArrays"></param>
|
||
/// <param name="plcModel"></param>
|
||
/// <returns></returns>
|
||
private async Task UnBindTrayStation(JYResult<byte[]> 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
|
||
{
|
||
MesUrlService service = new MesUrlService(CommonMethods.db.Db);
|
||
string url = service.GetMesUrl(trayID, ConstValue.INTER_UnBindTray);
|
||
if (string.IsNullOrEmpty(url))
|
||
{
|
||
throw new ArgumentException($"未找到{ConstValue.INTER_UnBindTray}对应的MES接口地址");
|
||
}
|
||
|
||
var (success, mesMessage) = await CommonMethods.hbgMes.UnbindTrayInfosAsync(
|
||
url,
|
||
CommonMethods.mesConfig.equipNum,
|
||
trayID
|
||
).ConfigureAwait(false);
|
||
|
||
return (success, mesMessage);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
return (false, $"MES解绑失败{ex.Message}");
|
||
}
|
||
}
|
||
#endregion
|
||
|
||
|
||
|
||
|
||
|
||
#region 创建空的数组List
|
||
|
||
private List<T> GetList<T>(int count, T defaultValue)
|
||
{
|
||
if (count <= 0)
|
||
{
|
||
return new List<T>();
|
||
}
|
||
// Enumerable.Repeat比循环Add更高效,尤其是大数据量
|
||
return Enumerable.Repeat(defaultValue, count).ToList();
|
||
}
|
||
|
||
public List<MesResType> GetMesType(int Num)
|
||
{
|
||
List<MesResType> mesResTypes = new List<MesResType>();
|
||
for (int i = 0; i < Num; i++)
|
||
{
|
||
mesResTypes.Add(MesResType.UnKnow);
|
||
}
|
||
return mesResTypes;
|
||
}
|
||
|
||
/// <summary>
|
||
/// PLC内批量写\0
|
||
/// </summary>
|
||
/// <param name="Num"></param>
|
||
/// <returns></returns>
|
||
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 辅助方法
|
||
|
||
/// <summary>
|
||
/// 批量清空PLC寄存器
|
||
/// </summary>
|
||
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<string, short> 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);
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 批量写入Int16到PLC
|
||
/// </summary>
|
||
private async Task WritePLCInt16Batch(PlcGroup pf, Dictionary<string, short> 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();
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 写入Int16到PLC
|
||
/// </summary>
|
||
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");
|
||
}
|
||
}
|
||
|
||
|
||
/// <summary>
|
||
/// 写入字节数组到PLC(带超时)
|
||
/// </summary>
|
||
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();
|
||
}
|
||
}
|
||
|
||
|
||
|
||
/// <summary>
|
||
/// 检测数据是否只包含字母加数字
|
||
/// </summary>
|
||
/// <param name="input"></param>
|
||
/// <returns></returns>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 格式化条码
|
||
/// </summary>
|
||
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\":\"电芯顶盖码\"}]}]}"
|
||
//} |