Files
1258/JinYuan.ControlCenter/ControlCenter.cs
T

2838 lines
118 KiB
C#
Raw Normal View History

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