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.Generic; using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.NetworkInformation; 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 1、变量定义 private static readonly object uiLock = new object(); private static ControlCenter _instance; public static ControlCenter Instance { get { lock (uiLock) { return _instance ??= new ControlCenter(); } } } private readonly Dictionary groups = new Dictionary(); private Dictionary, PlcGroup, Task>> stationFunctions; //private Dictionary, PlcGroup>> stationFunctions; private readonly Dictionary> plcDeviceMapping = new Dictionary>(); private readonly Dictionary groupTasks = new Dictionary(); private readonly Dictionary plcAlarmReaders = new Dictionary(); public CancellationTokenSource _cts; private Task _heartbeatTask; private const int HeartbeatInterval = 1000; // 1 second private Dictionary group_status = new Dictionary(); /// /// 定时器-定时刷新状态和数据 /// private System.Timers.Timer Timer_RefreshData; private readonly string configFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config"); #endregion #region 2、初始化函数 /// /// 构造函数 /// private ControlCenter() { _cts = new CancellationTokenSource(); if (CommonMethods.plcDevices == null) { CommonMethods.plcDevices = new List(); } InitializeDevicesFromExcel(); GetAlarmData(); InitFunc(); //initPrint();//打印机初始化 Timer_RefreshData = new System.Timers.Timer(1000); Timer_RefreshData.Elapsed += Timer_RefreshData_Elapsed; Timer_RefreshData.Start(); // 轴位置监控 //Task.Run(() => { ShowAxleEven_Run(); }); // 电机位置监控 //Task.Run(() => { ShowMotorEven_Run(); }); //Mes心跳监控 Task.Run(new Action(() => { UpdateMesHeartBeat(); })); //删除30天内日志 //DeleteLog.Start(CommonMethods.strDeletepath);//会把整个ApiLog删除 string[] clrPath = new string[] { CommonMethods.strAlarmLogspath, CommonMethods.strMesLogspath, CommonMethods.strWattrMeterpath, Application.StartupPath + "\\Logs\\" }; short[] seltWay = new short[] { 0, 0, 0, 1 }; DeleteLog.Start(clrPath, seltWay); if (!CommonMethods.IsRun) { CommonMethods.ResetEventRecv.Reset(); } } /// /// 初始化 工位方法,添加到字典里面 /// private void InitFunc() { stationFunctions = new Dictionary, PlcGroup, Task>> { ["包装箱进站"] = FeedingStation, // 线头扫描,包装箱进站 ["中部扫描"] = FeedingStationB, // 中部扫描 ["尾部扫描"] = FeedingStationC, // 尾部扫描 //["箱唛标签打印"] = PrintContainerStation, // 箱唛打印 //["栈板标签打印"] = PrintPalletStation, // 栈板打印,O2不打印 //["3S标签打印"] = Print3SStation, // 3S打印 在线网站拿数据的,贴标打印机自行打印 //["6J标签打印"] = Print6JStation, // 6J打印 //["绑定客户标签"] = BindingLabelStation, // 绑定客户标签 ["包装箱出站"] = BlankingStation, // 包装箱出站 ["中转数据"] = TransferStation, // 中转数据 }; } /// /// 启动心跳及工位读取线程 /// /// public async Task StartAsync() { CreatePLCThreads(); _heartbeatTask = StartHeartbeatCheck(_cts.Token); await Task.CompletedTask; } /// /// 加载配置文件 /// private void InitializeDevicesFromExcel() { CommonMethods.AddDataMonitorLog(1, "开始加载配置参数……".Translated()); string deviceConfigPath = Path.Combine(configFolderPath, "Device.xlsx"); string groupConfigPath = Path.Combine(configFolderPath, "Group.xlsx"); string variableConfigPath = Path.Combine(configFolderPath, "Variable.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); deviceConfig.Cts = new CancellationTokenSource(); deviceConfig.GroupList = groupConfigs .Where(g => g.IsActive && g.PlcNum == deviceConfig.PlcNum) .ToList(); foreach (var group in deviceConfig.GroupList) { group.VarList = variableConfigs .Where(v => v.GroupName == group.GroupName && v.PlcNum == deviceConfig.PlcNum) .ToList(); group_status[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 readonly Dictionary groupThreads = new Dictionary(); private readonly object lockObject = new object(); private void CreatePLCThreads() { for (int i = 0; i < CommonMethods.plcDevices.Count; i++) { var plc = CommonMethods.plcDevices[i]; if (plcDeviceMapping.TryGetValue(plc, out var plcDevices)) { foreach (var plcDevice in plcDevices) { foreach (var plcGroup in plcDevice.GroupList.Where(g => g.IsActive)) { string threadKey = $"{plcDevice.PlcNum}-{plcGroup.GroupName}"; var parameter = (group: plcGroup, plcIndex: i); Thread thread = new Thread(new ParameterizedThreadStart(ReadPLCWorkModel)); thread.IsBackground = true; thread.Priority = ThreadPriority.Highest; lock (lockObject) { groupThreads[threadKey] = thread; } thread.Start(parameter); } } } } } #endregion #region 循环读取PLC工位状态 /// /// 读取PLC循环工位 /// /// /// private void ReadPLCWorkModel(object parameter) { if (parameter is ValueTuple tuple) { var (group, plcIndex) = tuple; while (!_cts.Token.IsCancellationRequested) { if (CommonMethods.IsRun) { if (CommonMethods.plcDevices[plcIndex].IsConnected) { try { JYResult OpenValue = CommonMethods.plcDevices[plcIndex].ReadValue>(group.VarList[0].VarAddress, DataType.Short); if (OpenValue.IsSuccess && OpenValue.Content == 1 && group_status[group.GroupName] == 0) { group_status[group.GroupName] = 1; JYResult resByte = CommonMethods.plcDevices[plcIndex].ReadValue>($"{group.VarList[2].VarAddress}", DataType.ArrByte, Convert.ToUInt16(group.VarList[2].OffsetOrLength)); if (resByte != null && stationFunctions.TryGetValue(group.GroupName, out var action)) { Task.Run(() => action(resByte, group)); } } else if (OpenValue.IsSuccess && OpenValue.Content == 0) // 触发地址被PLC清0后的处理处理 { if(group_status[group.GroupName] != 0) { group_status[group.GroupName] = 0; CommonMethods.AddDataMonitorLog(0, $"{group.GroupName} PLC设置为0,清空状态为0"); } JYResult checkValue = CommonMethods.plcDevices[plcIndex].ReadValue>(group.VarList[1].VarAddress, DataType.Short); if (checkValue.IsSuccess && checkValue.Content != 0) { CommonMethods.plcDevices[plcIndex].WriteValue(group.VarList[1].VarAddress, 0, DataType.Short); LogHelper.Instance.WriteLog($"记录: {group.VarList[1].VarAddress}清0"); } } } catch (Exception ex) { group_status[group.GroupName] = 0; LogHelper.Instance.WriteError($"错误: {group.GroupName} - {ex.Message}"); } } } Thread.Sleep(50); } } } #endregion #region 开始停止联机事件 /// /// 开始联机 /// /// public async Task ConnectAllPlcsAsync() { bool allConnected = true; if (CommonMethods.plcDevices.Count <= 0) { CommonMethods.AddDataMonitorLog(1, "无PLC配置信息".Translated() + "," + "请检查配置文件".Translated()); return allConnected = 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(() => { // 设置一个连接超时 using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10))) // 10秒超时 { try { return plc.Connect(ref msg); } catch (OperationCanceledException) { msg = "连接超时"; return false; } } }).ConfigureAwait(false); deviceConfig.IsConnected = isConnected; if (!deviceConfig.IsConnected) { allConnected = false; LogHelper.Instance.WriteLog($"连接PLC失败: {deviceConfig.PlcNum}, 错误: {msg}"); CommonMethods.AddDataMonitorLog(1, "连接PLC失败".Translated() + $": {deviceConfig.PlcNum}, " + "错误".Translated() + $": {msg}"); } else { await SendRightToPLCAsync(); 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() { foreach (var plc in CommonMethods.plcDevices) { if (plcDeviceMapping.TryGetValue(plc, out var plcDevices)) { foreach (var deviceConfig in plcDevices) { plc.Disconnect(); deviceConfig.IsConnected = false; CommonMethods.AddDataMonitorLog(0, "已断开与PLC".Translated() + $"[{deviceConfig.PlcNum}]" + "的链接".Translated()); } } } } #endregion #endregion #region MES /// /// Mes心跳 /// private void UpdateMesHeartBeat() { Thread.Sleep(100); while (true)//true { try { if (CommonMethods.mesConfig.IsLoginMesOK && CommonMethods.IsRun)//CommonMethods.mesConfig.isUpMes && { if (Ping() == true) { CommonMethods.mesConfig.isConnected = true; } else//Ping失败,提示检查线路。 { CommonMethods.mesConfig.isConnected = false; if (DateTime.Now.Second % 10 == 0) { 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 Ping() { try { string strIP = CommonMethods.mesConfig.MESURLAddressIP; int port = Convert.ToInt32(CommonMethods.mesConfig.MESURLAddressPort); IPAddress ip = IPAddress.Parse(strIP); IPEndPoint _endPoint = new IPEndPoint(ip, port); if (_endPoint.Address == null) { return false; } Ping _ping = new Ping(); int timeout = 1000; PingReply mPingRcv = _ping.Send(_endPoint.Address, timeout); return (mPingRcv.Status == IPStatus.Success) ? true : false; } 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 ShowStateEven_RunAsync(); } } } } } await Task.Delay(HeartbeatInterval, cancellationToken); } } /// /// /// /// /// /// private async Task CheckHeartbeatForDevice(PlcReadWriteBase plc, PlcDevice plcDevice) { if (plcDevice.IsConnected) { JYResult result = await Task.Run(() => plc.ReadValue>(plcDevice.HeartBeat, DataType.Short)); if (result.IsSuccess) { plcDevice.ErrorTimes = 0; JYResult res = await Task.Run(() => plc.WriteValue(plcDevice.HeartBeat, (short)1, DataType.Short)); if (!res.IsSuccess) { plcDevice.IsConnected = false; LogHelper.Instance.WriteLog($"PLC编号:{plcDevice.PlcNum}=心跳写入异常:{res.Message}!!!"); } } else { HandleHeartbeatFailure(plcDevice, result); } } else { await ReconnectDevice(plc, plcDevice); } } private void HandleHeartbeatFailure(PlcDevice plcDevice, JYResult result) { plcDevice.ErrorTimes++; plcDevice.IsConnected = false; if (plcDevice.ErrorTimes >= plcDevice.AllowErrorTimes) { LogHelper.Instance.WriteLog($"编号:[{plcDevice.PlcNum}]的PLC链接断开:{result.Message}"); } } 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 5、报警处理 × //private async Task CheckAlarmsAsync(PlcReadWriteBase plc, string guidAlarm) //{ // if (plc == null) // { // CommonMethods.AddDataMonitorLog(2, "PLC 对象为 null".Translated()); // return; // } // if (!plc.IsConnected) // { // CommonMethods.AddDataMonitorLog(2, "PLC 未连接".Translated()); // return; // } // if (plcAlarmReaders.TryGetValue(plc, out var alarmReader)) // { // try // { // JYResult result = null; // // 使用 Task.Run 和超时机制 // var task = Task.Run(() => // { // try // { // return plc.ReadValue>("D10000", DataType.ArrUshort, 20); // } // catch (Exception ex) // { // CommonMethods.AddDataMonitorLog(2, "PLC 读取过程中发生异常".Translated() + $": {ex.Message}"); // return null; // } // }); // // 设置5秒超时 // if (await Task.WhenAny(task, Task.Delay(5000)) == task) // { // result = await task; // } // else // { // throw new TimeoutException("PLC 读取操作超时"); // } // if (result == null) // { // throw new InvalidOperationException("PLC 读取操作返回空结果"); // } // if (!result.IsSuccess) // { // throw new InvalidOperationException($"PLC 读取操作失败: {result.Message}"); // } // if (result.Content == null) // { // throw new InvalidOperationException("PLC 读取操作返回空内容"); // } // ushort[] alarmData = result.Content; // // 使用 alarmData 进行后续操作 // bool isAlarming = alarmReader.IsPLCAlarm(10000, 20, alarmData); // if (isAlarming) // { // foreach (var alarm in alarmReader.RecordAlarms) // { // CommonMethods.AddDataMonitorLog(1, $"当前报警: 代码 {alarm.AlarmCode}, 内容 {alarm.AlarmInfo}, 开始时间 {alarm.StartTime}"); // } // } // } // catch (Exception ex) // { // CommonMethods.AddDataMonitorLog(2, $"检查报警时发生错误: {ex.Message}"); // } // } // else // { // CommonMethods.AddDataMonitorLog(2, $"未找到 PLC 的报警读取器"); // } //} /// /// 新增报警 /// /// /// //private void AlrmReader_PLCAlarm_New(object sender, RecordAlarmDataArgs e) //{ // Task.Run(() => // { // CommonMethods.alarmManager.AddAlarm(e); // Debug.WriteLine($"新报警: 代码 {e.AlarmCode}, 内容 {e.AlarmInfo}, 日期 {e.AlarmDate}, 开始时间 {e.StartTime:HH:mm:ss}"); // }); //} /// /// 报警结束移除报警 /// /// /// //private void AlrmReader_PLCAlarm_Remove(object sender, RecordAlarmDataArgs e) //{ // Task.Run(() => // { // CommonMethods.alarmManager.RemoveAlarm(e); // Debug.WriteLine($"报警清除: 代码 {e.AlarmCode}, 内容 {e.AlarmInfo}, 日期 {e.AlarmDate}, 结束时间 {DateTime.Now:HH:mm:ss}"); // }); //} #endregion #region 6、设备状态检查 × /// /// 设备状态 /// /// //private async Task ShowStateEven_RunAsync() //{ // try // { // string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); // foreach (var plc in CommonMethods.plcDevices) // { // if (plcDeviceMapping.TryGetValue(plc, out var plcDevices)) // { // foreach (var plcDevice in plcDevices) // { // if (plcDevice.IsConnected) // { // var plcStatus = CommonMethods.plcStatuses[plc]; // bool isUpload = false; // string deviceStatus = string.Empty; // string deviceStatusDesc = string.Empty; // short newStatus = 100; // newStatus = await Task.Run(() => plc.ReadValue>("D10", DataType.Short).Content); // if (newStatus != 100) // { // if (newStatus != (short)plcStatus["OldStatus"]) // { // // 从 Alert 切换到非 Alert 状态时,上传报警信息 // if ((short)plcStatus["OldStatus"] == 3 && newStatus != 3) // { // await CheckAlarmsAsync(plc, (string)plcStatus["GuidAlarm"]); // } // // 只要状态变更,就生成新的 GUID // plcStatus["GuidAlarm"] = Guid.NewGuid().ToString(); // isUpload = true; // plcStatus["StatusCount"] = 0; // plcStatus["OldStatus"] = newStatus; // } // else // { // plcStatus["StatusCount"] = (int)plcStatus["StatusCount"] + 1; // if ((int)plcStatus["StatusCount"] >= 5 * 60) // { // isUpload = true; // plcStatus["StatusCount"] = 0; // } // } // (deviceStatus, deviceStatusDesc) = newStatus switch // { // 1 => ("Run", "正常运行"), // 2 => ("Maintain", "停止运行"), // 3 => ("Alert", "故障调试"), // 4 => ("Idle", "闲置待机"), // _ => (string.Empty, string.Empty) // }; // plcStatus["Status"] = new List { newStatus == 1, newStatus == 2, newStatus == 3, newStatus == 4 }; // CommonMethods.Statues = new List { newStatus == 1, newStatus == 2, newStatus == 3, newStatus == 4 }; // if (newStatus == 3) //报警状态 // { // await CheckAlarmsAsync(plc, (string)plcStatus["GuidAlarm"]); // } // else if (newStatus == 4) //闲置状态 // { // await ShowStuesEven_Run(Date, (string)plcStatus["GuidAlarm"], plc); // } // if (isUpload) // { // CommonMethods.AddDataMonitorLog(1, $"PLC[{plcDevice.PlcNum}]状态: {deviceStatusDesc}"); // } // if (CommonMethods.sysConfig.MesModeSwitching) // { // // 上传设备状态信息 // if (CommonMethods.sysConfig.MesModeSwitching && 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 // ); // if (success) // { // StatuesCount = 0; // } // } // } // } // } // } // } // } // } // catch (Exception ex) // { // await Task.Run(() => LogHelper.Instance.WriteEX(ex)); // } //} /// /// HMI手动触摸屏状态 /// /// /// /// /// //private async Task ShowStuesEven_Run(string Date, string strGuid, PlcReadWriteBase plc) //{ // if (plc.IsConnected) // { // string strCode = ""; // string strText = ""; // short HMIStatus = await Task.Run(() => plc.ReadValue>("D40", DataType.Short).Content); // if (HMIStatus != 0) // { // CommonMethods.AddDataMonitorLog(1, "触发了手动HMI弹窗功能!"); // (strCode, strText) = HMIStatus switch // { // 1 => ("B1", "计划维保"), // 2 => ("B2", "无生产计划"), // 3 => ("B3", "样品、试产"), // 4 => ("B4", "其他计划停机"), // 5 => ("D1", "设备故障"), // 6 => ("D2", "设备转型"), // 7 => ("D3", "工程变更"), // 8 => ("D4", "MES停机"), // 9 => ("D5", "品质异常"), // 10 => ("D6", "配件、物料更换"), // 11 => ("D7", "来料不良"), // 12 => ("D8", "待料"), // 13 => ("D9", "品质检查"), // 14 => ("D10", "清洁点检"), // 15 => ("D11", "环境不满足"), // 16 => ("D12", "其它非计划停机"), // _ => (string.Empty, string.Empty) // }; // if (!string.IsNullOrEmpty(strCode)) // { // CommonMethods.AddDataMonitorLog(1, $"{strCode} {strText}"); // } // if (CommonMethods.sysConfig.MesModeSwitching) // { // List alarmObj = new List(); // AlarmData alarmData = new AlarmData(); // alarmData.AlarmCode = strCode; // alarmData.StartTime = ""; // alarmData.EndTime = ""; // alarmData.AlarmType = "非计划损失"; // alarmData.AlarmContent = strText; // alarmObj.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); // if (success) // CommonMethods.AddDataMonitorLog(0, $"HMI手动弹窗状态已上传成功,代码:{strCode}"); // } // bool IsSuccess = CommonMethods.plcDevices[0].WriteInt16("D40", 0); // if (IsSuccess) // CommonMethods.AddDataMonitorLog(0, $"D40已复位0"); // } // } //} #endregion #region 定时刷新数据 int TotalNum = 0; int TotalNum1 = 0; int Regular = 0; int JetBeat = 0; private void Timer_RefreshData_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { if (Regular >= 300) { Task.Run(() => { UploadEquEnergy();//能耗 //ReUpProducResultParam();//定时补传 }); Regular = 0; } if (TotalNum > 4) { GetEnergyData(); //GetEnergyDataFormPlc(); Refresh12HourProdQty(); GetDefectTypeQty(); ReadPLCComm(); GetASqlAlarmData(DateTime.Today, CommonMethods.strClass); TotalNum = 0; } else if (TotalNum == 2)//设备状态读取 { SetClass(); //变更班次 } Task.Run(() => { ShowStateEven_Run(); }); if (TotalNum1 >= 3600 * 24) { Task.Run(() => { DeleteFileOfLocal(); }); TotalNum1 = 0; } //电能表心跳 //if (jetBeat > 5) //{ // if (!CommonMethods.energyMeter.Connected) // { // CommonMethods.energyMeter.SetTCPParams(IPAddress.Parse("192.168.1.25"), 502); // CommonMethods.energyMeter.Connect(); // } // jetBeat = 0; //} //jetBeat++; TotalNum++; TotalNum1++; Regular++; } #endregion #region 设备综合信息 /// /// 读取PLC通用信息 /// internal void ReadPLCComm() { if (CommonMethods.plcDevices.Count != 0 && CommonMethods.plcDevices[0].IsConnected) { Task.Run(() => { UpTotalCount(null); }); CommonMethods.TotalQty = CommonMethods.plcDevices[0].ReadFloat("D15");//生产总数 CommonMethods.ProOKQty = CommonMethods.plcDevices[0].ReadFloat("D17");//良品总数 CommonMethods.Ppm = CommonMethods.plcDevices[0].ReadFloat("D13"); CommonMethods.JiaDL = Convert.ToDouble(CommonMethods.plcDevices[0].ReadFloat("D11").ToString("F2")); #region 开机时间/运行时间 CommonMethods.PlcOpenAndRunTime = GetList(6, 0); JYResult resByte = CommonMethods.plcDevices[0].ReadValue>($"D50", DataType.ArrByte, 12); if (resByte.IsSuccess && resByte.Content.Length >= 24) { for (int i = 0; i < CommonMethods.PlcOpenAndRunTimeAdress.Count; i++) { CommonMethods.PlcOpenAndRunTime[i] = IntLib.GetIntFromByteArray(resByte.Content, i * 4, DataFormat.CDAB);//ShortLib.GetShortFromByteArray(resByte.Content, i * 4, DataFormat.CDAB); } } #endregion } } #endregion #region 保存12小时产能数据 public int totalNum = 0; // 总数 public int okNum = 0; // 良品数 /// /// 储存数据到数据库 /// /// public void UpTotalCount(int[] DataCount) { 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 = CommonMethods.db.AddReturnBool(mh); 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 = ""; if (flag)//产出 { strAddr = "D" + (1040 + hour); } else//良品数 { strAddr = "D" + (1070 + hour); } int ProdNGQty = CommonMethods.plcDevices[0].ReadInt16(strAddr); return ProdNGQty; } catch (Exception ex) { MessageBox.Show(ex.Message, "系统异常", MessageBoxButtons.OK, MessageBoxIcon.Hand); } return 0; } #endregion #region 5、设备状态 /// /// 定时获取设备状态 /// /// /// int OldStatues = Convert.ToInt32(IniConfigHelper.ReadIniData("上一次设备状态", "DeviceStatus", "1", CommonMethods.strDeviceStatusPath));//退出程序前设备状态 int StatuesCount = 0; DateTime StatuesBeginTime = DateTime.Now.AddMinutes(-6); string strGuidAlarm = IniConfigHelper.ReadIniData("上一次Guid", "DeviceGuid", Guid.NewGuid().ToString(), CommonMethods.strDeviceStatusPath);//退出程序前Guid bool run_AlarmMark = false;//特殊情况1标志(运行状态) bool run_IdleMark = false;//特殊情况2标志(闲置状态) bool isUpIdleAlarm = false;//闲置状态报警信息上传标志 List idleAlarmData = new List();//闲置状态的报警信息 private async void ShowStateEven_Run() { try { if (CommonMethods.plcDevices.Count > 0 && CommonMethods.plcDevices != null) { if (CommonMethods.plcDevices[0] != null && CommonMethods.plcDevices[0].IsConnected) { DateTime Date = DateTime.Now; bool isUpload = false; string deviceStatus = string.Empty; string deviceStatusDesc = string.Empty; short NewStatus = CommonMethods.plcDevices[0].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); } // 只要状态变更,就生成新的 GUID strGuidAlarm = Guid.NewGuid().ToString(); // 强制生成新的 GUID isUpload = true; //StatuesCount = 0; OldStatues = NewStatus; StatuesBeginTime = Convert.ToDateTime(Date); //记录最新设备状态和Guid //CommonMethods.UpdateConfig("MES_CONFIG", "DeviceStatus", NewStatus.ToString());// 保存设备状态到xml文件 //CommonMethods.UpdateConfig("MES_CONFIG", "Guid", strGuidAlarm);// 保存设备状态到xml文件 //CommonMethods.Configxml["MES_CONFIG"]["DeviceStatus"] = NewStatus.ToString(); // 保存设备状态到xml文件 //CommonMethods.Configxml["MES_CONFIG"]["Guid"] = strGuidAlarm; // 保存设备状态到xml文件 //CommonMethods.WriteXmlFile(CommonMethods.Configxml, CommonMethods.xmlFilePath); IniConfigHelper.WriteIniData("上一次设备状态", "DeviceStatus", NewStatus.ToString(), CommonMethods.strDeviceStatusPath); IniConfigHelper.WriteIniData("上一次Guid", "DeviceGuid", strGuidAlarm, CommonMethods.strDeviceStatusPath); } else { // 同种状态持续 5 分钟时上传状态信息 //StatuesCount++; //if (StatuesCount >= 5 * 60) //{ // isUpload = true; // StatuesCount = 0; //} if (Date.Subtract(StatuesBeginTime).TotalSeconds >= 5 * 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); 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 ); if (success) { StatuesCount = 0; } } } } } } catch (Exception ex) { LogHelper.Instance.WriteEX(ex); } } #region 触摸屏弹窗 private async void ShowStuesEven_Run(string strGuid) { if (CommonMethods.plcDevices[0].IsConnected) { string strCode = ""; string alarmName = ""; short HMIStatus = CommonMethods.plcDevices[0].ReadInt16("D40"); if (HMIStatus != 0) { CommonMethods.AddDataMonitorLog(0, "触发了手动HMI弹窗功能".Translated()); switch (HMIStatus) //注意:B1-D12都为闲置代码,上传代码是只需上传闲置代码 { //case 1://取消 // strCode = "B1"; // alarmName = "计划维保"; // CommonMethods.AddDataMonitorLog(0, "B1 计划维保"); // break; //case 2://取消 // strCode = "B2"; // alarmName = "无生产计划"; // CommonMethods.AddDataMonitorLog(0, "B2 无生产计划"); // break; //case 3://取消 // strCode = "B3"; // alarmName = "样品、试产"; // CommonMethods.AddDataMonitorLog(0, "B3 样品、试产"); // break; //case 4://取消 // strCode = "B4"; // alarmName = "其他计划停机"; // CommonMethods.AddDataMonitorLog(0, "B4.其他计划停机"); // break; 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); if (success) CommonMethods.AddDataMonitorLog(0, "HMI手动弹窗状态已上传成功,代码".Translated() + $":{strCode}"); } bool IsSuccess = CommonMethods.plcDevices[0].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); 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 = System.Environment.CurrentDirectory + "\\欧姆龙报警寄存器对应表.csv"; listAlarmForm = CSVHelper.ReadCSV(strFileName); } catch (Exception ex) { MessageBox.Show("加载报警文件失败!"); } } /// /// 报警信息读取 /// /// /// List listOld = new List(); List listNew = new List(); List listAlarmStatus = null; private async void ShowAlarmEven_Run(string strGuidAlarm) { if (!CommonMethods.IsRun || !CommonMethods.plcDevices[0].IsConnected) { return; } try { // 1. 读取 PLC 报警信息 var alarmStatusList = ReadAlarmStatusFromPLC(); if (alarmStatusList == null || listAlarmForm == null)//true { return; } // 2. 解析报警信息并生成新的报警列表 var newAlarms = ParseAlarmStatus(alarmStatusList, strGuidAlarm); if (newAlarms.Count > 0) { // 3. 检测新报警并添加到旧报警列表,上传第一次报警信息 await DetectNewAlarmsAsync(newAlarms); // 4. 显示新报警信息 DisplayAlarmInfo(newAlarms); } // 5. 检测已取消的报警并处理 var canceledAlarms = DetectCanceledAlarms(newAlarms); if (canceledAlarms.Count > 0) { // 6. 记录已取消的报警信息(保存本地) LogCanceledAlarms(canceledAlarms); // 7. 上传已取消的报警信息(报警结束上传一次) await UploadCanceledAlarmsAsync(canceledAlarms, strGuidAlarm); } } catch (Exception ex) { LogHelper.Instance.WriteEX(ex); } } /// /// 从 PLC 读取报警状态 /// private List ReadAlarmStatusFromPLC() { //string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); //JYResult resByte = CommonMethods.plcDevices[0].ReadValue>("D10150", DataType.ArrByte, 5000); //if (resByte.IsSuccess && resByte != null) //{ // int startPlcAddr = 10150; // 读取起始地址 // return PLCAlarmParseHelper.OmronFinsByte2Status(startPlcAddr, resByte.Content); //} 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字节 JYResult resByte = CommonMethods.plcDevices[0].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"); switch (LanguageManager.LanguageCode) { case "en"://英语报警信息 return alarmStatusList .Where(s => s.Status) .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData() { AlarmGuid = strGuidAlarm, PLCAdress = s.PLCAdress, AlarmCode = f.AlarmCode, AlarmContent = f.EnAlarmContent == "" ? s.PLCAdress : f.EnAlarmContent, AlarmType = "停机报警", AlarmDesc = f.EnAlarmContent == "" ? s.PLCAdress : f.EnAlarmContent, AlarmState = "1", StartTime = Date, EndTime = "",//Date Flag = 0 }).ToList(); case "hu"://匈牙利语报警信息 return alarmStatusList .Where(s => s.Status) .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData() { AlarmGuid = strGuidAlarm, PLCAdress = s.PLCAdress, AlarmCode = f.AlarmCode, AlarmContent = f.HuAlarmContent == "" ? s.PLCAdress : f.HuAlarmContent, AlarmType = "停机报警", AlarmDesc = f.HuAlarmContent == "" ? s.PLCAdress : f.HuAlarmContent, AlarmState = "1", StartTime = Date, EndTime = "",//Date Flag = 0 }).ToList(); case "ms"://马来语报警信息 return alarmStatusList .Where(s => s.Status) .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData() { AlarmGuid = strGuidAlarm, PLCAdress = s.PLCAdress, AlarmCode = f.AlarmCode, AlarmContent = f.MsAlarmContent == "" ? s.PLCAdress : f.MsAlarmContent, AlarmType = "停机报警", AlarmDesc = f.MsAlarmContent == "" ? s.PLCAdress : f.MsAlarmContent, AlarmState = "1", StartTime = Date, EndTime = "",//Date Flag = 0 }).ToList(); case "zh"://中语报警信息 default: return alarmStatusList .Where(s => s.Status) .Join(listAlarmForm, s => s.PLCAdress, f => f.PLCAdress, (s, f) => new AlarmData() { AlarmGuid = strGuidAlarm, PLCAdress = s.PLCAdress, AlarmCode = f.AlarmCode, AlarmContent = f.AlarmContent == "" ? s.PLCAdress : f.AlarmContent, AlarmType = "停机报警", AlarmDesc = f.AlarmContent == "" ? s.PLCAdress : f.AlarmContent, AlarmState = "1", StartTime = Date, EndTime = "",//Date Flag = 0 }).ToList(); } } /// /// 检测新报警并添加到旧报警列表 /// private async Task DetectNewAlarmsAsync(List newAlarms) { foreach (var alarm in newAlarms) { if (!listOld.Any(o => o.PLCAdress == alarm.PLCAdress)) { listOld.Add(alarm); // 4.5 新增逻辑(报警开始上传一次) await UploadCanceledAlarmsAsync(newAlarms, 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 async Task UploadCanceledAlarmsAsync(List canceledAlarms, string strGuidAlarm) { if (CommonMethods.sysConfig.MesModeSwitching && CommonMethods.mesConfig.isConnected) { await 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 void 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(r => { recordToUpdate = listAlarmStatistics.FirstOrDefault(x => x.PLCAdress.Trim() == r.PLCAdress); if (recordToUpdate != null) { CommonMethods.db.Update(it => it.AlarmCount == it.AlarmCount + 1, it => it.PLCAdress == r.PLCAdress && Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today); } 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 = CommonMethods.db.AddReturnBool(fd); ISAdd = false; } } 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); } CommonMethods.db.AddReturnBool(fd); } GetASqlAlarmData(DateTime.Today, CommonMethods.strClass); } /// /// 读取数据库报警信息 /// /// /// public void 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 = CommonMethods.db.QueryList("AlarmCount", 10, it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today); } else if (strClass == "白班".Translated()) { AlarmDatas = CommonMethods.db.QueryList("AlarmCount", 10, it => it.AlarmTime >= baiClassStart && it.AlarmTime <= baiClassEnd && it.Class == strClass); } else { AlarmDatas = CommonMethods.db.QueryList("AlarmCount", 10, it => it.AlarmTime >= yeClassStart && it.AlarmTime <= yeClassEnd && it.Class == strClass); } 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 /// /// 下发权限到PLC /// /// private async Task SendRightToPLCAsync() { ushort quanxian = 1; switch (CommonMethods.currentAdmin.LoginLevel) { case "操作员": quanxian = 1; break; case "工程师": quanxian = 2; break; case "管理员": quanxian = 3; break; default: break; } foreach (var plc in CommonMethods.plcDevices) { if (plcDeviceMapping.TryGetValue(plc, out var plcDevices)) { foreach (var plcDevice in plcDevices) { if (plcDevice.IsConnected) { JYResult result = await Task.Run(() => plc.WriteValue("D21", (short)quanxian, DataType.Short)); if (result.IsSuccess) { CommonMethods.AddDataMonitorLog(1, $"Write_PLC_permission_Successful"); } else { CommonMethods.AddDataMonitorLog(2, $"Write_PLC_permission(D15)_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) { if (CommonMethods.plcDevices[0].IsConnected) { if (!qiehuan) { if (!SetPlcTime()) { 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() { 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 (CommonMethods.plcDevices[0].IsConnected) { JYResult result = CommonMethods.plcDevices[0].WriteValue("D70", writeArr, DataType.ArrShort); if (result.IsSuccess) { IsSuccess = true; } } return IsSuccess; } #region 9、IDisposable 实现 // 如果需要在StopAsync中也显示进度,可以添加一个进度回调 private Task processTask; public async Task StopAsync(IProgress progress = null) { _cts.Cancel(); if (_heartbeatTask != null) { progress?.Report("正在停止心跳任务..."); try { await _heartbeatTask; } catch (OperationCanceledException) { // 忽略取消异常 } } if (processTask != null) { progress?.Report("正在停止处理任务..."); try { await processTask; } catch (OperationCanceledException) { // 忽略取消异常 } } foreach (var pair in groupTasks) { progress?.Report($"正在停止组任务: {pair.Key}..."); try { await pair.Value; } catch (OperationCanceledException) { // 忽略取消异常 } } } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { _cts?.Cancel(); _cts?.Dispose(); processTask?.Wait(); // 断开所有设备连接 foreach (var plc in CommonMethods.plcDevices) { plc.Disconnect(); //if (plcAlarmReaders.TryGetValue(plc, out var alarmReader)) //{ // alarmReader.PLCAlarm_New -= AlrmReader_PLCAlarm_New; // alarmReader.PLCAlarm_Remove -= AlrmReader_PLCAlarm_Remove; //} } CommonMethods.plcDevices.Clear(); plcDeviceMapping.Clear(); plcAlarmReaders.Clear(); CommonMethods.plcStatuses.Clear(); DisconnectAllPlcs(); } } #endregion #endregion #region 7、辅助方法 private static PLCType GetPLCTypeEnum(string plcTypeString) { if (Enum.TryParse(plcTypeString, out PLCType plcType)) { return plcType; } throw new ArgumentException($"PLC 类型无效: {plcTypeString}"); } #endregion } }