using JinYuan.Helper; using JinYuan.MES.Models; using JinYuan.Models; using JinYuan.VirtualDataLibrary; using Language; using MiniExcelLibs; using PLC; using PLC.PLCAlarm; using PLCCommunication; using PLCCommunication.Common; using PLCCommunication.Common.DataConvert; using SqlSugar; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using static System.Runtime.CompilerServices.RuntimeHelpers; using AlarmData = JinYuan.Models.AlarmData; using DataType = PLC.DataType; namespace JinYuan.ControlCenters { public partial class ControlCenter : IDisposable { #region 单例 private static readonly object uiLock = new object(); private static ControlCenter _instance; public static ControlCenter Instance { get { lock (uiLock) { if (_instance == null) _instance = new ControlCenter(); return _instance; } } } #endregion #region 变量 private Dictionary, PlcGroup, Task>> _stationFunctions; private Dictionary _groupThreads; private readonly Dictionary> _plcDeviceMapping = new Dictionary>(); private readonly Dictionary _plcAlarmReaders = new Dictionary(); private CancellationTokenSource _cts; private Task _heartbeatTask; private const int HeartbeatInterval = 1000; private readonly object _statusLock = new object(); private Dictionary _groupStatus = new Dictionary(); /// /// 定时器-定时刷新状态和数据 /// private System.Timers.Timer _timerRefreshData; #region 独立任务队列 private readonly ConcurrentQueue _taskQueue = new ConcurrentQueue(); private Thread _workerThread; private readonly ManualResetEvent _taskEvent = new ManualResetEvent(false); private bool _isWorkerRunning = true; // 队列监控 private int _maxQueueSize = 1000; private DateTime _lastQueueWarningTime = DateTime.MinValue; private int _totalProcessedCount = 0; private int _totalRejectedCount = 0; // 线程健康监控 private DateTime _lastHeartbeat = DateTime.Now; private int _stuckDetectCount = 0; #endregion #region 资源管理 //private readonly SemaphoreSlim _dbSemaphore = new SemaphoreSlim(10); // 限制数据库并发 private readonly SemaphoreSlim _plcWriteSemaphore = new SemaphoreSlim(5); // 限制PLC写入并发 private bool _isDisposed = false; private System.Timers.Timer _healthMonitorTimer; #endregion #endregion #region 2、初始化函数 private ControlCenter() { _groupThreads = new Dictionary(); _cts = new CancellationTokenSource(); if (CommonMethods.plcDevices == null) { CommonMethods.plcDevices = new List(); } // 启动独立任务处理线程 StartWorkerThread(); InitializeDevicesFromExcel(); GetAlarmData(); InitFunc(); _timerRefreshData = new System.Timers.Timer(1000); _timerRefreshData.Elapsed += Timer_RefreshData_Elapsed; _timerRefreshData.Start(); Thread thread = new Thread(ShowStateEven_Run); thread.IsBackground = true; thread.Start(); string[] clrPath = new[] { CommonMethods.strAlarmLogspath, CommonMethods.strMesLogspath, CommonMethods.strWattrMeterpath, @"D:\APILog\Logs\" }; short[] seltWay = new[] { (short)0, (short)0, (short)0, (short)1 }; DeleteLog.Start(clrPath, seltWay); if (!CommonMethods.IsRun) { CommonMethods.ResetEventRecv.Reset(); } // 启动健康监控 StartHealthMonitor(); } /// /// 开始健康监控 /// private void StartHealthMonitor() { _healthMonitorTimer = new System.Timers.Timer(30000); // 每30秒检查一次 _healthMonitorTimer.Elapsed += HealthMonitorCallback; _healthMonitorTimer.AutoReset = true; _healthMonitorTimer.Start(); } private void HealthMonitorCallback(object sender, System.Timers.ElapsedEventArgs e) { try { var now = DateTime.Now; var timeSinceLastHeartbeat = now - _lastHeartbeat; // 检查工作线程是否卡死 if (timeSinceLastHeartbeat.TotalSeconds > 60) { _stuckDetectCount++; LogHelper.Instance.WriteError($"健康监控:工作线程可能卡死,最后心跳:{_lastHeartbeat}, 卡死计数:{_stuckDetectCount}"); if (_stuckDetectCount >= 3) { LogHelper.Instance.WriteError($"健康监控:工作线程已卡死,尝试恢复..."); RecoverWorkerThread(); _stuckDetectCount = 0; } } else { _stuckDetectCount = 0; } // 记录系统状态 int queueSize = _taskQueue.Count; if (queueSize > _maxQueueSize * 0.8) { LogHelper.Instance.WriteError($"健康监控:任务队列接近溢出,当前大小:{queueSize}/{_maxQueueSize}, 已处理:{_totalProcessedCount}, 已拒绝:{_totalRejectedCount}"); } // 记录线程状态 int activeThreads = Process.GetCurrentProcess().Threads.Count; LogHelper.Instance.WriteLog($"健康监控 - 队列:{queueSize}, 线程数:{activeThreads}, 内存:{GC.GetTotalMemory(false) / 1024 / 1024}MB"); } catch (Exception ex) { LogHelper.Instance.WriteError($"健康监控异常:{ex.Message}"); } } /// /// 恢复工作线程 /// private void RecoverWorkerThread() { try { _isWorkerRunning = false; _taskEvent.Set(); if (_workerThread != null && _workerThread.IsAlive) { _workerThread.Join(5000); } // 清空队列 while (_taskQueue.TryDequeue(out _)) { } // 重新启动 _isWorkerRunning = true; StartWorkerThread(); LogHelper.Instance.WriteLog("健康监控:工作线程已恢复"); } catch (Exception ex) { LogHelper.Instance.WriteError($"恢复工作线程失败:{ex.Message}"); } } /// /// 独立工作线程 /// private void StartWorkerThread() { _workerThread = new Thread(delegate () { var lastProgressLog = DateTime.Now; while (_isWorkerRunning && !(_cts?.IsCancellationRequested ?? true)) { try { // 更新心跳 _lastHeartbeat = DateTime.Now; // 等待事件,添加超时防止永久阻塞 bool hasEvent = _taskEvent.WaitOne(1000); if (!hasEvent) { // 超时继续循环,保持心跳 continue; } // 批量处理任务,提高效率 int batchSize = Math.Min(10, _taskQueue.Count); for (int i = 0; i < batchSize; i++) { if (_taskQueue.TryDequeue(out Action task)) { try { // 添加超时控制 var taskWithTimeout = Task.Run(() => task()); if (!taskWithTimeout.Wait(30000)) // 30秒超时 { LogHelper.Instance.WriteError($"任务执行超时(30秒),已跳过"); } _totalProcessedCount++; } catch (Exception ex) { LogHelper.Instance.WriteError($"业务执行异常:{ex.Message}"); } } } // 定期记录处理统计 if ((DateTime.Now - lastProgressLog).TotalMinutes >= 5) { LogHelper.Instance.WriteLog($"任务处理统计 - 已处理:{_totalProcessedCount}, 已拒绝:{_totalRejectedCount}, 当前队列:{_taskQueue.Count}"); lastProgressLog = DateTime.Now; } } catch (Exception ex) { LogHelper.Instance.WriteError($"工作线程异常:{ex.Message}"); Thread.Sleep(1000); } } }); _workerThread.IsBackground = true; _workerThread.Priority = ThreadPriority.AboveNormal; _workerThread.Name = "ControlCenterWorker"; _workerThread.Start(); } /// /// 安全添加任务到队列 /// private bool EnqueueTask(Action task, string taskName = "") { try { // 检查队列是否已满 if (_taskQueue.Count >= _maxQueueSize) { _totalRejectedCount++; // 限制日志频率,避免日志风暴 if ((DateTime.Now - _lastQueueWarningTime).TotalSeconds > 30) { LogHelper.Instance.WriteError($"任务队列已满({_taskQueue.Count}/{_maxQueueSize}),拒绝任务:{taskName}"); _lastQueueWarningTime = DateTime.Now; } return false; } _taskQueue.Enqueue(task); _taskEvent.Set(); return true; } catch (Exception ex) { LogHelper.Instance.WriteError($"入队失败:{ex.Message}"); return false; } } /// /// 初始化工位方法 /// private void InitFunc() { _stationFunctions = new Dictionary, PlcGroup, Task>>(); _stationFunctions.Add("物流线料盘来料", TrayFeedingStation); _stationFunctions.Add("物流线料盘进站", CheckTrayStation); _stationFunctions.Add("电池进站", FeedingStation); _stationFunctions.Add("电池与拖盘绑定", PackStation); _stationFunctions.Add("强制放行", ForceOutStation); _stationFunctions.Add("料框码解绑", UnBindTrayStation); } /// /// 启动心跳及工位读取线程 /// public async Task StartAsync() { // 先清理旧的线程 StopAndCleanOldThreads(); // 创建新线程 CreatePLCThreads(); _heartbeatTask = StartHeartbeatCheck(_cts.Token); await Task.CompletedTask; } /// /// 停止并清理旧线程 /// private void StopAndCleanOldThreads() { try { foreach (var kvp in _groupThreads) { var thread = kvp.Value; if (thread != null && thread.IsAlive) { try { thread.Join(1000); } catch (ThreadInterruptedException) { } catch { } } } _groupThreads.Clear(); } catch (Exception ex) { LogHelper.Instance.WriteError($"清理旧线程异常:{ex.Message}"); } } /// /// 加载配置文件 /// private void InitializeDevicesFromExcel() { string gongwei = CommonMethods.sysConfig.StationNo; CommonMethods.AddDataMonitorLog(1, "开始加载配置参数……".Translated()); string deviceConfigPath = CommonMethods.deviceConfigPath;//Path.Combine(configFolderPath, "Device.xlsx"); string groupConfigPath = CommonMethods.groupConfigPath;//Path.Combine(configFolderPath, $"Group_{gongwei}.xlsx"); string variableConfigPath = CommonMethods.variableConfigPath;//Path.Combine(configFolderPath, $"Variable_{gongwei}.xlsx"); //string plcVariableConfigPath = Path.Combine(configFolderPath, "PLCVariable.xlsx"); if (!File.Exists(deviceConfigPath) || !File.Exists(groupConfigPath) || !File.Exists(variableConfigPath)) { LogHelper.Instance.WriteError("配置文件不存在"); return; } try { var deviceConfigs = MiniExcel.Query(deviceConfigPath).ToList(); var groupConfigs = MiniExcel.Query(groupConfigPath).ToList(); var variableConfigs = MiniExcel.Query(variableConfigPath).ToList(); foreach (var deviceConfig in deviceConfigs.Where(d => d.IsActive)) { IPEndPoint iep = new IPEndPoint(IPAddress.Parse(deviceConfig.IPAddress), deviceConfig.Port); PlcReadWriteBase plc = PLCFactory.CreatePLC(GetPLCTypeEnum(deviceConfig.PLCType), deviceConfig.PlcNum, $"PLC_{deviceConfig.PlcNum}", iep); string GongWei = CommonMethods.sysConfig.GongWei; var stationNo = CommonMethods.sysConfig.StationNo; deviceConfig.Cts = new CancellationTokenSource(); deviceConfig.GroupList = groupConfigs .Where(g => g.IsActive && g.PlcNum == deviceConfig.PlcNum && ((g.PlcNum == 1 && g.StationName == stationNo) || (g.PlcNum == 2 && g.StationName == GongWei)) || g.GroupName == "MES心跳") .ToList(); foreach (var group in deviceConfig.GroupList) { group.VarList = variableConfigs .Where(v => v.GroupName == group.GroupName && v.PlcNum == deviceConfig.PlcNum && v.StationName == group.StationName) .ToList(); lock (_statusLock) { _groupStatus[group.GroupName] = 0; } } _plcDeviceMapping[plc] = new List { deviceConfig }; CommonMethods.plcDevices.Add(plc); // 初始化 PLCAlarm Reader报警读取 var alarmReader = new PLCAlarmReader( Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AlarmConfig.csv"), Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AlarmRecords"), RecordType.RecordWhenFinish, false ); //alarmReader.PLCAlarm_New += AlrmReader_PLCAlarm_New; //alarmReader.PLCAlarm_Remove += AlrmReader_PLCAlarm_Remove; _plcAlarmReaders[plc] = alarmReader; CommonMethods.plcStatuses[plc] = new Dictionary { ["OldStatus"] = (short)0, ["StatusCount"] = 0, ["GuidAlarm"] = string.Empty, ["Status"] = new List { false, false, false, false } }; } } catch (Exception ex) { LogHelper.Instance.WriteError($"加载配置失败:{ex.Message}"); } CommonMethods.AddDataMonitorLog(1, "配置文件加载完成".Translated()); } #endregion #region 3、PLC读写 #region 创建PLC线程 几个工位就几个线程 /// /// 按工位读取线程 /// private void CreatePLCThreads() { for (int i = 0; i < CommonMethods.plcDevices.Count; i++) { PlcReadWriteBase plc = CommonMethods.plcDevices[i]; int plcIndex = i; // 捕获索引 if (_plcDeviceMapping.TryGetValue(plc, out var devList)) { foreach (PlcDevice dev in devList) { foreach (PlcGroup group in dev.GroupList) { if (group.IsActive) { string key = dev.PlcNum + "-" + group.GroupName; // 如果已存在同名线程,先停止 if (_groupThreads.ContainsKey(key)) { var oldThread = _groupThreads[key]; if (oldThread != null && oldThread.IsAlive) { try { oldThread.Join(500); } catch { } } } Thread t = new Thread(ReadPLCWorkModel); t.IsBackground = true; t.Priority = ThreadPriority.Highest; t.Name = $"PLC_{key}"; _groupThreads[key] = t; var param = new Tuple(group, plcIndex); t.Start(param); } } } } } LogHelper.Instance.WriteLog($"PLC线程创建完成,共{_groupThreads.Count}个线程"); } #endregion #region 循环读取PLC工位状态 /// /// 读取PLC循环工位 /// /// private void ReadPLCWorkModel(object parameter) { Tuple param = parameter as Tuple; if (param == null) return; PlcGroup group = param.Item1; int plcIndex = param.Item2; // 添加重连计数器 int consecutiveErrorCount = 0; const int maxConsecutiveErrors = 10; int lastLogSecond = 0; // 用于限制日志频率 while (_cts != null && !_cts.Token.IsCancellationRequested) { try { // 检查系统运行状态 if (!CommonMethods.IsRun) { Thread.Sleep(100); consecutiveErrorCount = 0; continue; } PlcReadWriteBase plc = CommonMethods.plcDevices[plcIndex]; if (plc == null || !plc.IsConnected) { consecutiveErrorCount++; if (consecutiveErrorCount > maxConsecutiveErrors && lastLogSecond != DateTime.Now.Second) { lastLogSecond = DateTime.Now.Second; LogHelper.Instance.WriteError($"【诊断-PLC状态】{group.GroupName} | PLC未连接 | 连续失败次数={consecutiveErrorCount}"); } Thread.Sleep(100); continue; } consecutiveErrorCount = 0; if (!CommonMethods.sysConfig.WuLiu && group.GroupName == "物流线料盘来料") { Thread.Sleep(100); continue; } // 重点监视物流线料盘进站 bool isCriticalStation = group.GroupName == "物流线料盘进站" || group.GroupName == "物流线料盘来料"; // 读取触发信号 var trigResult = ReadWithTimeout(plc, group, 2000); if (trigResult == null || !trigResult.IsSuccess) { if (isCriticalStation && lastLogSecond != DateTime.Now.Second) { lastLogSecond = DateTime.Now.Second; LogHelper.Instance.WriteError($"【诊断-读取失败】{group.GroupName} | 读取触发信号失败"); } Thread.Sleep(50); continue; } short trig = trigResult.Content; int status = 0; lock (_statusLock) { if (_groupStatus.ContainsKey(group.GroupName)) status = _groupStatus[group.GroupName]; } // 记录触发信号(仅对关键工站) if (isCriticalStation && trig != 0) { StationDiagnostics.LogTrigger(group.GroupName, trig, status); } if (trig == 1 && status == 0) { if (isCriticalStation) { LogHelper.Instance.WriteLog($"【诊断-信号触发】{group.GroupName} | 检测到触发信号 | 开始处理..."); } JYResult data = plc.ReadValue>( group.VarList[1].VarAddress, DataType.ArrByte, Convert.ToUInt16(group.VarList[1].OffsetOrLength) ); if (data != null && _stationFunctions.ContainsKey(group.GroupName)) { Func, PlcGroup, Task> func = _stationFunctions[group.GroupName]; lock (_statusLock) { _groupStatus[group.GroupName] = 1; } // 使用安全的入队方法 string groupName = group.GroupName; DateTime triggerTime = DateTime.Now; bool enqueued = EnqueueTask(() => { try { StationDiagnostics.LogTaskStart(groupName); // 记录入队到开始执行的延迟 var queueDelay = DateTime.Now - triggerTime; if (queueDelay.TotalMilliseconds > 500) { LogHelper.Instance.WriteError($"【诊断-队列延迟】{groupName} | 入队到执行延迟={queueDelay.TotalMilliseconds:F0}ms"); } // 添加超时控制 var task = func(data, group); if (!task.Wait(30000)) { StationDiagnostics.LogTimeout(groupName, 30); LogHelper.Instance.WriteError($"【诊断-业务超时】{groupName} | 执行超过30秒"); // 超时后强制完成,避免状态卡死 lock (_statusLock) { if (_groupStatus.ContainsKey(groupName) && _groupStatus[groupName] == 1) { WriteFinishToPLC(group, plcIndex); _groupStatus[groupName] = 0; LogHelper.Instance.WriteError($"【诊断-强制恢复】{groupName} | 超时后强制重置状态"); } } return; } StationDiagnostics.LogTaskComplete(groupName, true); LogHelper.Instance.WriteLog($"【业务完成】{groupName} | 总耗时={(DateTime.Now - triggerTime).TotalMilliseconds:F0}ms"); } catch (Exception ex) { StationDiagnostics.LogTaskComplete(groupName, false, ex.Message); LogHelper.Instance.WriteError($"【诊断-异常】{groupName} | {ex.Message}"); // 异常时也要重置状态 lock (_statusLock) { if (_groupStatus.ContainsKey(groupName) && _groupStatus[groupName] == 1) { _groupStatus[groupName] = 0; } } } }, groupName); if (!enqueued) { LogHelper.Instance.WriteError($"【诊断-入队失败】{groupName} | 队列已满,任务被拒绝"); lock (_statusLock) { _groupStatus[group.GroupName] = 0; } } else { StationDiagnostics.LogTaskEnqueued(groupName, _taskQueue.Count); } } } else if (trig == 0 && status == 1) { if (isCriticalStation) { LogHelper.Instance.WriteLog($"【诊断-信号下降】{group.GroupName} | 触发信号已复位 | 清理状态"); } lock (_statusLock) { _groupStatus[group.GroupName] = 0; } ClearFinishFlags(group, plcIndex); } Thread.Sleep(50); } catch (Exception ex) { LogHelper.Instance.WriteError($"PLC读取异常:{ex.Message}"); Thread.Sleep(100); } } } /// /// 带超时的PLC读取(新增) /// private JYResult ReadWithTimeout(PlcReadWriteBase plc, PlcGroup group, int timeoutMs) { try { var task = Task.Run(() => plc.ReadValue>(group.VarList[0].VarAddress, DataType.Short)); if (task.Wait(timeoutMs)) { return task.Result; } else { LogHelper.Instance.WriteError($"PLC读取超时:{group.GroupName}"); return null; } } catch { return null; } } private void WriteFinishToPLC(PlcGroup group, int plcIndex) { try { PlcReadWriteBase plc = CommonMethods.plcDevices[plcIndex]; if (plc == null || !plc.IsConnected) return; // 使用信号量限制并发写入 if (_plcWriteSemaphore.Wait(1000)) { try { switch (group.GroupName) { case "物流线料盘来料": case "物流线料盘进站": plc.WriteInt16(group.VarList[5].VarAddress, 1); plc.WriteInt16(group.VarList[4].VarAddress, 1); break; case "电池进站": plc.WriteInt16(group.VarList[5].VarAddress, 1); break; case "电池与拖盘绑定": plc.WriteInt16(group.VarList[8].VarAddress, 1); plc.WriteInt16(group.VarList[7].VarAddress, 1); break; case "料框码解绑": plc.WriteInt16(group.VarList[2].VarAddress, 1); break; case "强制放行": plc.WriteInt16(group.VarList[2].VarAddress, 1); break; } } finally { _plcWriteSemaphore.Release(); } } else { LogHelper.Instance.WriteError($"获取PLC写入信号量超时:{group.GroupName}"); } } catch (Exception ex) { LogHelper.Instance.WriteError($"写入PLC失败:{ex.Message}"); } } /// /// 清空PLC响应标志 /// private void ClearFinishFlags(PlcGroup group, int plcIndex) { try { PlcReadWriteBase plc = CommonMethods.plcDevices[plcIndex]; if (plc == null || !plc.IsConnected) return; if (_plcWriteSemaphore.Wait(500)) { try { switch (group.GroupName) { case "电池与拖盘绑定": plc.WriteInt16(group.VarList[8].VarAddress, 0); plc.WriteInt16(group.VarList[7].VarAddress, 0); break; case "电池进站": plc.WriteInt16(group.VarList[5].VarAddress, 0); break; case "物流线料盘来料": case "物流线料盘进站": //plc.WriteInt16(group.VarList[4].VarAddress, 0); plc.WriteInt16(group.VarList[5].VarAddress, 0); break; case "料框码解绑": plc.WriteInt16(group.VarList[2].VarAddress, 0); break; } } finally { _plcWriteSemaphore.Release(); } } } catch { } } #endregion #region 开始停止联机事件 /// /// 开始联机 /// /// public async Task ConnectAllPlcsAsync() { bool allConnected = true; if (CommonMethods.plcDevices.Count <= 0) { CommonMethods.AddDataMonitorLog(1, "无PLC配置信息".Translated() + "," + "请检查配置文件".Translated()); return false; } foreach (var plc in CommonMethods.plcDevices) { if (_plcDeviceMapping.TryGetValue(plc, out var plcDevices)) { foreach (var deviceConfig in plcDevices) { string msg = ""; try { // 使用 Task.Run 来在后台线程执行连接操作 bool isConnected = await Task.Run(delegate () { // 设置一个连接超时 //using var cts = new CancellationTokenSource(10000); try { return plc.Connect(ref msg); } catch { msg = "连接超时"; return false; } }); deviceConfig.IsConnected = isConnected; plc.IsConnected = isConnected; if (!isConnected) { allConnected = false; LogHelper.Instance.WriteLog($"连接PLC失败: {deviceConfig.PlcNum}, 错误: {msg}"); CommonMethods.AddDataMonitorLog(1, "连接PLC失败".Translated() + $": {deviceConfig.PlcNum}, " + "错误".Translated() + $": {msg}"); } else { await SendConfigToPLCAsync(plc); await SendUserRightToPLCAsync(plc); CommonMethods.AddDataMonitorLog(0, "成功连接PLC".Translated() + $": {deviceConfig.PlcNum}"); } } catch (Exception ex) { allConnected = false; LogHelper.Instance.WriteLog($"连接PLC时发生异常: {deviceConfig.PlcNum}, 错误: {ex.Message}"); CommonMethods.AddDataMonitorLog(2, "连接PLC时发生异常".Translated() + $": {deviceConfig.PlcNum}" + "错误".Translated() + $": {ex.Message}"); } } } } return allConnected; } /// /// 断开链接 /// public void DisconnectAllPlcs() { try { if (_cts != null && !_cts.IsCancellationRequested) { _cts.Cancel(); } foreach (var plc in CommonMethods.plcDevices) { try { if (plc.IsConnected) { plc.Disconnect(); plc.IsConnected = false; CommonMethods.AddDataMonitorLog(0, "已断开与PLC".Translated() + $"[{plc.PlcID}]" + "的链接".Translated()); } } catch (Exception ex) { Debug.WriteLine($"断开PLC {plc.PlcID} 异常:{ex.Message}"); } } } catch (Exception ex) { Debug.WriteLine($"断开连接异常: {ex.Message}"); } } #endregion #endregion #region MES /// /// Mes心跳 /// private void UpdateMesHeartBeat() { Thread.Sleep(100); while (!_cts.Token.IsCancellationRequested) { try { if (CommonMethods.IsRun)//CommonMethods.mesConfig.isUpMes && CommonMethods.mesConfig.IsLoginMesOK && { bool isOnline = FastPing(); CommonMethods.mesConfig.isConnected = isOnline; if (!isOnline) { foreach (var plc in CommonMethods.plcDevices) { if (plc.IsConnected && plc.Name == "PLC_1") { plc.WriteInt16("D80", 1); } } } //if (Ping() == true) //{ // CommonMethods.mesConfig.isConnected = true; // string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping成功,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort}"; // TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr); //} //else//Ping失败,提示检查线路。 //{ // int count = 0; // bool mesOnLine = false; // do//重复3次 // { // Thread.Sleep(1000); // if (Ping() == true) { mesOnLine = true; break; } // count++; // } // while (count < 3); // if (!mesOnLine) // { // CommonMethods.mesConfig.isConnected = false; // if (CommonMethods.plcDevices.Count() > 0) // { // for (int i = 0; i < CommonMethods.plcDevices.Count(); i++) // { // if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1") // { // CommonMethods.plcDevices[i].WriteInt16("D80", (short)1); // string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping失败,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort}"; // TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr); // } // } // } // } //} } } catch (Exception ex) { string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping异常,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort},原因:{ex.ToString()}"; TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr); } Thread.Sleep(1000); } } /// /// Ping 检查网络线路问题 /// /// public bool FastPing() { try { string ip = CommonMethods.mesConfig.MESURLAddressIP; using var _ping = new Ping(); int timeout = 500; var reply = _ping.Send(ip, timeout); return reply.Status == IPStatus.Success; } catch (Exception ex) { string errStr = $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff")},Mes心跳Ping异常,IP:{CommonMethods.mesConfig.MESURLAddressIP},端口号:{CommonMethods.mesConfig.MESURLAddressPort},原因:{ex.ToString()}"; TxtHelper.WriteTxt($@"D:\APILog\Logs\MesLogs\{DateTime.Now.ToString("yyyyMMdd")}\Mes心跳报警\{DateTime.Now.ToString("HH")}.txt", errStr); return false; } } #endregion #region 4、心跳和状态检查 /// /// 心跳检测 /// /// /// private async Task StartHeartbeatCheck(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested) { if (CommonMethods.IsRun) { foreach (var plc in CommonMethods.plcDevices) { if (_plcDeviceMapping.TryGetValue(plc, out var plcDevices)) { foreach (var plcDevice in plcDevices) { if (plcDevice.IsActive && plcDevice.IsHeartBeat) { await CheckHeartbeatForDevice(plc, plcDevice); } } } } } await Task.Delay(HeartbeatInterval, cancellationToken); } } /// /// /// /// /// /// private async Task CheckHeartbeatForDevice(PlcReadWriteBase plc, PlcDevice plcDevice) { if (plcDevice.IsConnected) { try { JYResult result = await Task.Run(() => plc.ReadValue>(plcDevice.HeartBeat, DataType.Short)); if (result.IsSuccess) { plcDevice.ErrorTimes = 0; await Task.Run(() => plc.WriteValue(plcDevice.HeartBeat, (short)1, DataType.Short)); plcDevice.IsConnected = true; plc.IsConnected = true; return; } } catch { } plcDevice.ErrorTimes++; if (plcDevice.ErrorTimes >= plcDevice.AllowErrorTimes) { plcDevice.IsConnected = false; plc.IsConnected = false; LogHelper.Instance.WriteLog($"编号:[{plcDevice.PlcNum}]的PLC链接断开:{plcDevice.ErrorTimes}"); } } else { await ReconnectDevice(plc, plcDevice); } } private async Task ReconnectDevice(PlcReadWriteBase plc, PlcDevice plcDevice) { if (!plcDevice.IsFirstConnect) { await Task.Delay(plcDevice.ReConnectTime); plc.Disconnect(); } string msg = ""; var connectResult = await Task.Run(() => plc.Connect(ref msg)); plcDevice.IsConnected = connectResult; LogHelper.Instance.WriteLog(connectResult ? $"PLC编号:{plcDevice.PlcNum}{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接成功{msg}!" : $"[{plcDevice.PlcNum}]#PLC{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接失败{msg}!"); plcDevice.IsFirstConnect = false; } #endregion #region 定时刷新数据 private int TotalNum = 0; private int TotalNum1 = 0; private int Regular = 0; private bool _isUploadingEnergy = false; private bool _isGettingEnergy = false; private void Timer_RefreshData_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { try { // 防止定时器重入 if (Regular >= 300 && !_isUploadingEnergy) { _isUploadingEnergy = true; Task.Run(() => { try { UploadEquEnergy(); } finally { _isUploadingEnergy = false; } }); Regular = 0; } if (TotalNum > 4) { if (CommonMethods.sysConfig.StationNo == "1" && !_isGettingEnergy) { _isGettingEnergy = true; Task.Run(() => { try { GetEnergyData(); } finally { _isGettingEnergy = false; } }); } else if (!_isGettingEnergy) { _isGettingEnergy = true; Task.Run(() => { try { GetEnergyDataFormPlc(); } finally { _isGettingEnergy = false; } }); } Task.Run(() => Refresh12HourProdQty()); GetDefectTypeQty(); ReadPLCComm(); Task.Run(() => GetASqlAlarmData(DateTime.Today, CommonMethods.strClass)); TotalNum = 0; } else if (TotalNum == 2)//设备状态读取 { SetClass(); //变更班次 } if (TotalNum1 >= 3600 * 24) { Task.Run(() => DeleteFileOfLocal()); TotalNum1 = 0; } TotalNum++; TotalNum1++; Regular++; } catch (Exception ex) { LogHelper.Instance.WriteError($"定时器异常:{ex.Message}"); } } #endregion #region 设备综合信息 /// /// 读取PLC通用信息 /// internal void ReadPLCComm() { try { foreach (var plc in CommonMethods.plcDevices) { if (plc.IsConnected && plc.Name == "PLC_1") { _ = UpTotalCount(null); CommonMethods.TotalQty = plc.ReadFloat("D15");//生产总数 CommonMethods.ProOKQty = plc.ReadFloat("D17");//良品总数 CommonMethods.Ppm = plc.ReadFloat("D13"); CommonMethods.JiaDL = Convert.ToDouble(plc.ReadFloat("D11").ToString("F2")); #region 开机时间/运行时间 CommonMethods.PlcOpenAndRunTime = GetList(6, 0); JYResult resByte = plc.ReadValue>($"D50", DataType.ArrByte, 12); if (resByte.IsSuccess && resByte.Content.Length >= 24) { for (int j = 0; j < CommonMethods.PlcOpenAndRunTimeAdress.Count; j++) { CommonMethods.PlcOpenAndRunTime[j] = IntLib.GetIntFromByteArray(resByte.Content, j * 4, DataFormat.CDAB); } } #endregion } } } catch { } } #endregion #region 保存12小时产能数据 public int totalNum = 0; // 总数 public int okNum = 0; // 良品数 /// /// 储存数据到数据库 /// /// public async Task UpTotalCount(int[] DataCount) { try { DateTime now = DateTime.Now; string date = now.ToString("yyyy-MM-dd"); int hour = now.Minute < 30 ? (now.Hour + 23) % 24 : now.Hour; if (now.Hour == 0 && now.Minute < 30) date = now.AddDays(-1).ToString("yyyy-MM-dd"); int inQty = GetToDayHourQty(hour, true); int outQty = GetToDayHourQty(hour, false); Hourprod prod = new Hourprod { FDate = date, FHour = hour, ProdIn = inQty, ProdOut = outQty, TestTime = now, Ppm = (int)CommonMethods.Ppm, JiaDL = Convert.ToDecimal(CommonMethods.JiaDL) }; int update = CommonMethods.db.UpdateSingle(prod, it => new Hourprod { ProdIn = prod.ProdIn, ProdOut = prod.ProdOut, TestTime = prod.TestTime }, it => it.FDate == date && it.FHour == hour); if (update <= 0) await CommonMethods.db.AddReturnBoolAsync(prod); } catch { } //try //{ // DateTime dateTime = DateTime.Now; // string strDate = dateTime.ToString("yyyy-MM-dd"); // // 获取当前时间的小时和分钟 // int currentHour = dateTime.Hour; // int currentMinute = dateTime.Minute; // // 根据当前分钟确定统计小时 // // 如果当前时间在 X:30 之前,则统计的是前一个小时的数据(从前一个小时的30分到这个小时的29分) // // 如果当前时间在 X:30 之后,则统计的是当前小时的数据(从当前小时的30分到下个小时的29分) // int statisticalHour; // if (currentMinute < 30) // { // statisticalHour = (currentHour + 23) % 24; // 前一个小时,处理跨天的情况 // // 如果是凌晨0点前的数据,需要使用前一天的日期 // if (currentHour == 0) // { // strDate = dateTime.AddDays(-1).ToString("yyyy-MM-dd"); // } // } // else // { // statisticalHour = currentHour; // } // // 数据有效性验证 // if (statisticalHour < 0 || statisticalHour > 23) // { // throw new ArgumentException($"无效的小时值: {statisticalHour}"); // } // //读取统计小时的PLC投入总数和产出数 // int ProdCurrAllQty = GetToDayHourQty(statisticalHour, true); // int ProdCurrOKQty = GetToDayHourQty(statisticalHour, false); // // 确保数量为非负数 // if (ProdCurrAllQty < 0 || ProdCurrOKQty < 0) // { // LogHelper.Instance.WriteError($"产量不能为负数: 投入={ProdCurrAllQty}, 产出={ProdCurrOKQty}"); // //throw new ArgumentException($"产量不能为负数: 投入={ProdCurrAllQty}, 产出={ProdCurrOKQty}"); // } // Hourprod mh = new Hourprod // { // FDate = strDate, // FHour = statisticalHour, // ProdIn = ProdCurrAllQty, // ProdOut = ProdCurrOKQty, // TestTime = dateTime, // Ppm = (int)CommonMethods.Ppm, // JiaDL = Convert.ToDecimal(CommonMethods.JiaDL) // }; // // 尝试更新记录 // int res = CommonMethods.db.UpdateSingle( // mh, // it => new Hourprod // { // ProdIn = mh.ProdIn, // ProdOut = mh.ProdOut, // TestTime = mh.TestTime, // Ppm = mh.Ppm, // JiaDL = mh.JiaDL // }, // it => it.FDate == mh.FDate && it.FHour == mh.FHour // ); // // 如果更新失败(记录不存在),则新增记录 // if (res <= 0) // { // bool addResult = await CommonMethods.db.AddReturnBoolAsync(mh).ConfigureAwait(false); // if (!addResult) // { // LogHelper.Instance.WriteError($"保存小时产能新增记录失败"); // } // } // // 可以添加日志记录成功信息 // // Logger.Log($"成功更新/新增产能数据: 日期={strDate}, 小时={statisticalHour}, 时段={statisticalHour}:30~{(statisticalHour + 1) % 24}:29"); //} //catch (Exception ex) //{ // // 这里应该添加错误日志记录 // LogHelper.Instance.WriteEX($"保存小时产能数据失败: ", ex); //} } /// /// 每小时产能读取处理 /// /// /// /// private int GetToDayHourQty(int hour, bool flag) { try { string strAddr = flag ? $"D{1040 + hour}" : $"D{1070 + hour}"; foreach (var plc in CommonMethods.plcDevices) { if (plc.IsConnected && plc.Name == "PLC_1") { int ProdNGQty = plc.ReadInt16(strAddr); return ProdNGQty; } } } catch (Exception ex) { MessageBox.Show(ex.Message, "系统异常", MessageBoxButtons.OK, MessageBoxIcon.Hand); } return 0; } #endregion #region 5、设备状态 /// /// 定时获取设备状态 /// /// /// int OldStatues = int.TryParse(IniConfigHelper.ReadIniData("上一次设备状态", "DeviceStatus", "1", CommonMethods.strDeviceStatusPath), out var result) ? result : 0; //退出程序前设备状态 string strGuidAlarm = IniConfigHelper.ReadIniData("上一次Guid", "DeviceGuid", Guid.NewGuid().ToString(), CommonMethods.strDeviceStatusPath);//退出程序前Guid DateTime StatuesBeginTime = DateTime.Now; // DateTime.Now.AddMinutes(-6); bool run_AlarmMark = false;//特殊情况1标志(运行状态) bool run_IdleMark = false;//特殊情况2标志(闲置状态) bool isUpIdleAlarm = false;//闲置状态报警信息上传标志 List idleAlarmData = new List();//闲置状态的报警信息 private async void ShowStateEven_Run() { while (!_cts.Token.IsCancellationRequested) { try { foreach (var plc in CommonMethods.plcDevices) { if (plc.IsConnected && plc.Name == "PLC_1") { DateTime Date = DateTime.Now; bool isUpload = false; string deviceStatus = string.Empty; string deviceStatusDesc = string.Empty; short NewStatus = plc.ReadInt16("D10"); if (NewStatus != 100) { //特殊情况1:运行->报警->……->运行 if (NewStatus == 3 && OldStatues == 1) { run_AlarmMark = true; }//第一次进入报警状态 if (NewStatus == 1) { run_AlarmMark = false; }//下一次运行状态,置位 if (run_AlarmMark) { NewStatus = 3; }//报警状态后下一次运行状态期间一直是报警状态 //特殊情况2:运行->闲置->……->运行 if (NewStatus == 4 && OldStatues == 1) { run_IdleMark = true; }//第一次进入闲置状态 if (NewStatus == 1) { run_IdleMark = false; }//下一次闲置状态,置位 if (run_IdleMark) { NewStatus = 4; }//闲置状态后下一次运行状态期间一直是闲置状态 //闲置状态结束报警信息上传 if (OldStatues == 4 && NewStatus != 4 && isUpIdleAlarm) { _ = UpIdleDataAsync(idleAlarmData, strGuidAlarm); } //设备状态信号灯变量 CommonMethods.Statues = new List { NewStatus == 1, NewStatus == 2, NewStatus == 3, NewStatus == 4 }; // 状态切换时生成新的 GUID if (NewStatus != OldStatues) { // 从 Alert 切换到非 Alert 状态时,上传报警信息 if (OldStatues == 3 && NewStatus != 3) { ShowAlarmEven_Run(strGuidAlarm, NewStatus); } // 只要状态变更,就生成新的 GUID strGuidAlarm = Guid.NewGuid().ToString(); // 强制生成新的 GUID isUpload = true; OldStatues = NewStatus; StatuesBeginTime = Convert.ToDateTime(Date); IniConfigHelper.WriteIniData("上一次设备状态", "DeviceStatus", NewStatus.ToString(), CommonMethods.strDeviceStatusPath); IniConfigHelper.WriteIniData("上一次Guid", "DeviceGuid", strGuidAlarm, CommonMethods.strDeviceStatusPath); } else { if (Date.Subtract(StatuesBeginTime).TotalSeconds < 5 * 60 && Date.Subtract(StatuesBeginTime).TotalSeconds >= 4 * 60) { isUpload = true; StatuesBeginTime = DateTime.Now; } } // 根据状态设置设备状态和描述 switch (NewStatus) { case 1: deviceStatus = "Run"; deviceStatusDesc = "正常运行"; break; case 2://取消 deviceStatus = "Maintain"; deviceStatusDesc = "停止运行"; break; case 3: deviceStatus = "Alert"; deviceStatusDesc = "故障调试"; // Alert 状态时上传报警信息 ShowAlarmEven_Run(strGuidAlarm, NewStatus); break; case 4: deviceStatus = "Idle"; deviceStatusDesc = "闲置待机"; ShowStuesEven_Run(strGuidAlarm); break; } // 上传设备状态信息 if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected && isUpload && (NewStatus == 1 || NewStatus == 3 || NewStatus == 4)) { var (success, message) = await CommonMethods.hbgMes.UploadEquStatusAsync( CommonMethods.mesConfig.DeviceStatus, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, strGuidAlarm, // RUN 状态时传空 deviceStatus, null ).ConfigureAwait(false); if (success) { //StatuesCount = 0; } } } } } } catch (Exception ex) { LogHelper.Instance.WriteEX(ex); } Thread.Sleep(100); //Task.Delay(100).CountinueWith(); } } #region 触摸屏弹窗 private async void ShowStuesEven_Run(string strGuid) { if (CommonMethods.plcDevices.Count() > 0) { for (int i = 0; i < CommonMethods.plcDevices.Count(); i++) { if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1") { string strCode = ""; string alarmName = ""; short HMIStatus = CommonMethods.plcDevices[i].ReadInt16("D40"); if (HMIStatus != 0) { CommonMethods.AddDataMonitorLog(0, "触发了手动HMI弹窗功能".Translated()); switch (HMIStatus) //注意:B1-D12都为闲置代码,上传代码是只需上传闲置代码 { case 5: strCode = "D1"; alarmName = "设备故障".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 6: strCode = "D2"; alarmName = "设备转型".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 7: strCode = "D3"; alarmName = "工程变更".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 8: strCode = "D4"; alarmName = "MES停机".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 9: strCode = "D5"; alarmName = "品质异常".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 10: strCode = "D6"; alarmName = "配件,物料更换".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 11: strCode = "D7"; alarmName = "来料不良".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 12: strCode = "D8"; alarmName = "待料".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 13: strCode = "D9"; alarmName = "品质检查".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 14: strCode = "D10"; alarmName = "清洁点检".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 15: strCode = "D11"; alarmName = "环境不满足".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; case 16: strCode = "D12"; alarmName = "其它非计划停机".Translated(); CommonMethods.AddDataMonitorLog(0, $"{strCode} {alarmName}"); break; default: break; } if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected) { List alarmObj = new List(); AlarmData alarmData = new AlarmData(); alarmData.AlarmCode = strCode; alarmData.StartTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); alarmData.EndTime = ""; alarmData.AlarmType = "非计划损失"; alarmData.AlarmContent = alarmName; alarmObj.Add(alarmData); //闲置报警上传标志 isUpIdleAlarm = true; idleAlarmData.Add(alarmData); //上传报警 var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, alarmObj, strGuid).ConfigureAwait(false); if (success) CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗状态已上传成功,代码".Translated() + $":{strCode}"); } bool IsSuccess = CommonMethods.plcDevices[i].WriteInt16("D40", 0); if (IsSuccess) CommonMethods.AddDataMonitorLog(0, $"D40已复位0".Translated()); } } } } } #endregion /// /// HMI手动弹窗结束状态上传 /// /// /// /// private async Task UpIdleDataAsync(List idleAlarmDate, string strGuidAlarm) { if (idleAlarmDate.Count == 0) { return; } string strCode = ""; foreach (var item in idleAlarmDate) { item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); strCode = item.AlarmCode + ","; } if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected) { //上传报警 var (success, message) = await CommonMethods.hbgMes.UploadEquAlarmAsync(CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, idleAlarmDate, strGuidAlarm).ConfigureAwait(false); if (success) CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗结束状态已上传成功,代码".Translated() + $":{strCode.TrimEnd(',')}"); } idleAlarmData.Clear(); isUpIdleAlarm = false; } #endregion #region 6、设备报警 //报警表单数据 List listAlarmForm = null; public void GetAlarmData() { //加载PLC报警文件 try { string strFileName = Path.Combine(Environment.CurrentDirectory, "欧姆龙报警寄存器对应表.csv"); listAlarmForm = CSVHelper.ReadCSV(strFileName); } catch (Exception ex) { MessageBox.Show("加载报警文件失败!"); } } /// /// 报警信息读取 /// /// /// List listOld = new List(); List listNew = new List(); private void ShowAlarmEven_Run(string strGuidAlarm,int MachineState) { //if (!CommonMethods.IsRun || !CommonMethods.plcDevices[0].IsConnected) //{ // return; //} bool boolIsConnected = false; if (CommonMethods.plcDevices.Count() > 0) { for (int i = 0; i < CommonMethods.plcDevices.Count(); i++) { if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1") { boolIsConnected = true; break; } } } if (!CommonMethods.IsRun || !boolIsConnected) { return; } try { // 1. 读取 PLC 报警信息 List alarmStatusList = ReadAlarmStatusFromPLC(); if (alarmStatusList == null || listAlarmForm == null)//true { return; } // 2. 解析报警信息并生成新的报警列表 List newAlarms = ParseAlarmStatus(alarmStatusList, strGuidAlarm); if (newAlarms.Count > 0) { // 3. 检测新报警并添加到旧报警列表,上传第一次报警信息 DetectNewAlarmsAsync(newAlarms); // 4. 文字显示新报警信息 DisplayAlarmInfo(newAlarms); } // 5. 检测已取消的报警并处理 List canceledAlarms = DetectCanceledAlarms(newAlarms); if (canceledAlarms.Count > 0) { // 6. 记录已取消的报警信息(保存本地) LogCanceledAlarms(canceledAlarms); // 7. 上传已取消的报警信息(报警结束上传一次) UploadCanceledAlarmsAsync(canceledAlarms, strGuidAlarm); } if (MachineState != 3 && listOld.Count > 0) //设备状态变为非报警并旧报警仍有报警。旧报警上传并清空 { List tempOldAlarmList = new List(); foreach (var item in listOld) { item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //所有报警全部结束时间赋值 tempOldAlarmList.Add(item); } // 6. 记录已取消的报警信息(保存本地) LogCanceledAlarms(tempOldAlarmList); // 7. 上传已取消的报警信息(报警结束上传一次) UploadCanceledAlarmsAsync(tempOldAlarmList, strGuidAlarm); listOld.Clear(); } } catch (Exception ex) { LogHelper.Instance.WriteEX(ex); } } /// /// 从 PLC 读取报警状态 /// private List ReadAlarmStatusFromPLC() { string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); // 读取足够的字节覆盖所有可能的报警地址 int startAddr = 10010; int endAddr = 10519; // 根据实际最大地址调整 int wordCount = endAddr - startAddr + 1; int byteCount = wordCount * 2; // 每个字2字节 if (CommonMethods.plcDevices.Count() > 0) { for (int i = 0; i < CommonMethods.plcDevices.Count(); i++) { if (CommonMethods.plcDevices[i].IsConnected && CommonMethods.plcDevices[i].Name == "PLC_1") { JYResult resByte = CommonMethods.plcDevices[i].ReadValue>($"D{startAddr}", DataType.ArrByte, (ushort)byteCount); if (resByte.IsSuccess && resByte.Content != null) { return PLCAlarmParseHelper.OmronFinsByte2Status(startAddr, resByte.Content); } } } } return null; } /// /// 解析报警状态并生成新的报警列表 /// private List ParseAlarmStatus(List alarmStatusList, string strGuidAlarm) { string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); var alarmTypeMap = new Dictionary // 定义报警类型映射字典 { {'1', "停机报警"}, {'2', "非停机报警"}, }; switch (LanguageManager.LanguageCode) { case "en"://英语报警信息 return alarmStatusList .Where(status => status.Status) .Join( listAlarmForm, status => status.PLCAdress, form => form.PLCAdress, (status, form) => { var alarmContent = string.IsNullOrEmpty(form.EnAlarmContent) ? status.PLCAdress : form.EnAlarmContent; string alarmType = "停机报警"; // 默认值 if (form.AlarmCode?.Length >= 3) { char thirdChar = form.AlarmCode[2]; // 尝试从字典获取对应的值 if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType)) { alarmType = mappedType; } } return new AlarmData { AlarmGuid = strGuidAlarm, PLCAdress = status.PLCAdress, AlarmCode = form.AlarmCode, AlarmContent = alarmContent, AlarmType = alarmType, AlarmDesc = alarmContent, AlarmState = "1", StartTime = Date, EndTime = "", Flag = 0 }; }) .ToList(); case "hu"://匈牙利语报警信息 return alarmStatusList .Where(status => status.Status) .Join( listAlarmForm, status => status.PLCAdress, form => form.PLCAdress, (status, form) => { var alarmContent = string.IsNullOrEmpty(form.HuAlarmContent) ? status.PLCAdress : form.HuAlarmContent; string alarmType = "停机报警"; // 默认值 if (form.AlarmCode?.Length >= 3) { char thirdChar = form.AlarmCode[2]; // 尝试从字典获取对应的值 if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType)) { alarmType = mappedType; } } return new AlarmData { AlarmGuid = strGuidAlarm, PLCAdress = status.PLCAdress, AlarmCode = form.AlarmCode, AlarmContent = alarmContent, AlarmType = alarmType, AlarmDesc = alarmContent, AlarmState = "1", StartTime = Date, EndTime = "", Flag = 0 }; }) .ToList(); case "ms"://马来语报警信息 return alarmStatusList .Where(status => status.Status) .Join( listAlarmForm, status => status.PLCAdress, form => form.PLCAdress, (status, form) => { var alarmContent = string.IsNullOrEmpty(form.MsAlarmContent) ? status.PLCAdress : form.MsAlarmContent; string alarmType = "停机报警"; // 默认值 if (form.AlarmCode?.Length >= 3) { char thirdChar = form.AlarmCode[2]; // 尝试从字典获取对应的值 if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType)) { alarmType = mappedType; } } return new AlarmData { AlarmGuid = strGuidAlarm, PLCAdress = status.PLCAdress, AlarmCode = form.AlarmCode, AlarmContent = alarmContent, AlarmType = alarmType, AlarmDesc = alarmContent, AlarmState = "1", StartTime = Date, EndTime = "", Flag = 0 }; }) .ToList(); case "zh": default: return alarmStatusList .Where(status => status.Status) .Join( listAlarmForm, status => status.PLCAdress, form => form.PLCAdress, (status, form) => { var alarmContent = string.IsNullOrEmpty(form.AlarmContent) ? status.PLCAdress : form.AlarmContent; string alarmType = "停机报警"; // 默认值 if (form.AlarmCode?.Length >= 3) { char thirdChar = form.AlarmCode[2]; // 尝试从字典获取对应的值 if (alarmTypeMap.TryGetValue(thirdChar, out string mappedType)) { alarmType = mappedType; } } return new AlarmData { AlarmGuid = strGuidAlarm, PLCAdress = status.PLCAdress, AlarmCode = form.AlarmCode, AlarmContent = alarmContent, AlarmType = alarmType, AlarmDesc = alarmContent, AlarmState = "1", StartTime = Date, EndTime = "", Flag = 0 }; }) .ToList(); } } private HashSet _allUploadedAlarmContents = new HashSet(); private void DetectNewAlarmsAsync(List newAlarms) { List tempAlarmList = new List(); foreach (var alarm in newAlarms) { if (!listOld.Any(o => o.PLCAdress == alarm.PLCAdress)) { listOld.Add(alarm); tempAlarmList.Add(alarm); //把新报警保存到临时表中 } } if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected) { CommonMethods.hbgMes.UploadEquAlarmAsync( CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, tempAlarmList, strGuidAlarm ); } } /// /// 显示报警信息 /// private void DisplayAlarmInfo(List newAlarms) { StringBuilder sb = new StringBuilder(); newAlarms.ForEach(r => sb.Append($"{r.AlarmContent} ")); if (sb.Length > 0) { CommonMethods.SlideAlarmText = sb.ToString().Substring(0, sb.Length - 10); } } /// /// 检测已取消的报警 /// private List DetectCanceledAlarms(List newAlarms) { var canceledAlarms = new List(); var listOldAlarm = new List(listOld); foreach (var item in listOldAlarm) { if (!newAlarms.Any(n => n.PLCAdress == item.PLCAdress)) { item.EndTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); canceledAlarms.Add(item); listOld.Remove(item); } } return canceledAlarms; } /// /// 记录已取消的报警信息 /// private void LogCanceledAlarms(List canceledAlarms) { var nowDate = DateTime.Now; string basePath = $@"{CommonMethods.strAlarmLogspath}\{nowDate.Year}\{nowDate.Month}"; string fileName = $@"{nowDate.Day}.csv"; string title = "GUID,报警地址,故障代码,报警内容,开始时间,结束时间,是否停机"; string alarmContent = string.Join("\n", canceledAlarms.Select(m1 => $"{m1.AlarmGuid},{m1.PLCAdress},{m1.AlarmCode},{m1.AlarmContent},{m1.StartTime},{m1.EndTime},{m1.AlarmType}")); CSVHelper.WriterCSV(basePath, fileName, title, alarmContent); foreach (var m1 in canceledAlarms) { var alarm = new SysLog { InsertTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), LogType = "报警日志", Note = m1.AlarmContent == "" ? m1.PLCAdress : m1.AlarmContent, AlarmType = "错误", Operater = CommonMethods.currentAdmin.LoginName, VarName = m1.PLCAdress, AlarmSet = "", AlarmValue = "1", }; // 显示报警信息 CommonMethods.AddAlarmLog(alarm);//存报警信息 // 报警top10保存 SaveAlarmCount(canceledAlarms); } } /// /// 报警结束上传 /// /// /// private void UploadCanceledAlarmsAsync(List canceledAlarms, string strGuidAlarm) { if (CommonMethods.sysConfig.MesModeSwitching) { CommonMethods.hbgMes.UploadEquAlarmAsync( CommonMethods.mesConfig.DeviceAlarm, CommonMethods.mesConfig.siteCode, CommonMethods.mesConfig.lineCode, CommonMethods.mesConfig.equipNum, CommonMethods.mesConfig.MaterialCode, CommonMethods.mesConfig.mesUserName, canceledAlarms, strGuidAlarm ); } } /// /// 报警Top10保存 /// private async Task SaveAlarmCount(List alarmDatas) { bool ISAdd = false; var recordToUpdate = new AlarmCountEntry(); DateTime today = DateTime.Today; DateTime upTime = DateTime.Now; var listAlarmStatistics = CommonMethods.db.QueryWhereList(it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today); List fd = new List(); if (listAlarmStatistics != null) { alarmDatas.ForEach(async r => { recordToUpdate = listAlarmStatistics.FirstOrDefault(x => x.PLCAdress.Trim() == r.PLCAdress); if (recordToUpdate != null) { await CommonMethods.db.UpdateAsync(it => it.AlarmCount == it.AlarmCount + 1, it => it.PLCAdress == r.PLCAdress && Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today).ConfigureAwait(false); } else { ISAdd = true; AlarmCountEntry alarm = new AlarmCountEntry(); alarm.PLCAdress = r.PLCAdress; alarm.AlarmContent = r.AlarmContent; alarm.AlarmCount = 1; alarm.Class = CommonMethods.strClass; alarm.AlarmTime = upTime; fd.Add(alarm); } }); if (ISAdd) { bool res = await CommonMethods.db.AddReturnBoolAsync(fd).ConfigureAwait(false); ISAdd = false; } } // 显示报警信息方法已存数据库,报警TOP10就插入新增了 //else //{ // foreach (var m in listNew) // { // AlarmCountEntry alarm = new AlarmCountEntry(); // alarm.PLCAdress = m.PLCAdress; // alarm.AlarmContent = m.AlarmContent; // alarm.AlarmCount = 1; // alarm.Class = CommonMethods.strClass; // alarm.AlarmTime = today; // fd.Add(alarm); // } // await CommonMethods.db.AddReturnBoolAsync(fd); //} await GetASqlAlarmData(DateTime.Today, CommonMethods.strClass).ConfigureAwait(false); } /// /// 读取数据库报警信息 /// /// /// public async Task GetASqlAlarmData(DateTime today, string strClass) { try { List chartAlarms = new List(); List AlarmDatas = null; List List_X = new List(); List List_Y = new List(); DateTime baiClassStart = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 8:30:00")); DateTime baiClassEnd = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 20:29:59")); DateTime yeClassStart = Convert.ToDateTime(DateTime.Now.ToString("yyyy-MM-dd 20:30:00")); DateTime yeClassEnd = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd 8:29:59")); if (strClass == "全天".Translated()) { AlarmDatas = await CommonMethods.db.QueryListAsync("AlarmCount", 10, it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today).ConfigureAwait(false); } else if (strClass == "白班".Translated()) { AlarmDatas = await CommonMethods.db.QueryListAsync("AlarmCount", 10, it => it.AlarmTime >= baiClassStart && it.AlarmTime <= baiClassEnd && it.Class == strClass).ConfigureAwait(false); } else { AlarmDatas = await CommonMethods.db.QueryListAsync("AlarmCount", 10, it => it.AlarmTime >= yeClassStart && it.AlarmTime <= yeClassEnd && it.Class == strClass).ConfigureAwait(false); } if (AlarmDatas != null && AlarmDatas.Count > 0) { for (int i = 0; i < AlarmDatas.Count; i++) { chartAlarms.Add(new ChartDataType() { DataType = AlarmDatas[i].AlarmContent, DataCount = (int)AlarmDatas[i].AlarmCount }); } CommonMethods.Lst_AlarmTop10_Qty = chartAlarms; } else { CommonMethods.Lst_AlarmTop10_Qty = null; } } catch (Exception) { } } #endregion #region 配置下发给PLC public async Task SendConfigToPLCAsync(PlcReadWriteBase plc) { try { // 给物流线PLC下发 生产型号 和 本机架各垛位的档位 if (plc.IsConnected && plc.Name == "PLC_2") { await SendConfigToWuliuPLCAsync(plc); } else if (plc.IsConnected && plc.Name == "PLC_1") { await SendConfigToZuPanPLCAsync(plc); } } catch (Exception ex) { CommonMethods.AddLog(true, "PC写入PLC配置异常".Translated() + ":" + ex.Message); LogHelper.Instance.WriteEX(ex); } } private async Task SendConfigToWuliuPLCAsync(PlcReadWriteBase plc) { string plc_addr = CommonMethods.mesConfig.GetWuliuModelAddr(); if (plc_addr == null || String.IsNullOrEmpty(plc_addr)) { CommonMethods.AddDataMonitorLog(2, $"机架号不识别,无对应机架号的PLC生产型号地址({plc_addr})"); return; } int model = CommonMethods.mesConfig.GetModel(CommonMethods.mesConfig.ModelName); JYResult result = await Task.Run(() => plc.WriteValue(plc_addr, (short)model, DataType.Short)).ConfigureAwait(false); if (result.IsSuccess) { CommonMethods.AddDataMonitorLog(1, $"Write_WuLiu_PLC_model({plc_addr})_{model}_Successful"); } else { CommonMethods.AddDataMonitorLog(2, $"Write_Wuliu_PLC_model({plc_addr})_{model}_Failed"); } Dictionary plc_addrDict = CommonMethods.mesConfig.GetWuliuGradeAddr(); if (plc_addrDict == null || plc_addrDict.Count == 0) { CommonMethods.AddDataMonitorLog(2, $"机架号不识别或者垛位号错误,无对应的PLC分档地址({plc_addr})"); return; } foreach (var kvp in plc_addrDict) { if (kvp.Value == null || String.IsNullOrEmpty(kvp.Value)) { continue; } int stationNo = kvp.Key; string plcAddress = kvp.Value; string strGrade = MesConfig.GetLocGrade(stationNo, CommonMethods.curFdConfig); int grade = CommonMethods.GetGradeNo(strGrade); result = await Task.Run(() => plc.WriteValue(plcAddress, (short)grade, DataType.Short)).ConfigureAwait(false); if (result.IsSuccess) { CommonMethods.AddDataMonitorLog(1, $"Write_PLC_grade({plcAddress})_{strGrade}({grade})_Successful"); } else { CommonMethods.AddDataMonitorLog(2, $"Write_PLC_grade({plcAddress})_{strGrade}({grade})_Failed"); } } } private async Task SendConfigToZuPanPLCAsync(PlcReadWriteBase plc) { var writeTasks = new List(); Dictionary loc_plcAddr = CommonMethods.mesConfig.gw_loc_plcAddr[Convert.ToInt16(CommonMethods.sysConfig.StationNo)]; FDConfig fdconfig = CommonMethods.curFdConfig; foreach (KeyValuePair locKvp in loc_plcAddr) { int stationNo = locKvp.Key; string plcAddr = locKvp.Value; string gradeStr = MesConfig.GetLocGrade(stationNo, fdconfig); try { // 转换等级 int gradeValue = CommonMethods.GetGradeNo(gradeStr); string logBase = $"LOC{stationNo} Write_Zupan_PLC({plcAddr}:等级{gradeStr},转换PLC值{gradeValue})"; JYResult result = await Task.Run(() => plc.WriteValue(plcAddr, (short)gradeValue, DataType.Short)).ConfigureAwait(false); if (result.IsSuccess) { CommonMethods.AddDataMonitorLog(1, $"{logBase} 成功"); CommonMethods.AddOPLog(true, $"{logBase} 成功"); } else { string failMsg = $"{logBase} 失败,错误:{result.Message ?? "无详细信息"}"; CommonMethods.AddDataMonitorLog(2, failMsg); CommonMethods.AddOPLog(false, failMsg); } } catch (Exception ex) { string exMsg = $"LOC{stationNo} Write_Zupan_PLC({plcAddr}) 异常:{ex.Message},堆栈:{ex.StackTrace}"; CommonMethods.AddDataMonitorLog(3, exMsg); CommonMethods.AddOPLog(false, exMsg); } } } #endregion #region 下发权限给PLC /// /// 下发权限到PLC /// public async Task SendUserRightToPLCAsync(PlcReadWriteBase plc) { if (!plc.IsConnected || plc.Name != "PLC_1") { if(plc.Name == "PLC_1") CommonMethods.AddDataMonitorLog(2, $"组盘PLC连接中断,无法给PLC下发用户名和权限"); return; } ushort quanxian = 1; switch (CommonMethods.currentAdmin.LoginLevel) { case "操作员": quanxian = 1; break; case "工程师": quanxian = 2; break; case "管理员": quanxian = 3; break; default: break; } Dictionary plc_addrs = CommonMethods.mesConfig.GetHMIUserRightAddr(); if (!string.IsNullOrEmpty(plc_addrs["right"])) { JYResult result = await Task.Run(() => plc.WriteValue(plc_addrs["right"], (short)quanxian, DataType.Short)); if (result.IsSuccess) { CommonMethods.AddDataMonitorLog(1, $"Write_PLC_permission_{quanxian}({plc_addrs["right"]})_Successful"); } else { CommonMethods.AddDataMonitorLog(2, $"Write_PLC_permission_{quanxian}({plc_addrs["right"]})_Failed"); } } if (!string.IsNullOrEmpty(plc_addrs["user"])) { const int BARCODE_SIZE = 40; string loginName = string.Concat(CommonMethods.currentAdmin.LoginName + GetString_0(BARCODE_SIZE - CommonMethods.currentAdmin.LoginName.Length)); byte[] codeBytes = Encoding.Default.GetBytes(loginName).ByteReverse(); JYResult result2 = await Task.Run(() => plc.WriteValue(plc_addrs["user"], codeBytes, DataType.ArrByte)); if (result2.IsSuccess) { CommonMethods.AddDataMonitorLog(1, $"Write_PLC_username_{loginName}({plc_addrs["user"]})_Successful\n"); } else { CommonMethods.AddDataMonitorLog(2, $"Write_PLC_username_{loginName}({plc_addrs["user"]})_Failed"); } } } #endregion #region 获取白夜班次 bool qiehuan = false; private void SetClass() { try { var nowDate = DateTime.Now; string strClass = ""; DateTime time1 = Convert.ToDateTime($"{nowDate.ToShortDateString()} {CommonMethods.ChangeClassTime}"); DateTime time2 = Convert.ToDateTime($"{nowDate.ToShortDateString()} {Convert.ToDateTime(CommonMethods.ChangeClassTime).AddHours(12).ToString("HH:mm:ss")}"); if (nowDate <= time1 && nowDate >= time2) { foreach (var plc in CommonMethods.plcDevices) { if (_plcDeviceMapping.TryGetValue(plc, out var plcDevices)) { foreach (var plcDevice in plcDevices) { if (plcDevice.IsConnected) { if (!qiehuan) { if (!SetPlcTime(plc)) { CommonMethods.AddLog(true, $"PC写入PLC时间失败".Translated()); } else { qiehuan = true; CommonMethods.AddLog(false, "PC写入PLC时间成功".Translated()); } } } } strClass = "白班".Translated(); } } } else { qiehuan = false; strClass = "夜班".Translated(); } CommonMethods.strClass = strClass; } catch (Exception ex) { CommonMethods.AddLog(true, "PC写入PLC时间异常".Translated() + ":" + ex.Message); LogHelper.Instance.WriteEX(ex); } } #endregion #region 写入PLC时间 /// /// 写当前时间给PLC /// private bool SetPlcTime(PlcReadWriteBase plcDevice) { var nowDate = DateTime.Now; string yearStr = nowDate.Year.ToString(); bool IsSuccess = false; // 为防止PLC时间出现偏差,每次自动运行的时候写PLC系统时间 short[] writeArr = new short[] { Convert.ToInt16(nowDate.Year),//D70 Convert.ToInt16(nowDate.Month),//D71 Convert.ToInt16(nowDate.Day),//D72 Convert.ToInt16(nowDate.Hour),//D73 Convert.ToInt16(nowDate.Minute),//D74 Convert.ToInt16(nowDate.Second),//D75 (short)1 //D76 }; if (plcDevice.IsConnected) { JYResult result = plcDevice.WriteValue("D70", writeArr, DataType.ArrShort); if (result.IsSuccess) { IsSuccess = true; } } return IsSuccess; } #endregion #region 9、IDisposable 实现 // 如果需要在StopAsync中也显示进度,可以添加一个进度回调 //private Task processTask; public async Task StopAsync(IProgress progress = null) { progress?.Report("正在停止控制中心..."); // 停止健康监控 if (_healthMonitorTimer != null) { _healthMonitorTimer.Stop(); _healthMonitorTimer.Dispose(); } // 取消所有操作 if (_cts != null) { _cts.Cancel(); } _isWorkerRunning = false; _taskEvent.Set(); // 等待心跳任务完成 if (_heartbeatTask != null) { progress?.Report("停止心跳任务..."); try { // 使用Wait带超时 _heartbeatTask.Wait(5000); } catch (AggregateException) { } catch { } } // 停止所有PLC读取线程 progress?.Report("停止PLC读取线程..."); StopAndCleanOldThreads(); // 等待工作线程完成 if (_workerThread != null && _workerThread.IsAlive) { progress?.Report("停止工作线程..."); _workerThread.Join(5000); } // 清理队列 int remainingTasks = _taskQueue.Count; if (remainingTasks > 0) { progress?.Report($"清理剩余{remainingTasks}个任务..."); while (_taskQueue.TryDequeue(out _)) { } } progress?.Report("控制中心已停止"); } public void Dispose() { if (_isDisposed) return; _isDisposed = true; Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _isWorkerRunning = false; _taskEvent.Set(); _taskEvent.Dispose(); if (_cts != null) { _cts.Cancel(); _cts.Dispose(); } if (_healthMonitorTimer != null) { _healthMonitorTimer.Stop(); _healthMonitorTimer.Dispose(); } _dbSemaphore?.Dispose(); _plcWriteSemaphore?.Dispose(); if (_timerRefreshData != null) { _timerRefreshData.Stop(); _timerRefreshData.Dispose(); } foreach (var plc in CommonMethods.plcDevices) { try { plc.Disconnect(); } catch { } } CommonMethods.plcDevices?.Clear(); _plcDeviceMapping?.Clear(); _plcAlarmReaders?.Clear(); CommonMethods.plcStatuses?.Clear(); _groupStatus?.Clear(); _stationFunctions?.Clear(); } } #endregion #region 7、辅助方法 private static PLCType GetPLCTypeEnum(string plcTypeString) { if (Enum.TryParse(plcTypeString, out PLCType plcType)) { return plcType; } throw new ArgumentException($"PLC 类型无效: {plcTypeString}"); } #endregion #region 诊断日志 private void StartDiagnosticMonitor() { Thread monitorThread = new Thread(() => { while (!_cts.Token.IsCancellationRequested) { try { Thread.Sleep(60000); // 每分钟输出一次报告 // 检查关键工站是否卡死 CheckStuckStation("物流线料盘进站"); CheckStuckStation("物流线料盘来料"); CheckStuckStation("电池进站"); // 输出诊断报告 var report = StationDiagnostics.GetDiagnosticReport(); LogHelper.Instance.WriteLog(report); // 输出系统状态 LogHelper.Instance.WriteLog($"【系统状态】队列大小={_taskQueue.Count} | 工作线程运行={_isWorkerRunning} | 总处理={_totalProcessedCount} | 总拒绝={_totalRejectedCount}"); // 检查PLC连接状态 foreach (var plc in CommonMethods.plcDevices) { if (plc != null) { LogHelper.Instance.WriteLog($"【PLC状态】{plc.Name} | IsConnected={plc.IsConnected}"); } } } catch (Exception ex) { LogHelper.Instance.WriteError($"监控线程异常:{ex.Message}"); } } }); monitorThread.IsBackground = true; monitorThread.Name = "DiagnosticMonitor"; monitorThread.Start(); } public string GetStationStatus(string stationName) { lock (_statusLock) { var status = _groupStatus.ContainsKey(stationName) ? _groupStatus[stationName] : -1; var queueSize = _taskQueue.Count; var workerAlive = _workerThread?.IsAlive ?? false; return $"工站:{stationName} 状态:{status} 队列:{queueSize} 工作线程存活:{workerAlive}"; } } // 手动重置卡死的工站 public void ResetStation(string stationName) { lock (_statusLock) { if (_groupStatus.ContainsKey(stationName)) { _groupStatus[stationName] = 0; LogHelper.Instance.WriteLog($"手动重置工站: {stationName}"); } } } private void CheckStuckStation(string stationName) { // 检查是否有工站卡在处理中状态超过30秒 lock (_statusLock) { if (_groupStatus.ContainsKey(stationName) && _groupStatus[stationName] == 1) { LogHelper.Instance.WriteError($"【诊断-卡死检测】{stationName} | 状态为1(处理中)已超过30秒,可能已卡死"); } } } public class StationDiagnostics { private static readonly object _lock = new object(); private static Dictionary> _triggerHistory = new Dictionary>(); private static Dictionary _timeoutCount = new Dictionary(); private static Dictionary _lastTriggerTime = new Dictionary(); private static Dictionary _lastCompleteTime = new Dictionary(); /// /// 记录触发信号 /// public static void LogTrigger(string stationName, short trigValue, int status) { lock (_lock) { if (!_triggerHistory.ContainsKey(stationName)) _triggerHistory[stationName] = new Queue(); _triggerHistory[stationName].Enqueue(DateTime.Now); // 只保留最近100条记录 while (_triggerHistory[stationName].Count > 100) _triggerHistory[stationName].Dequeue(); _lastTriggerTime[stationName] = DateTime.Now; LogHelper.Instance.WriteLog($"【诊断-触发】{stationName} | Trig={trigValue} | Status={status} | 队列长度={_triggerHistory[stationName].Count}"); } } /// /// 记录任务入队 /// public static void LogTaskEnqueued(string stationName, int queueSize) { lock (_lock) { LogHelper.Instance.WriteLog($"【诊断-入队】{stationName} | 当前队列大小={queueSize}"); } } /// /// 记录任务开始执行 /// public static void LogTaskStart(string stationName) { lock (_lock) { var now = DateTime.Now; if (_lastTriggerTime.ContainsKey(stationName)) { var waitTime = (now - _lastTriggerTime[stationName]).TotalMilliseconds; LogHelper.Instance.WriteLog($"【诊断-执行开始】{stationName} | 等待时间={waitTime:F0}ms"); } else { LogHelper.Instance.WriteLog($"【诊断-执行开始】{stationName}"); } } } /// /// 记录任务完成 /// public static void LogTaskComplete(string stationName, bool success, string errorMsg = "") { lock (_lock) { _lastCompleteTime[stationName] = DateTime.Now; LogHelper.Instance.WriteLog($"【诊断-执行完成】{stationName} | 成功={success} | 错误={errorMsg}"); } } /// /// 记录超时 /// public static void LogTimeout(string stationName, int timeoutSeconds) { lock (_lock) { if (!_timeoutCount.ContainsKey(stationName)) _timeoutCount[stationName] = 0; _timeoutCount[stationName]++; LogHelper.Instance.WriteError($"【诊断-超时】{stationName} | 超时时间={timeoutSeconds}秒 | 累计超时次数={_timeoutCount[stationName]}"); } } /// /// 获取诊断报告 /// public static string GetDiagnosticReport() { lock (_lock) { var sb = new StringBuilder(); sb.AppendLine("=== 工站诊断报告 ==="); foreach (var station in _triggerHistory.Keys) { sb.AppendLine($"工站: {station}"); sb.AppendLine($" 最近触发时间: {(_lastTriggerTime.ContainsKey(station) ? _lastTriggerTime[station].ToString() : "无")}"); sb.AppendLine($" 最近完成时间: {(_lastCompleteTime.ContainsKey(station) ? _lastCompleteTime[station].ToString() : "无")}"); sb.AppendLine($" 触发次数(最近100): {_triggerHistory[station].Count}"); sb.AppendLine($" 超时次数: {(_timeoutCount.ContainsKey(station) ? _timeoutCount[station] : 0)}"); if (_lastTriggerTime.ContainsKey(station) && _lastCompleteTime.ContainsKey(station)) { var processingTime = _lastCompleteTime[station] - _lastTriggerTime[station]; sb.AppendLine($" 最近处理耗时: {processingTime.TotalMilliseconds:F0}ms"); } sb.AppendLine(); } return sb.ToString(); } } } #endregion } }