Files
1257B_DB/JinYuan.ControlCenter/ControlCenter.cs
T
2026-08-06 09:23:30 +08:00

2040 lines
86 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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<string, PlcGroup> groups = new Dictionary<string, PlcGroup>();
private Dictionary<string, Func<JYResult<byte[]>, PlcGroup, Task>> stationFunctions;
//private Dictionary<string, Action<JYResult<byte[]>, PlcGroup>> stationFunctions;
private readonly Dictionary<PlcReadWriteBase, List<PlcDevice>> plcDeviceMapping = new Dictionary<PlcReadWriteBase, List<PlcDevice>>();
private readonly Dictionary<string, Task> groupTasks = new Dictionary<string, Task>();
private readonly Dictionary<PlcReadWriteBase, PLCAlarmReader> plcAlarmReaders = new Dictionary<PlcReadWriteBase, PLCAlarmReader>();
public CancellationTokenSource _cts;
private Task _heartbeatTask;
private const int HeartbeatInterval = 1000; // 1 second
private Dictionary<string, int> group_status = new Dictionary<string, int>();
/// <summary>
/// 定时器-定时刷新状态和数据
/// </summary>
private System.Timers.Timer Timer_RefreshData;
private readonly string configFolderPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Config");
#endregion
#region 2、初始化函数
/// <summary>
/// 构造函数
/// </summary>
private ControlCenter()
{
_cts = new CancellationTokenSource();
if (CommonMethods.plcDevices == null)
{
CommonMethods.plcDevices = new List<PlcReadWriteBase>();
}
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();
}
}
/// <summary>
/// 初始化 工位方法,添加到字典里面
/// </summary>
private void InitFunc()
{
stationFunctions = new Dictionary<string, Func<JYResult<byte[]>, PlcGroup, Task>>
{
["包装箱进站"] = FeedingStation, // 线头扫描,包装箱进站
["中部扫描"] = FeedingStationB, // 中部扫描
["尾部扫描"] = FeedingStationC, // 尾部扫描
//["箱唛标签打印"] = PrintContainerStation, // 箱唛打印
//["栈板标签打印"] = PrintPalletStation, // 栈板打印,O2不打印
//["3S标签打印"] = Print3SStation, // 3S打印 在线网站拿数据的,贴标打印机自行打印
//["6J标签打印"] = Print6JStation, // 6J打印
//["绑定客户标签"] = BindingLabelStation, // 绑定客户标签
["包装箱出站"] = BlankingStation, // 包装箱出站
["中转数据"] = TransferStation, // 中转数据
};
}
/// <summary>
/// 启动心跳及工位读取线程
/// </summary>
/// <returns></returns>
public async Task StartAsync()
{
CreatePLCThreads();
_heartbeatTask = StartHeartbeatCheck(_cts.Token);
await Task.CompletedTask;
}
/// <summary>
/// 加载配置文件
/// </summary>
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<PlcDevice>(deviceConfigPath).ToList();
var groupConfigs = MiniExcel.Query<PlcGroup>(groupConfigPath).ToList();
var variableConfigs = MiniExcel.Query<PlcVariable>(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<PlcDevice> { 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<string, object>
{
["OldStatus"] = (short)0,
["StatusCount"] = 0,
["GuidAlarm"] = string.Empty,
["Status"] = new List<bool> { false, false, false, false }
};
}
}
catch (Exception ex)
{
LogHelper.Instance.WriteError($"加载配置失败:{ex.Message}");
}
CommonMethods.AddDataMonitorLog(1, "配置文件加载完成".Translated());
}
#endregion
#region 3、PLC读写
#region 创建PLC线程 几个工位就几个线程
/// <summary>
/// 按工位读取线程
/// </summary>
///
private readonly Dictionary<string, Thread> groupThreads = new Dictionary<string, Thread>();
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工位状态
/// <summary>
/// 读取PLC循环工位
/// </summary>
/// <param name="plcModel"></param>
///
private void ReadPLCWorkModel(object parameter)
{
if (parameter is ValueTuple<PlcGroup, int> tuple)
{
var (group, plcIndex) = tuple;
while (!_cts.Token.IsCancellationRequested)
{
if (CommonMethods.IsRun)
{
if (CommonMethods.plcDevices[plcIndex].IsConnected)
{
try
{
JYResult<short> OpenValue = CommonMethods.plcDevices[plcIndex].ReadValue<JYResult<short>>(group.VarList[0].VarAddress, DataType.Short);
if (OpenValue.IsSuccess && OpenValue.Content == 1 && group_status[group.GroupName] == 0)
{
group_status[group.GroupName] = 1;
JYResult<byte[]> resByte = CommonMethods.plcDevices[plcIndex].ReadValue<JYResult<byte[]>>($"{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<short> checkValue = CommonMethods.plcDevices[plcIndex].ReadValue<JYResult<short>>(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 开始停止联机事件
/// <summary>
/// 开始联机
/// </summary>
/// <returns></returns>
public async Task<bool> 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;
}
/// <summary>
/// 断开链接
/// </summary>
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
/// <summary>
/// Mes心跳
/// </summary>
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);
}
}
/// <summary>
/// Ping 检查网络线路问题
/// </summary>
/// <returns></returns>
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、心跳和状态检查
/// <summary>
/// 心跳检测
/// </summary>
/// <param name="cancellationToken"></param>
/// <returns></returns>
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);
}
}
/// <summary>
///
/// </summary>
/// <param name="plc"></param>
/// <param name="plcDevice"></param>
/// <returns></returns>
private async Task CheckHeartbeatForDevice(PlcReadWriteBase plc, PlcDevice plcDevice)
{
if (plcDevice.IsConnected)
{
JYResult result = await Task.Run(() => plc.ReadValue<JYResult<short>>(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<ushort[]> result = null;
// // 使用 Task.Run 和超时机制
// var task = Task.Run(() =>
// {
// try
// {
// return plc.ReadValue<JYResult<ushort[]>>("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 的报警读取器");
// }
//}
/// <summary>
/// 新增报警
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//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}");
// });
//}
/// <summary>
/// 报警结束移除报警
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
//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、设备状态检查 ×
/// <summary>
/// 设备状态
/// </summary>
/// <returns></returns>
//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<JYResult<short>>("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<bool> { newStatus == 1, newStatus == 2, newStatus == 3, newStatus == 4 };
// CommonMethods.Statues = new List<bool> { 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));
// }
//}
/// <summary>
/// HMI手动触摸屏状态
/// </summary>
/// <param name="Date"></param>
/// <param name="strGuid"></param>
/// <param name="plc"></param>
/// <returns></returns>
//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<JYResult<short>>("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<AlarmData> alarmObj = new List<AlarmData>();
// 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 设备综合信息
/// <summary>
/// 读取PLC通用信息
/// </summary>
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<int>(6, 0);
JYResult<byte[]> resByte = CommonMethods.plcDevices[0].ReadValue<JYResult<byte[]>>($"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; // 良品数
/// <summary>
/// 储存数据到数据库
/// </summary>
/// <param name="DataCount"></param>
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<Hourprod>(mh);
if (!addResult)
{
LogHelper.Instance.WriteError($"保存小时产能新增记录失败");
}
}
// 可以添加日志记录成功信息
// Logger.Log($"成功更新/新增产能数据: 日期={strDate}, 小时={statisticalHour}, 时段={statisticalHour}:30~{(statisticalHour + 1) % 24}:29");
}
catch (Exception ex)
{
// 这里应该添加错误日志记录
LogHelper.Instance.WriteEX($"保存小时产能数据失败: ", ex);
}
}
/// <summary>
/// 每小时产能读取处理
/// </summary>
/// <param name="hour"></param>
/// <param name="flag"></param>
/// <returns></returns>
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、设备状态
/// <summary>
/// 定时获取设备状态
/// </summary>
/// <param name="mt"></param>
///
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<AlarmData> idleAlarmData = new List<AlarmData>();//闲置状态的报警信息
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<bool> { 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<AlarmData> alarmObj = new List<AlarmData>();
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
/// <summary>
/// HMI手动弹窗结束状态上传
/// </summary>
/// <param name="idleAlarmDate"></param>
/// <param name="strGuidAlarm"></param>
/// <returns></returns>
private async Task UpIdleDataAsync(List<AlarmData> 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<AlarmForm> listAlarmForm = null;
public void GetAlarmData()
{
//加载PLC报警文件
try
{
string strFileName = System.Environment.CurrentDirectory + "\\欧姆龙报警寄存器对应表.csv";
listAlarmForm = CSVHelper<AlarmForm>.ReadCSV<AlarmForm>(strFileName);
}
catch (Exception ex)
{
MessageBox.Show("加载报警文件失败!");
}
}
/// <summary>
/// 报警信息读取
/// </summary>
/// <param name="mt"></param>
/// <param name="resByte"></param>
List<AlarmData> listOld = new List<AlarmData>();
List<AlarmData> listNew = new List<AlarmData>();
List<AlarmStatus> 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);
}
}
/// <summary>
/// 从 PLC 读取报警状态
/// </summary>
private List<AlarmStatus> ReadAlarmStatusFromPLC()
{
//string Date = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
//JYResult<byte[]> resByte = CommonMethods.plcDevices[0].ReadValue<JYResult<byte[]>>("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<byte[]> resByte = CommonMethods.plcDevices[0].ReadValue<JYResult<byte[]>>(
$"D{startAddr}", DataType.ArrByte, (ushort)byteCount);
if (resByte.IsSuccess && resByte.Content != null)
{
return PLCAlarmParseHelper.OmronFinsByte2Status(startAddr, resByte.Content);
}
return null;
}
/// <summary>
/// 解析报警状态并生成新的报警列表
/// </summary>
private List<AlarmData> ParseAlarmStatus(List<AlarmStatus> 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();
}
}
/// <summary>
/// 检测新报警并添加到旧报警列表
/// </summary>
private async Task DetectNewAlarmsAsync(List<AlarmData> newAlarms)
{
foreach (var alarm in newAlarms)
{
if (!listOld.Any(o => o.PLCAdress == alarm.PLCAdress))
{
listOld.Add(alarm);
// 4.5 新增逻辑(报警开始上传一次)
await UploadCanceledAlarmsAsync(newAlarms, strGuidAlarm);
}
}
}
/// <summary>
/// 显示报警信息
/// </summary>
private void DisplayAlarmInfo(List<AlarmData> 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);
}
}
/// <summary>
/// 检测已取消的报警
/// </summary>
private List<AlarmData> DetectCanceledAlarms(List<AlarmData> newAlarms)
{
var canceledAlarms = new List<AlarmData>();
var listOldAlarm = new List<AlarmData>(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;
}
/// <summary>
/// 记录已取消的报警信息
/// </summary>
private void LogCanceledAlarms(List<AlarmData> 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<AlarmCSVData>.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);
}
}
/// <summary>
/// 上传已取消的报警信息
/// </summary>
private async Task UploadCanceledAlarmsAsync(List<AlarmData> 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
);
}
}
/// <summary>
/// 告警TOP10保存
/// </summary>
private void SaveAlarmCount(List<AlarmData> alarmDatas)
{
bool ISAdd = false;
var recordToUpdate = new AlarmCountEntry();
DateTime today = DateTime.Today;
DateTime upTime = DateTime.Now;
var listAlarmStatistics = CommonMethods.db.QueryWhereList<AlarmCountEntry>(it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today);
List<AlarmCountEntry> fd = new List<AlarmCountEntry>();
if (listAlarmStatistics != null)
{
alarmDatas.ForEach(r =>
{
recordToUpdate = listAlarmStatistics.FirstOrDefault(x => x.PLCAdress.Trim() == r.PLCAdress);
if (recordToUpdate != null)
{
CommonMethods.db.Update<AlarmCountEntry>(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);
}
/// <summary>
/// 读取数据库报警信息
/// </summary>
/// <param name="today"></param>
/// <param name="strClass"></param>
public void GetASqlAlarmData(DateTime today, string strClass)
{
try
{
List<ChartDataType> chartAlarms = new List<ChartDataType>();
List<AlarmCountEntry> AlarmDatas = null;
List<string> List_X = new List<string>();
List<int> List_Y = new List<int>();
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<AlarmCountEntry>("AlarmCount", 10, it => Convert.ToDateTime(it.AlarmTime.ToString("yyyy-MM-dd")) == today);
}
else if (strClass == "白班".Translated())
{
AlarmDatas = CommonMethods.db.QueryList<AlarmCountEntry>("AlarmCount", 10, it => it.AlarmTime >= baiClassStart && it.AlarmTime <= baiClassEnd && it.Class == strClass);
}
else
{
AlarmDatas = CommonMethods.db.QueryList<AlarmCountEntry>("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
/// <summary>
/// 下发权限到PLC
/// </summary>
///
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时间
/// <summary>
/// 写当前时间给PLC
/// </summary>
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<string> 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
}
}