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.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using DataType = PLC.DataType; namespace JinYuan.ControlCenters { public partial class ControlCenter : IDisposable { 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(); private readonly ConcurrentDictionary _lastOpenValues = new ConcurrentDictionary(); public CancellationTokenSource _cts; private Task _heartbeatTask; private const int HeartbeatInterval = 1000; // 1 second /// /// 定时器-定时刷新状态和数据 /// private System.Timers.Timer Timer_RefreshData; private readonly string configFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config"); private ControlCenter() { _cts = new CancellationTokenSource(); if (CommonMethods.plcDevices == null) { CommonMethods.plcDevices = new List(); } InitializeDevicesFromExcel(); GetAlarmData(); InitFunc(); Timer_RefreshData = new System.Timers.Timer(1000); Timer_RefreshData.Elapsed += Timer_RefreshData_Elapsed; Timer_RefreshData.Start(); Thread thread = new Thread(ShowStateEven_Run); thread.IsBackground = true; thread.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, @"D:\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>> { ["电芯进站A"] = FeedingStationA, ["电芯进站B"] = FeedingStationB, ["电芯出站A"] = BlankingStationA, ["喷码工位A"] = JettingStationA, ["喷码工位B"] = JettingStationB, ["复投工位"] = ReInStation, //["电芯出站B"] = BlankingStationB, //["结构件料盘"] = MaterialStation, // 根据需要添加更多站点功能 }; } public async Task StartAsync() { CreatePLCThreads(); _heartbeatTask = StartHeartbeatCheck(_cts.Token); await Task.CompletedTask; } #region PLC 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(); var plcVariableConfigs = MiniExcel.Query(plcVariableConfigPath).ToList(); ConcurrentDictionary PLCVariable = new ConcurrentDictionary(); if (plcVariableConfigs.Count > 0) { foreach (PLCGroupVariable variable in plcVariableConfigs) { if (variable.VarAddress != null && !variable.VarAddress.Equals("")) { PLCVariable.TryAdd(variable.VarAddress.Trim(), variable); } } } CommonMethods.PLCVariable = PLCVariable; 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(); } plcDeviceMapping[plc] = new List { deviceConfig }; CommonMethods.plcDevices.Add(plc); string tmp = AppDomain.CurrentDomain.BaseDirectory; // 初始化 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()); } #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 sw = new Stopwatch(); var (group, plcIndex) = tuple; string groupKey = group.GroupName; 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); // 获取上一次的值,如果不存在则默认为0 short lastValue = _lastOpenValues.GetOrAdd(groupKey, 0); // 从0到1才执行 if (OpenValue.IsSuccess && OpenValue.Content == 1 && lastValue == 0) { LogHelper.Instance.WriteLog($"[{groupKey}] 检测到上升沿(0->1),开始处理", "PLC线程"); // 更新上一次的值 _lastOpenValues[groupKey] = 1; sw.Restart(); JYResult resByte = CommonMethods.plcDevices[plcIndex].ReadValue>($"{group.VarList[2].VarAddress}", DataType.ArrByte, Convert.ToUInt16(group.VarList[2].OffsetOrLength)); sw.Stop(); LogHelper.Instance.WriteLog($"[{groupKey}] 读取数据区 {group.VarList[2].VarAddress}, " + $"长度={group.VarList[2].OffsetOrLength}, 成功={resByte?.IsSuccess},耗时:{sw.ElapsedMilliseconds}","PLC线程"); sw.Restart(); bool bRet = CommonMethods.plcDevices[plcIndex].WriteInt16(group.VarList[0].VarAddress, (short)2); sw.Stop(); LogHelper.Instance.WriteLog($"[{groupKey}] 往{group.VarList[0].VarAddress}写2,写入结果:{bRet},耗时:{sw.ElapsedMilliseconds}ms","PLC线程"); if (!bRet) { bRet = CommonMethods.plcDevices[plcIndex].WriteInt16(group.VarList[0].VarAddress, (short)2); LogHelper.Instance.WriteLog($"重试:往{group.VarList[0].VarAddress}写2,写入结果:{bRet}", "PLC线程"); } if (resByte != null && stationFunctions.TryGetValue(group.GroupName, out var action)) { LogHelper.Instance.WriteLog($"[{groupKey}] 开始执行工位逻辑", "PLC线程"); sw.Restart(); Task.Run(() => action(resByte, group)); sw.Stop(); LogHelper.Instance.WriteLog($"[{groupKey}] 工位逻辑执行完成,耗时:{sw.ElapsedMilliseconds}ms", "PLC线程"); } else { bool bRet2 = CommonMethods.plcDevices[plcIndex].WriteInt16(group.VarList[0].VarAddress, (short)2); LogHelper.Instance.WriteLog($"异常情况,resByte={resByte}为空或者{group.GroupName}找不到执行函数,往{group.VarList[0].VarAddress}写0,写入结果:{bRet2}", "PLC线程"); } } else if (OpenValue.IsSuccess && OpenValue.Content == 0) { // 当值变为0时,更新记录,以便下次0->1时触发 _lastOpenValues[groupKey] = 0; } } catch (Exception ex) { LogHelper.Instance.WriteError($"错误: {group.GroupName} - {ex.Message}", "SysErrorLog"); var bRet = CommonMethods.plcDevices[plcIndex].WriteInt16(group.VarList[0].VarAddress, (short)0); LogHelper.Instance.WriteLog($"异常场景下,往{group.VarList[0].VarAddress}写0,写入是否成功:{bRet}", "PLC线程"); } } } Thread.Sleep(100); } } } #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; } } }); 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 { 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; try { result = await Task.Run(() => plc.ReadValue>(plcDevice.HeartBeat, DataType.Short)); } catch (Exception ex) { plcDevice.ErrorTimes++; plcDevice.IsConnected = false; LogHelper.Instance.WriteLog($"PLC编号:{plcDevice.PlcNum} 读取心跳异常:{ex.Message}!!!"); return; } if (result.IsSuccess) { plcDevice.ErrorTimes = 0; JYResult res; try { res = await Task.Run(() => plc.WriteValue(plcDevice.HeartBeat, (short)1, DataType.Short)); } catch (Exception ex) { plcDevice.IsConnected = false; LogHelper.Instance.WriteError($"PLC编号:{plcDevice.PlcNum} 写入心跳异常:{ex.Message}!!!"); return; } if (!res.IsSuccess) { plcDevice.IsConnected = false; LogHelper.Instance.WriteError($"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.WriteError($"编号:[{plcDevice.PlcNum}]的PLC链接断开:{result.Message}"); } } private async Task ReconnectDevice(PlcReadWriteBase plc, PlcDevice plcDevice) { if (!plcDevice.IsFirstConnect) { await Task.Delay(plcDevice.ReConnectTime); try { plc.Disconnect(); } catch (Exception ex) { LogHelper.Instance.WriteError($"编号:[{plcDevice.PlcNum}]的PLC断开链接异常:{ex.Message}"); } } string msg = ""; bool connectResult = false; try { connectResult = await Task.Run(() => plc.Connect(ref msg)); } catch (Exception ex) { msg = ex.Message; connectResult = false; } plcDevice.IsConnected = connectResult; LogHelper.Instance.WriteError(connectResult ? $"PLC编号:{plcDevice.PlcNum}{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接成功{msg}!" : $"[{plcDevice.PlcNum}]#PLC{(plcDevice.IsFirstConnect ? "初次" : "重新")}连接失败{msg}!"); plcDevice.IsFirstConnect = false; } #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(() => { GetEnergyDataFormSerialPort(); UploadEquEnergy(); }); Regular = 0; } if (TotalNum > 4) { //GetEnergyDataFormSerialPort(); 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 > 3) //{ //if (CommonMethods.jetClientA.Connect()) { CommonMethods.jetClientA.connectState = true; } //else { CommonMethods.jetClientA.connectState = false; } //if (CommonMethods.jetClientB.Connect()) { CommonMethods.jetClientB.connectState = true; } //else { CommonMethods.jetClientB.connectState = false; } //if (!CommonMethods.energyMeter.Connected) { 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(async () => { await UpTotalCount(null); Thread.Sleep(50); }); 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] = ShortLib.GetShortFromByteArray(resByte.Content, i * 4, DataFormat.CDAB); } } #endregion } } #endregion #region 保存12小时产能数据 public int totalNum = 0; // 总数 public int okNum = 0; // 良品数 /// /// 储存数据到数据库 /// /// public async Task 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 = await CommonMethods.db.AddReturnBoolAsync(mh); if (!addResult) { LogHelper.Instance.WriteError($"保存小时产能新增记录失败"); } } // 可以添加日志记录成功信息 // Logger.Log($"成功更新/新增产能数据: 日期={strDate}, 小时={statisticalHour}, 时段={statisticalHour}:30~{(statisticalHour + 1) % 24}:29"); } catch (Exception ex) { // 这里应该添加错误日志记录 LogHelper.Instance.WriteEX($"保存小时产能数据失败: ", ex); } } //string strErr = ""; //DateTime dateTime = DateTime.Now; //string strDate = dateTime.ToString("yyyy-MM-dd"); //int Min = dateTime.Minute; //int Hour = dateTime.Hour; //if (Min < 30) //{ // Hour -= 1; //} //if (dateTime.Hour < 30 && dateTime.Hour == 0) //{ // Hour = 0; //} ////读取PLC投入总数 //int ProdCurrAllQty = GetToDayHourQty(Hour, true); ////读取PLC产出数 //int ProdCurrOKQty = GetToDayHourQty(Hour, false); //Hourprod mh = new Hourprod(); //mh.FDate = strDate; //mh.FHour = Hour; //mh.ProdIn = ProdCurrAllQty; //mh.ProdOut = ProdCurrOKQty; //mh.TestTime = DateTime.Now; //mh.Ppm = CommonMethods.Ppm; //mh.JiaDL = Convert.ToDecimal(CommonMethods.JiaDL); ////更新 //int res = CommonMethods.db.UpdateSingle(mh, it => new Hourprod() { ProdIn = mh.ProdIn, ProdOut = mh.ProdOut, TestTime = DateTime.Now, Ppm = mh.Ppm, JiaDL = mh.JiaDL, }, // it => it.FDate == mh.FDate && it.FHour == mh.FHour); //if (res <= 0) //更新失败就新增保存 //{ // CommonMethods.db.AddReturnBool(mh); //} /// /// 每小时产能读取处理 /// /// /// /// 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() { while (true) { try { if (CommonMethods.plcDevices.Count > 0 && CommonMethods.plcDevices != null) { if (CommonMethods.plcDevices[0] != null && CommonMethods.plcDevices[0].IsConnected)//检查plc配置是否启用 { 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);//guid } //设备状态信号灯变量 CommonMethods.Statues = new List { NewStatus == 1, NewStatus == 2, NewStatus == 3, NewStatus == 4 };//bool类型的4个true状态 // 状态切换时生成新的 GUID if (NewStatus != OldStatues) { // 从 Alert 切换到非 Alert 状态时,上传报警信息 if (OldStatues == 3 && NewStatus != 3) { ShowAlarmEven_Run(strGuidAlarm, NewStatus); } // 只要状态变更,就生成新的 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, NewStatus); LogHelper.Instance.WriteLog($"{strGuidAlarm}:设备进入Alert状态", "报警状态记录"); 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); } Thread.Sleep(100); } } #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();//新报警信息表 private void ShowAlarmEven_Run(string strGuidAlarm, int MachineState) { if (!CommonMethods.IsRun || !CommonMethods.plcDevices[0].IsConnected) { return; } try { // 1. 读取 PLC 报警信息 List alarmStatusList = ReadAlarmStatusFromPLC(); if (alarmStatusList == null || listAlarmForm == null)//true { return; } // 2. 解析报警信息并生成新的报警列表 List newAlarms = ParseAlarmStatus(alarmStatusList, strGuidAlarm); if (newAlarms.Count > 0) { LogHelper.Instance.WriteLog($"{strGuidAlarm}: 报警信息为 {string.Join(" | ", newAlarms)}", "报警状态记录"); // 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"); //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 = 10150; int endAddr = 14750; // 根据实际最大地址调整 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"); 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(); } #region /*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 = "停机报警", AlarmType = f.AlarmCode.Length > 3 && f.AlarmCode[2]!= '2' ? "停机报警":"非停机报警", AlarmDesc = f.AlarmContent == "" ? s.PLCAdress : f.AlarmContent, AlarmState = "1", StartTime = Date, EndTime = "",//Date Flag = 0 }).ToList(); }*/ #endregion } // List datas = new List(); private string UploadedAlarmContent = string.Empty; /// /// 检测新报警并添加到旧报警列表 /// 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); Task.Run(() => { SaveAlarmCount(canceledAlarms); Thread.Sleep(50); }); } } /// /// 上传已取消的报警信息 /// private void UploadCanceledAlarmsAsync(List canceledAlarms, string strGuidAlarm) { //for (int i = 0; i < canceledAlarms.Count; i++) //{ // if (canceledAlarms[i].EndTime.Length > 1) //// { // var alarm = new List { canceledAlarms[i] }; 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 ); } // } //} //// datas.Clear(); //listOld.Clear(); } /// /// 报警保存 /// 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); } 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); 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); } /// /// 读取数据库报警信息 /// /// /// 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 = 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 8、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 #region 8、轴/电机位置监控 private void ReadAxleCSV(int flag, ref List axleLog, ref List motorLog) { axleLog = new List(); motorLog = new List(); try { string strLine = ""; string[] csvReset;//轴位置暂存 string PlcAddress = string.Empty; bool isExits = false; switch (flag) { case 1://本机轴设定位置 IniConfigHelper.CreateIniFile(configFolderPath, "Motor_GunBiaoAxle.ini"); if (!File.Exists(Path.Combine(configFolderPath, "Motor_GunBiaoAxle.ini")))//判断ini文件是否存在 { isExits = true; } using (StreamReader reader = new StreamReader(Path.Combine(configFolderPath, "Motor_GunBiaoAxle.csv"), System.Text.Encoding.GetEncoding("GB2312"))) { while ((strLine = reader.ReadLine()) != null) { csvReset = strLine.Split(',', '[', ']'); if (csvReset.Length != 5 && csvReset[0] == "") { continue; } if (string.IsNullOrEmpty(csvReset[4]) || string.IsNullOrWhiteSpace(csvReset[4])) { break; // 空地址跳出读取 } PlcAddress = csvReset[4]; AxleLog axle = new AxleLog(); axle.m_AxleDscribe = csvReset[0] + csvReset[1]; axle.m_AxleLocation = csvReset[3]; axle.m_AxleAddress = PlcAddress; axleLog.Add(axle); if (isExits) { string Value = CommonMethods.plcDevices[0].ReadFloat(PlcAddress).ToString(); IniConfigHelper.WriteIniData(axle.m_AxleDscribe, axle.m_AxleLocation, Value, Path.Combine(configFolderPath, "Motor_GunBiaoAxle.ini")); } } reader.Close(); reader.Dispose(); } break; case 2://本机电机位置设定值motor IniConfigHelper.CreateIniFile(configFolderPath, "Motor_GunBiaoMotor.ini"); if (!File.Exists(Path.Combine(configFolderPath, "Motor_GunBiaoMotor.ini")))//判断ini文件是否存在 { isExits = true; } using (StreamReader reader = new StreamReader(Path.Combine(configFolderPath, "Motor_GunBiaoMotor.csv"), System.Text.Encoding.GetEncoding("GB2312"))) { while ((strLine = reader.ReadLine()) != null) { csvReset = strLine.Split(',', '[', ']'); if (csvReset.Length != 5 && csvReset[0] == "") { continue; } if (string.IsNullOrEmpty(csvReset[4]) || string.IsNullOrWhiteSpace(csvReset[4])) { break; // 空地址跳出读取 } PlcAddress = csvReset[4]; MotorLog motor = new MotorLog(); motor.m_MotorDscribe = csvReset[0] + csvReset[1]; motor.m_MotorLocation = csvReset[3]; motor.m_MotorAddress = PlcAddress; motorLog.Add(motor); if (isExits) { string Value = CommonMethods.plcDevices[0].ReadFloat(PlcAddress).ToString(); IniConfigHelper.WriteIniData(motor.m_MotorDscribe, motor.m_MotorLocation, Value, Path.Combine(configFolderPath, "Motor_GunBiaoMotor.ini")); } } reader.Close(); reader.Dispose(); } break; default: break; } } catch (Exception ex) { axleLog = null; motorLog = null; LogHelper.Instance.WriteError($"轴/电机位置读取配置文件错误:{ex}", "SysErrorLog"); } } /// 轴/电机对象集合 /// /// private static List motorLog = new List(); private static List axleLog = new List(); private static Stopwatch swtest = new Stopwatch(); /// /// 监控轴位置变化 /// public void ShowAxleEven_Run() { try { while (true) { if (CommonMethods.plcDevices[0] != null && CommonMethods.plcDevices[0].IsConnected) { short nowPLCstate = CommonMethods.plcDevices[0].ReadInt16("D10"); if (nowPLCstate != 1) { if (axleLog.Count < 1 || motorLog.Count < 1) { Thread.Sleep(1500); ReadAxleCSV(1, ref axleLog, ref motorLog);//轴位置csv文件不存在,out axlelog=null,导致下次判断axlelog.Count报错(未将对象引用设置到对象实例) Thread.Sleep(1500); } else { try { // 第二种写法一次读出来 swtest.Restart(); JYResult resByte = CommonMethods.plcDevices[0].ReadValue>($"{axleLog[0].m_AxleAddress}", DataType.ArrByte, Convert.ToUInt16(axleLog.Count * 4)); swtest.Stop(); long tempswt = swtest.ElapsedMilliseconds; swtest.Restart(); if (resByte != null && resByte.IsSuccess && resByte.Content != null) { string AxleUpLocationOld; string AxleUpLocationNew; for (int i = 0; i < axleLog.Count; i++) { double NewCurrent = FloatLib.GetFloatFromByteArray(resByte.Content, i * 4); //D23000 起 AxleUpLocationOld = IniConfigHelper.ReadIniData(axleLog[i].m_AxleDscribe, axleLog[i].m_AxleLocation, "0", Path.Combine(configFolderPath, "Motor_GunBiaoAxle.ini")); AxleUpLocationNew = Math.Round(NewCurrent, 2).ToString(); if (AxleUpLocationNew != AxleUpLocationOld) { string AlterTime = DateTime.Now.ToString("yyyy-MM-dd_HH:mm:ss.fff"); //日期时间,电机描述,位置描述,位置修改前,当前位置 LogHelper.PrintMotorLog.LogInfo(AlterTime, axleLog[i].m_AxleDscribe, axleLog[i].m_AxleLocation, AxleUpLocationOld, AxleUpLocationNew); IniConfigHelper.WriteIniData(axleLog[i].m_AxleDscribe, axleLog[i].m_AxleLocation, AxleUpLocationNew, Path.Combine(configFolderPath, "Motor_GunBiaoAxle.ini")); } } } swtest.Stop(); tempswt = swtest.ElapsedMilliseconds; } catch (Exception ex) { LogHelper.Instance.WriteError($"轴位置监控线程错误:{ex}", "SysErrorLog"); } } } } else { Thread.Sleep(5000); } Thread.Sleep(1000); } } catch (Exception ex) { LogHelper.Instance.WriteError($"轴位置监控线程错误:{ex}", "SysErrorLog"); } } /// /// 监控电机位置变化 /// public void ShowMotorEven_Run() { try { while (true) { if (CommonMethods.plcDevices[0] != null && CommonMethods.plcDevices[0].IsConnected) { short nowPLCstate = CommonMethods.plcDevices[0].ReadInt16("D10"); if (nowPLCstate != 1) { if (axleLog.Count < 1 || motorLog.Count < 1) { Thread.Sleep(3000); ReadAxleCSV(2, ref axleLog, ref motorLog);//电机位置csv文件不存在,out axlelog=null,导致下次判断axlelog.Count报错(未将对象引用设置到对象实例) Thread.Sleep(1500); } else { try { // 第二种写法一次读出来 swtest.Restart(); JYResult resByte = CommonMethods.plcDevices[0].ReadValue>($"{motorLog[0].m_MotorAddress}", DataType.ArrByte, Convert.ToUInt16(motorLog.Count * 4)); swtest.Stop(); long tempswt = swtest.ElapsedMilliseconds; swtest.Restart(); if (resByte != null && resByte.IsSuccess && resByte.Content != null) { string MotorUpLocationOld; string MotorUpLocationNew; for (int i = 0; i < motorLog.Count; i++) { double NewCurrent = FloatLib.GetFloatFromByteArray(resByte.Content, i * 4); //D23000 起 MotorUpLocationOld = IniConfigHelper.ReadIniData(motorLog[i].m_MotorDscribe, motorLog[i].m_MotorLocation, "0", Path.Combine(configFolderPath, "Motor_GunBiaoAxle.ini")); MotorUpLocationNew = Math.Round(NewCurrent, 2).ToString(); if (MotorUpLocationNew != MotorUpLocationOld) { string AlterTime = DateTime.Now.ToString("yyyy-MM-dd_HH:mm:ss.fff"); //日期时间,电机描述,位置描述,位置修改前,当前位置 LogHelper.PrintMotorLog.LogInfo(AlterTime, motorLog[i].m_MotorDscribe, motorLog[i].m_MotorLocation, MotorUpLocationOld, MotorUpLocationNew); IniConfigHelper.WriteIniData(motorLog[i].m_MotorDscribe, motorLog[i].m_MotorLocation, MotorUpLocationNew, Path.Combine(configFolderPath, "Motor_GunBiaoAxle.ini")); } } } swtest.Stop(); tempswt = swtest.ElapsedMilliseconds; } catch (Exception ex) { LogHelper.Instance.WriteError($"电机位置监控线程错误:{ex}", "SysErrorLog"); } } } } else { Thread.Sleep(5000); } Thread.Sleep(1000); } } catch (Exception ex) { LogHelper.Instance.WriteError($"电机位置监控线程错误:{ex}", "SysErrorLog"); } } #endregion } }